技術文章Qt


 
レイアウトのお話
Nobuhide Tsuda
16-Apr-2011

GUIプログラムは、ウィジットのレイアウトが見やすく、操作しやすいことが肝要である。 また、場合によっては動的にレイアウトを変えたい場合もある。
Qt には QLabel、QPushButton などなど、たくさんの便利なウィジットと、 それらを簡単かつ自由にレイアウトするためのクラスが用意されている。 ウィジットを画面に適切にレイアウトし、signal と処理 slot を適切に接続すればGUIなプログラムを簡単に作成することができる。

本稿ではウィジットをレイアウトする方法について解説する。

setGeomeatry() によるレイアウト

 1: MainWindow::MainWindow(QWidget *parent, Qt::WFlags flags)
 2:     : QMainWindow(parent, flags)
 3: {
 4:     QWidget *cw = new QWidget();
 5:     setCentralWidget(cw);                       //  cw をメインウィンドウのセントラルウィジットに設定
 6:     QLabel *label_1 = new QLabel(tr("label-1"), cw);    //  ラベルオブジェクト生成
 7:     label_1->setGeometry(10, 10, 80, 20);               //  位置 (x=10, y=10, wd=80, ht=20)を指定
 8:     QLabel *label_2 = new QLabel(tr("label-2"), cw);
 9:     label_2->setGeometry(10, 40, 80, 20);
10:     QPushButton *pb1 = new QPushButton(tr("PushButton-1"), cw);
11:     pb1->setGeometry(80, 10, 100, 20);
12: }

上記コードを実行すると、下図の様に表示される。

自由度は大きいが、以下のような問題がある。

● 演習問題: label-1 と label-2 の間に、label-1.5 を挿入すには具体的にどうしたいいか想像してみてください。

● 演習問題: ウィジットが隠れることが無いよう、ウィンドウリサイズを制限するにはどうしらいいか?

QLayout

簡単かつ動的にウィジットをレイアウトする仕組み=レイアウトクラスがいくつか用意されている。

QWidget::setLayout(QLayout *) により、ウィジットにレイアウトオブジェクトを(1個のみ)設定できる。
QLayout は addWidget(QWidget*) または addLayout(QLayout*) により、子ウィジットまたは子レイアウトを追加でき、 下図の様に階層化することができる。

QHBoxLayout, QVBoxLayout

水平または垂直にウィジットを配置するレイアウト

 1: void MainWindow::testVBoxLayout()
 2: {
 3:     QDialog aDlg;
 4:     QVBoxLayout *layout = new QVBoxLayout();
 5:     layout->addWidget(new QLabel(tr("label-1")));       //  ラベル
 6:     layout->addWidget(new QLineEdit());                 //  行エディット
 7:     layout->addWidget(new QLabel(tr("label-2")));       //  ラベル
 8:     //layout->addStretch();
 9:     //layout->addWidget(new QPlainTextEdit());
10:     layout->addWidget(new QPushButton(tr("PushButton-2")));
11:     aDlg.setLayout(layout);
12:     aDlg.exec();
13: }

setLayout() されたウィジットは、レイアウトの中身でデフォルトサイズが決まる。

ラベル、行エディット、プッシュボタンなど縦に伸びないものだけの場合、ウィンドウを縦に拡大すると均等にレイアウトされる:

QPlainTextEdit の様に縦方向に伸びるものがあれば、それだけが拡大縮小される:

 1: void MainWindow::testVBoxLayout()
 2: {
 3:     QDialog aDlg;
 4:     QVBoxLayout *layout = new QVBoxLayout();
        .....
 9:     layout->addWidget(new QPlainTextEdit());
         .....
13: }

下図は先のソースコードの 9行目のコメントアウト行から // を削除したもの。

addStretch() により、拡大する領域(画面には表示されない)を挿入することができる (前後に addStretch() することで中央揃えにすることもできる):

 1: void MainWindow::testVBoxLayout()
 2: {
 3:     QDialog aDlg;
 4:     QVBoxLayout *layout = new QVBoxLayout();
        .....
 8:     layout->addStretch();
        .....
13: }

下図は先のソースコードの 8行目のコメントアウト行から // を削除したもの。

● 演習問題: addStretch() を使い、label-1 〜 label-2 を上下中央位置に表示するようにしなさい。

QBoxLayout::addLayout() により、レイアウトを階層化することができる。 QVBoxLayout の下層に QHBoxLayout を配置するなどして、見栄えが良く使いやすい画面を作ることができる。

 1: void MainWindow::testHierarchiBL()
 2: {
 3:     QDialog aDlg;
 4:     QVBoxLayout *vLayout = new QVBoxLayout();
 5:     QHBoxLayout *hLayout = new QHBoxLayout();
 6:         hLayout->addWidget(new QLabel(tr("hoge:")));
 7:         hLayout->addWidget(new QLineEdit());
 8:         vLayout->addLayout(hLayout);
 9:     QHBoxLayout *hLayout = new QHBoxLayout();
10:         hLayout->addWidget(new QLabel(tr("hogeHoge:")));
11:         hLayout->addWidget(new QLineEdit());
12:         vLayout->addLayout(hLayout);
13:     vLayout->addStretch();
14:     QHBoxLayout *hLayout = new QHBoxLayout();
15:         hLayout->addStretch();
16:         hLayout->addWidget(new QPushButton(tr("PushButton")));
17:         hLayout->addStretch();
18:         vLayout->addLayout(hLayout);
19:     aDlg.setLayout(vLayout);
20:     aDlg.exec();
21: }

