はじめての Qt Creator - フォーム編集 - 練習
(掲載 2023年7月28日)
前回のフォーム編集の基礎知識の説明について、実際に動かして練習をします。
フォーム編集で、レイアウト機能を使わずに、Push Button (クラスは QPushButton) をひとつ配置すると以下のようになります。
フォーム編集結果から生成される C++ コードの Push Button 配置の部分は、以下のようになります。Widget は QWidget インスタンスへのポインターです。
QPushButton* button;
button = new QPushButton(Widget);
button->setGeometry(QRect(215, 134, 70, 32));
配置した Push Button をダブルクリックするとボタンテキストを編集できます。ボタンテキストを Button に変更し、ツールバーの サイズ調整 をクリックして、サイズヒントの大きさにしています。QRect の実引数は、x、y、幅、高さで、このように操作しているので、幅と高さはサイズヒントの大きさになっています。子ウィジェットの配置の要点は以下の通りです。
- 子ウィジェットは親ウィジェットを指定してインスタンス生成する
- その結果として、子ウィジェットは親ウィジェットの上に配置される
- setGeometry() で、親ウィジェットの左上を起点として位置が指定され、大きさが設定される
- 子ウィジェットは親ウィジェットでクリップされる
setGeometry() で子ウィジェットの位置と大きさを指定する代わりに、位置を move(int x, int y) で設定でき、サイズを resize(int width, int height) で設定できます。親ウィジェットの幅を小さくすると以下のように子ウィジェットがクリップされます。クリップされないようにするには、親ウィジェットのサイズ変更時に、Push Button の位置を調整するコードを書く必要があります。
ここで説明した機能だけを用いて、ウィジェットのレイアウトができます。しかし、ウィンドウサイズが変更されたときやフォントやテキストが変更されたときにウィジェットの位置と大きさを変更するコードを書かなければなりません。これは手間がかかる作業のため Qt にはレイアウトマネージメント機能が用意されていて、内部で setGeometry() や move()、resize() などを使って、自動的にウィジェットのレイアウト調整ができるようになっています。
レイアウトのコード
フォーム編集を以下のようにした場合に生成される C++ コードを説明します。
- Push Button を 2 つ配置する
- ボタンテキストを Left と Right に変更する
- フォームの空いている部分をクリックして、フォームを選択状態にしてから、ツールバーの 水平に並べる をクリックする
- Push Button 上でマウス右ボタンをクリックしてメニューを開き、オブジェクト名を変更... を選択して、オブジェクト名をそれぞれ leftButton と rightButton に変更する
手書きのコード
コードを手書きすると以下のようようになります。以降のフォーム編集の結果から生成されるコードと比べてみてください。
widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr);
};
#endif // WIDGET_H
widget.cpp:
#include "widget.h"
#include <QPushButton>
#include <QLayout>
Widget::Widget(QWidget* parent)
: QWidget(parent)
{
setWindowTitle("Widget");
resize(500, 300);
auto topLayout = new QHBoxLayout(this);
auto leftButton = new QPushButton("Left");
topLayout->addWidget(leftButton);
auto rightButton = new QPushButton("Right");
topLayout->addWidget(rightButton);
}
フォーム編集の結果は、XML 形式ファイルに保存され、そのサフィックスは .ui です。
この .ui ファイルを Qt に用意された uic コマンドで C++ コードに変換しファイルに保存して用います。
保存するファイル名は ui_widget.h で、widget の部分はクラス名を小文字にした名前が使われます。
ui_widget.h:
/********************************************************************************
** Form generated from reading UI file 'widget.ui'
**
** Created by: Qt User Interface Compiler version 5.15.14
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_WIDGET_H
#define UI_WIDGET_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_Widget
{
public:
QHBoxLayout *topLayout;
QPushButton *leftButton;
QPushButton *rightButton;
void setupUi(QWidget *Widget)
{
if (Widget->objectName().isEmpty())
Widget->setObjectName(QString::fromUtf8("Widget"));
Widget->resize(500, 300);
topLayout = new QHBoxLayout(Widget);
topLayout->setObjectName(QString::fromUtf8("topLayout"));
leftButton = new QPushButton(Widget);
leftButton->setObjectName(QString::fromUtf8("leftButton"));
topLayout->addWidget(leftButton);
rightButton = new QPushButton(Widget);
rightButton->setObjectName(QString::fromUtf8("rightButton"));
topLayout->addWidget(rightButton);
retranslateUi(Widget);
QMetaObject::connectSlotsByName(Widget);
} // setupUi
void retranslateUi(QWidget *Widget)
{
Widget->setWindowTitle(QCoreApplication::translate("Widget", "Widget", nullptr));
leftButton->setText(QCoreApplication::translate("Widget", "Left", nullptr));
rightButton->setText(QCoreApplication::translate("Widget", "Right", nullptr));
} // retranslateUi
};
namespace Ui {
class Widget: public Ui_Widget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_WIDGET_H
手書きよりもコード量が多くなっているのは、レイアウト以外の処理もしているためです。コードの特徴は、以下のようになります。
- フォームのオブジェクト名がクラス名に対応している
- 子ウィジェットのオブジェクト名が子ウィジェットのインスタンスを抱えるメンバ変数名に対応している
- setupUi() メンバー関数の引数にフォームのクラスのインスタンスを渡すと、そのインスタンスに子ウィジェットを配置するようになっている
- テキストはコンストラクターでは設定せずに retranslateUi() メンバー関数にまとめられている
- オートコネクションと呼ばれるシグナルとスロットの自動接続のために QMetaObject::connectSlotsByName() が呼ばれている
retranslateUi() は、実行時の言語変更のために用いられます。簡単に言うと、言語変更を通知するイベントが来たときに、この関数を呼ぶようにします。第 1 回で説明したように、今回のプロジェクト作成手順では、実行時の言語変更を考慮したコードは生成されません。
オートコネクションは、シグナルに関連付けされたスロット名からシグナルとスロットを接続する機能です。Qt Creator のフォーム編集で、そのようなスロットの作成をサポートしてます。実際の操作方法は、配置した部品上でマウス右ボタンをクリックしてメニューを開き、スロットへ移動... を選択します。一見便利なようてですが、最近は以下の理由により非推奨機能になっています。
- 明示的なコードの理解
オートコネクションは、特定のシグナルとスロットを自動的に接続する機能です。これにより、コードが自動的に動作するように見えますが、実際の接続がどのように行われているかを明確に理解することが難しくなります。また、スロット名が処理内容を適切に反映しないため、スロット名から処理内容を推測できません。特に大規模なプロジェクトでは、誤って接続されたシグナルやスロットを見つけることが困難になり、メンテナンス性が低下します。
- コードの可読性 オートコネクションを使うとコードが簡単になりますが、複雑になると理解が難しくなります。明示的なコネクションを使ってコードの構造を明確にする方が可読性が高くなります。
- リファクタリングの容易さ 明示的なコネクションを使用すると、コードのリファクタリングが容易になります。シグナルやスロットの名前を変更する必要がある場合や、接続を別のクラスに移動したい場合に、オートコネクションはそれらの変更を反映するのが難しくなります。明示的なコネクションを使用すると、変更が直接反映されるため、保守性が向上します。
以上の理由から、Qt ウィジェットの開発においては、可能な限り明示的なコネクションを使用することをお勧めします。明確で読みやすいコードを書くことで、保守性や拡張性が向上し、バグの可能性も低減されます。
フォーム編集での実際のレイアウト操作
前回説明したレイアウトの機能を実際にフォーム編集で試してみましょう。
水平/垂直ボックスレイアウト
水平と垂直のボックスレイアウトはどちらも使い方は同じなので、水平ボックスレイアウトについて説明します。
まず、第 1 回で説明した Qt ウィジェットアプリケーションプロジェクトを適当なプロジェクト名で、クラス名を Widget にして作成してください。次に、以下の手順でフォーム編集操作をします。
- プロパティーエディターでフォームの幅を 500、高さを 200 に設定します
- ウィジェットボックスから Push Button をフォームにドラグ&ドロップします
- フォーム上の Push Button をダブルクリックして編集状態にし、ボタンテキストを Left に変更し、リターンキーで編集を完了します
- フォーム上の Push Button を右クリックしてメニューを開き、オブジェクト名を変更を選択して、オブジェクト名を変更ダイアログを開き、オブジェクト名を leftButton に変更します
- フォーム上の Push Button をクリックして選択します
- flat プロパティーをチェックして true に設定します
- autoFillBackground プロパティーをチェックして true に設定します
- palette プロパティーの値をクリックし、パレットを編集ダイアログを開きます
- 色役割 Button の値をダブルクリックして、色選択ダイアログで、色を赤色に変更します
- パレットを編集ダイアログの OK をクリックして、Push Button の背景色を確定します
以下の手順でもうひとつ Push Button を配置します。
- フォーム上の Push Button をクリックして選択します
- コピー & ペーストし、ドラグしてフォーム上の Push Button の右横に配置します
- ボタンテキストを Right に変更し、オブジェクト名を rightButton に変更し、色役割 Button の値を青色に変更します
以下の手順でフォーム上の 2 の PushButton をレイアウトで水平に並べます。
- フォーム上の 2 つの Push Button をラバーバンドで囲んで両方共に選択状態にします
- ツールバーの水平に並べるをクリックします
以上のフォーム編集をすると以下のようになり、部品間のスペースやマージンなどがはっきりして、レアアウトがどのように機能するか理解しやすくなります。
赤い細い矩形は、横方向レイアウトを示しています。2 つの Push Button はサイズヒントの大きさで、Push Button 間の隙間はスペーシングのデフォルト値になっています。
以下のフォーム編集操作をしてください。
- スペーシングの隙間をクリックしてレイアウトを選択状態にします
- 下方にドラグして上に隙間を作ります
- ウィジェットボックスから Text Edit を 2 の Push Button の上方に配置します
- フォームの空いている部分をクリックして、フォームを選択状態にします
- ツールバーの縦に並べるをクリックして、フォームにトップレベルのレイアウトを付けます
上記フォーム編集を行うと以下のようになります。
Text Edit の縦方向のポリシーが Expanding なので、縦方向に広がっています。2 つの Push Button の幅が同じなのは、横方向に十分な空白があり、共にストレッチファクターがデフォルトの 0 のままだからです。マージンについて、フォームの垂直方向レイアウトのマージンは 0 てではなく、Push Button を水平に並べるレイアウトのマージンが 0 になっていることに注意してください。
ツールバーのサイズ調整ををクリックすると以下のようになります。
Text Edit の大きさは、サイズヒントのサイズになっています。フォームの右下のサイズグリップでフォームの大きさを最も小さくしてみてください。Text Edit は QTextEdit::minimumSizeHint() の大きさである 74x74 まで小さくできますが、2 つ並んだ Push Button の横幅のため、高さは 74 になっても幅は 2 つ並んだ Push Button の横幅になります。2 つの Push Button の大きさは、サイズヒントの大きさになっています。
ビルドせずにプレビューをしてみましょう。ツールメニュー → フォームエディター → プレビューと辿ります。プレビューウィンドウのサイズを変更して子ウィジェットがレイアウト機能でうまくレイアウトされることを確認してください。確認ができたならば、プレヒューウィンドウのクローズボタンをクリックしてウィンドウを閉じます。
レイアウトの機能をいくつか試してみましょう。
- 2 つの Push Button を囲むレイアウトの layoutSpacing の値を 0 にすると Push Button 間の隙間がなくなります。
- 左の Push Button 横方向のストレッチを 1 に設定すると左の Push Button が余白を吸収して、横一杯に広がります。右の Push Button の幅は、サイズヒントの横幅になります。
- 右の Push Button の横方向のストレッチを 2 に設定すると 2 つの Push Button の横幅の比は 1:2 になります。
- 2 つの Push Button の間に Horizontal Spacer をドラグ&ドロップして配置します。スペーサーの横幅は、そのサイズヒントの幅になり、2 つの Push Button の横幅は、レイアウトの幅からスペーサーの幅を引いた値を 1:2 の比率で配分した幅になります。フォームの大きさを小さくするとスペーサーの幅は 0 まで小さくなります。
- スペーサーをクリックして選択し、sizeType プロパティーを Fixed に変更します。フォームの大きさを小さくしてもスペーサー幅は固定されたままになります。
- 2 つの Push Button のストレッチファクターをデフォルトの 0 に戻し、スペーサーの sizeType もデフォルトの Expanding に戻します。スペーサーが横いっぱいに広がって、2 つの Push Button の幅は、サイズヒントの幅になります。
- ウィジェットのストレッチファクターとレイアウトのストレッチファクターを設定して、どのように適用されるかを試してみてください。特にレイアウトのストレッチファクターが優先されることを確認してみましょう。
次は、アライメントの練習です。 新たに、Qt ウィジェットアプリケーションプロジェクトを適当なプロジェクト名で、クラス名を Widget にして作成してください。以下の手順でフォーム編集操作をします。
- プロパティーエディターでフォームの幅を 400、高さを 300 に設定します
- ウィジェットボックスから Push Button をフォームにドラグ&ドロップします
- フォーム上の Push Button をダブルクリックして編集状態にし、ボタンテキストを Button に変更し、リターンキーで編集を完了します
- フォーム上の Push Button をクリックして選択します
- フォームの空いている部分をクリックして、フォームを選択状態にします
- ツールバーの垂直に並べるをクリックします
以上のフォーム編集をすると以下のようになります。
アラインメントを試してみましょう。
- Push Button の上で、マウスの右ボタンをクリックしてメニーューを開き、レイアウトの配置サブメニューの中央揃え(横方向) を選択します。
- 左端揃えを選択すると左端に配置されます。
- 右端揃えを選択すると左端に配置されます。
アラインメントを横方向の調整なしに設定し、ツールバーのレイアウトを破棄をクリックし、横方向のレイアウトをクリックします。
- Push Button の上で、マウスの右ボタンをクリックしてメニーューを開き、レイアウトの配置サブメニューの中央揃え(横方向) を選択します。
- 上端揃えを選択すると上端に配置されます。
- 左端揃えを選択すると左上に配置されます。
- 複数の Push Button を水平に並べて、それぞれにアラインメントを適用できます。
グリッドレイアウトでアラインメントを試してみましょう。
- Text Edit と Push Button をレイアウトを使わずに以下のように置きます。大体の位置とサイズで構いません。
- フォームの空いている部分をクリックして、フォームを選択状態にし、ツールバーの格子状に並べるをクリックします。
- Push Button のアラインメントを中央揃え(横方向) に設定します
今回は、フォーム編集をいろいろ練習しました。次回は、練習したフォーム編集から生成されたコードを手書きのコードと対比して説明します。