● 演習問題: 下図の様なダイアログを作成するには、BOXレイアウトをどのように配置したらいいか考えなさい。

QGridLayout

QGridLayout を使うことで、ウィジットを表計算ソフトの画面の様に格子状配置することができる。

QGridLayout にウィジットを配置する場合は QGridLayout::addWidget(QWidget*, nt row, int column, Qt::Alignment alignment = 0) メソッドを使用する。 第2、第3引数でウィジットを配置する行番号(0..*)、カラム番号(0..*)を指定できる。 第4引数でアライメントを指定することも出来る。

 1: void MainWindow::testGridLayout()
 2: {
 3:     QDialog aDlg;
 4:     QVBoxLayout *vLayout = new QVBoxLayout();
 5:     QGridLayout *gLayout = new QGridLayout();
 6:         gLayout->addWidget(new QLabel(tr("hoge:")), 0, 0, Qt::AlignRight);      //  (0, 0) 位置
 7:         gLayout->addWidget(new QLineEdit(), 0, 1);                              //  (0, 1) 位置
 8:         gLayout->addWidget(new QLabel(tr("hogehoge:")), 1, 0, Qt::AlignRight);  //  (1, 0) 位置
 9:         gLayout->addWidget(new QLineEdit(), 1, 1);                              //  (1, 1) 位置
10:         vLayout->addLayout(gLayout);
11:     vLayout->addStretch();
12:     .....
13: }

QGridLayout::addWidget(QWidget * widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = 0 ) の第4、第5引数で行スパン、カラムスパンを指定することもできる。

● 演習問題: 行スパン・カラムスパンを使用したサンプルを作ってみなさい。

QFormLayout

QFormLayout は、(ラベル:入力エリア)*数行 というよくあるパターンを簡単に記述するためのものである。
QFormLayout::addRow( QWidget * label, QWidget * field ) により、フォームの1行を追加することができる。
第2引数は QWidget * なので、QLineEdit だけでなく、QCheckBox や QGroupBox を使って QRadioButton を配置することもできる。

以下のソースは前節のものと同じUIを実現するものであるが、記述・理解がはるかに容易になっている。

 1: void MainWindow::testFormLayout()
 2: {
 3:     QDialog aDlg;
 4:     QVBoxLayout *vLayout = new QVBoxLayout();
 5:     QFormLayout *fLayout = new QFormLayout();
 6:         fLayout->setLabelAlignment(Qt::AlignRight);
 7:         fLayout->addRow(tr("hoge:"), new QLineEdit());          //  1行目
 8:         fLayout->addRow(tr("hogehoge:"), new QLineEdit());      //  2行目
 9:         vLayout->addLayout(fLayout);
10:     vLayout->addStretch();
11:     .....
12: }

● 演習問題: 入力部分にチェックボックスを配置してみなさい

● 演習問題: 入力部分にラジオボタンを配置してみなさい

QStackedLayout

QStackedLayout はページ切り替え可能なレイアウトである。
切り替えるUIはコンボボックス・ラジオボタンなど任意のものを使用可能。
タブで切り替えたい場合は QTabWidget が用意されているので、それを使う方が楽。

QStackedLayout::addWidget( QWidget * widget ) で各ページのウィジットを追加する。
表示されているページは QWidget *currentWidget () const で取得可能。
表示ページの切り替えは setCurrentIndex ( int index ) または setCurrentWidget ( QWidget * widget ) で行うことができる。

void MainWindow::testStackedLayout()
{
    QDialog aDlg;
    QVBoxLayout *vLayout = new QVBoxLayout();   //  垂直レイアウト
    QComboBox *pageComboBox = new QComboBox;    //  ページ切り替え用コンボボックス
        pageComboBox->addItem(tr("Page 1"));
        pageComboBox->addItem(tr("Page 2"));
        pageComboBox->addItem(tr("Page 3"));
        vLayout->addWidget(pageComboBox);
    QStackedLayout *stackedLayout = new QStackedLayout();
        stackedLayout->addWidget(new QCalendarWidget);      //  1ページ目ウィジット設定
        stackedLayout->addWidget(new QDial);                //  2ページ目ウィジット設定
        stackedLayout->addWidget(new QPlainTextEdit);       //  3ページ目ウィジット設定
        vLayout->addLayout(stackedLayout);
        connect(pageComboBox, SIGNAL(activated(int)),
                stackedLayout, SLOT(setCurrentIndex(int)));     //  ページ切り替えのために signal-slot 接続
    .....
}

● 演習問題:ラジオボタンでページを切り替えるようにしなさい。

まとめ

Qt のレイアウトクラスは非常に柔軟・強力で簡単に使用することができる。 難点は、複雑なレイアウトになるとソースコードが見づらいということくらいかもしれない。
(※
演算子オーバーロードを使いソースコードを見やすくする方法 を参考に読んでね)

レイアウトクラスを駆使し見栄えがよくて使いやすいUIを設計してね。

● 演習問題:下図の〇〇、△△、×× に適切な語句を入れて笑いを取りなさい。

※ 次回は「イベントのお話」の予定