ホーム · All Namespaces · 全てのクラス · メインのクラス · グループ別 · モジュール一覧 · 関数一覧

レイアウト管理

The Qt layout system provides a simple and powerful way of automatically arranging child widgets within a widget to ensure that they make good use of the available space.

Introduction

Qt includes a set of layout management classes that are used to describe how widgets are laid out in an application's user interface. These layouts automatically position and resize widgets when the available space for them changes, ensuring that they are consistently arranged and that the user interface as a whole remains usable.

All QWidget subclasses can use layouts to manage their children. The QWidget::setLayout() function applies a layout to a widget. When a layout is set on a widget in this way, it takes charge of the following tasks:

Qt's layout classes were designed for hand-written C++ code, allowing measurements to be specified in pixels for simplicity, so they're easy to understand and use. The code generated for forms created using Qt Designer を使って生成したコードもまたこれらのレイアウトクラスを使用します。 Qt Designer is also useful to use when experimenting with the design of a form since it avoids the compile, link and run cycle usually involved in user interface development.

水平、垂直、格子レイアウト

The easiest way to give your widgets a good layout is to use the built-in layout managers: QHBoxLayout QVBoxLayout QGridLayoutといったクラスがあります。これらのクラスは QLayoutを継承しており、 QObject QWidgetではない)から派生しています。これらはウィジェットの座標・サイズを注意深く管理します。より複雑なレイアウトを管理したい場合は、レイアウトマネージャ同士を内側に組み込むことで対応できます。

以下のコードでは QHBoxLayout を作成し、その中で5つの QPushButtonを、ちょうど最初のスクリーンショットにあるような形に並べて管理します。

     QWidget *window = new QWidget;
     QPushButton *button1 = new QPushButton("One");
     QPushButton *button2 = new QPushButton("Two");
     QPushButton *button3 = new QPushButton("Three");
     QPushButton *button4 = new QPushButton("Four");
     QPushButton *button5 = new QPushButton("Five");
     QHBoxLayout *layout = new QHBoxLayout;
     layout->addWidget(button1);
     layout->addWidget(button2);
     layout->addWidget(button3);
     layout->addWidget(button4);
     layout->addWidget(button5);
     window->setLayout(layout);
     window->show();

そして QVBoxLayout を使ったコードは、レイアウトを生成している行を除いてまったく同じになります。 QGridLayout を使ったコードは、子ウィジェットの位置を行と列とで指定しなければならないので、少し異なります。

     QWidget *window = new QWidget;
     QPushButton *button1 = new QPushButton("One");
     QPushButton *button2 = new QPushButton("Two");
     QPushButton *button3 = new QPushButton("Three");
     QPushButton *button4 = new QPushButton("Four");
     QPushButton *button5 = new QPushButton("Five");
     QGridLayout *layout = new QGridLayout;
     layout->addWidget(button1, 0, 0);
     layout->addWidget(button2, 0, 1);
     layout->addWidget(button3, 1, 0, 1, 2);
     layout->addWidget(button4, 2, 0);
     layout->addWidget(button5, 2, 1);
     window->setLayout(layout);
     window->show();

ここで3番目の QPushButton spans 2 columns. This is possible by specifying 2 as the fifth argument to QGridLayout::addWidget() が用いられます。

レイアウトを使う場合、子ウィジェットの構築時に親ウィジェットを渡す必要はありません。レイアウトは子ウィジェットがレイアウトに追加されたときに自動的にウィジェットの親を( QWidget::setParent()を使って)付け替えます。

重要: レイアウト内のウィジェットはレイアウトがインストールされているウィジェットの子ウィジェットとなります。レイアウト自身の子ウィジェット ではありません 。ウィジェットはレイアウトではなくただひとつのウィジェットのみを親ウィジェットとしてもちます。

レイアウトは他のレイアウトの上に addLayout() を使って重ねることができます。内部レイアウトは追加されたレイアウトの子要素となります。 Basic Layouts の例では、この機能を使って複雑なダイアログを作成します。

レイアウトへのウィジェットの追加

レイアウトにウィジェットを追加すると、レイアウトは次のように処理を行います。

  1. すべてのウィジェットはまず最初にあるサイズの領域に割り当てられます。これには QWidget::sizePolicy() が用いられます。
  2. すべてのウィジェットが 0 を超える stretch factor を持っている場合、その stretch factor に比例した領域が割り当てられます(以下で説明します)。
  3. ウィジェットの stretch factor が 0 の場合は、他のウィジェットが領域を必要としない場合のみ領域が広げられます。この場合、まず Expanding サイズポリシーに基づいて領域が割り当てられます。
  4. 最小サイズより小さい領域しか割り当てられていないウィジェットには(最小サイズが特定できない場合は最小サイズヒントを使用)、要求されている最小サイズを割り当てます。(ウィジェットは最小サイズと最小サイズヒントを必ず持つ必要はありません。この場合は stretch factor によってこれらを決定します)。
  5. 最大サイズより大きな領域を割り当てられているウィジェットには、要求されている最大サイズを割り当てます。(ウィジェットは最大サイズを持つ必要はありません。この場合は stretch factor を用いてこれらを決定します)。

Stretch Factors

ウィジェットは通常 stretch factor set を指定せずに生成されます。レイアウトにおかれたときに、ウィジェットには共有の領域が割り当てられ、そのサイズは QWidget::sizePolicy() または最小サイズヒントのどちらか大きいほうとなります。stretch factor はそれぞれに比例してどれだけの領域がウィジェットに割り当てられるかを決めるのに使われます。

ここで3つのウィジェットが QHBoxLayout の中におかれ、それぞれの stretch factor set が指定されていない場合、以下のようなレイアウトとなります。

もし伸長係数を各ウィジットに設定した場合、それに比例してレイアウトされるでしょう( minimum size hint 以下にはなりません)。例えば

Custom Widgets in Layouts

ユーザー定義のウィジェットクラスを作成した場合も、レイアウトプロパティとやり取りをするべきです。ウィジェットが QLayout, this is already taken care of. If the widget does not have any child widgets, or uses manual layout, you can change the behavior of the widget using any or all of the following mechanisms:

サイズヒント、、最小サイズヒント、またはサイズポリシーが変更となった場合は QWidget::updateGeometry() を呼び出してください。これはレイアウトの再計算の引き金となります。複数回連続した QWidget::updateGeometry() の呼び出しはまとめられ、一回の再計算となります。

If the preferred height of your widget depends on its actual width (e.g., a label with automatic word-breaking), set the height-for-width フラグが size policy にあるのでそれをセットし、 QWidget::heightForWidth() を再実装してください。

そして QWidget::heightForWidth() を実装した場合でも、適切な sizeHint() を提供することは良い考えです。

これらの関数を実装する際のガイダンスとしては、 Trading Height for Width Qt Quarterly内にあります。

Layout Issues

ラベルウィジェットの中でリッチテキストを使用することは親ウィジェットのレイアウトで問題となることがあります。問題がおきるかどうかは、ラベルで改行するときにQtレイアウトマネージャがどうリッチテキストを扱うか、によります。

この場合、親レイアウトを QLayout::FreeResize という、内部のウィンドウの最小サイズに合わせないでレイアウトするモードにするか、ユーザーに使用できないほどウィンドウを小さくされるのを防ぐ、といった対応を行います。これは、問題のおきたクラスの派生クラスを作成し、sizeHint() やminimumSizeHint()といった関数を適切に実装することで行います。

In some cases, it is relevant when a layout is added to a widget. When you set the widget of a QDockWidget or a QScrollArea (with QDockWidget::setWidget() and QScrollArea::setWidget()), the layout must already have been set on the widget. If not, the widget will not be visible.

Manual Layout

ある特殊な形式のレイアウトを作成したい場合は、上で説明した方法でカスタムウィジェットを作成するように作成することができます。まず QWidget::resizeEvent() を再実装して子ウィジェットごとに必要なサイズを計算し、 setGeometry() を呼び出します。

ウィジェットはレイアウトの再計算が必要になった時 QEvent::LayoutRequest タイプのイベントを受信します。 QWidget::event() を再実装し、 QEvent::LayoutRequest イベントを処理します。

Writing Custom Layout Managers

Manual layout の代わりとして、独自のレイアウトマネージャを QLayoutのサブクラスとして実装するという手段があります。 Border Layout Flow Layout という例を使って、どのようにするのかここで説明します。

ここで詳細な例をあげます。CardLayoutは同名のJavaのレイアウトマネージャに想起されたものです。これはアイテム(ウィジェットやネストしたレイアウト)をそれぞれの上に、 QLayout::spacing() の実装が必要です。

独自のレイアウトクラスを実装するには、以下を定義する必要があります。

多くの場合、さらに minimumSize() の実装が必要です。

The Header File (card.h)

 #ifndef CARD_H
 #define CARD_H
 #include <QLayout>
 #include <QList>
 class CardLayout : public QLayout
 {
 public:
     CardLayout(QWidget *parent, int dist)
         : QLayout(parent, 0, dist) {}
     CardLayout(QLayout *parent, int dist)
         : QLayout(parent, dist) {}
     CardLayout(int dist)
         : QLayout(dist) {}
     ~CardLayout();
     void addItem(QLayoutItem *item);
     QSize sizeHint() const;
     QSize minimumSize() const;
     QLayoutItem *itemAt(int) const;
     QLayoutItem *takeAt(int);
     void setGeometry(const QRect &rect);
 private:
     QList<QLayoutItem*> list;
 };
 #endif

The Implementation File (card.cpp)

 #include "card.h"

まず最初に、レイアウトを横断してアクセスする関数 itemAt() と takeAt() を実装します。これらの関数は内部ではレイアウトシステムがウィジェットを削除するときに使用されます。またアプリケーションプログラマも使用可能です。

ItemAt() は与えられたインデックスのアイテムを返します。takeAt() は与えられたインデックスのアイテムを削除し、そのアイテムを返します。この例ではリストのインデックスをレイアウトのインデックスとして使います。より複雑なデータ構造を使用した場合、これらの処理をアイテムの数に比例したオーダーより効果的に行うことができるかもしれません。

 QLayoutItem *CardLayout::itemAt(int idx) const
 {
     // QList::value() performs index checking, and returns 0 if we are
     // outside the valid range
     return list.value(idx);
 }
 QLayoutItem *CardLayout::takeAt(int idx)
 {
     // QList::take does not do index checking
     return idx >= 0 && idx < list.size() ? list.takeAt(idx) : 0;
 }

addItem() にはデフォルトのアイテムレイアウトの仕方を実装します。必ず実装しなければいけません。これは QLayout::add() の時、つまり QLayout コンストラクタがレイアウトを親としてとった時に使われます。レイアウトが追加の引数によって場所を決定する場合は、追加のアクセス関数を提供します。ちょうど行と列が QGridLayout::addItem(), addWidget(), and addLayout() でオーバーロードされているように、です。

 void CardLayout::addItem(QLayoutItem *item)
 {
     list.append(item);
 }

レイアウトは追加されたアイテムについて責任を持ちます。 QLayoutItem QObjectを継承していないため、必ず手動で破棄しなければなりません。関数 QLayout::deleteAllItems() は上で定義している takeAt() を使って、レイアウトの中のすべてのアイテムを破棄します。

 CardLayout::~CardLayout()
 {
     deleteAllItems();
 }

そして setGeometry() 関数で、実際のレイアウトを決定します。引数として margin() を含まない長方形の領域が渡されます。そのサイズが妥当なら、spacing() をアイテム間の距離として使います。

 void CardLayout::setGeometry(const QRect &r)
 {
     QLayout::setGeometry(r);
     if (list.size() == 0)
         return;
     int w = r.width() - (list.count() - 1) * spacing();
     int h = r.height() - (list.count() - 1) * spacing();
     int i = 0;
     while (i < list.size()) {
         QLayoutItem *o = list.at(i);
         QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
         o->setGeometry(geom);
         ++i;
     }
 }

sizeHint() と minimumSize() はとてもよく似た実装となります。どちらの関数も spacing() を含み margin() を使用しないサイズを返します。

 QSize CardLayout::sizeHint() const
 {
     QSize s(0,0);
     int n = list.count();
     if (n > 0)
         s = QSize(100,70); //start with a nice default size
     int i = 0;
     while (i < n) {
         QLayoutItem *o = list.at(i);
         s = s.expandedTo(o->sizeHint());
         ++i;
     }
     return s + n*QSize(spacing(), spacing());
 }
 QSize CardLayout::minimumSize() const
 {
     QSize s(0,0);
     int n = list.count();
     int i = 0;
     while (i < n) {
         QLayoutItem *o = list.at(i);
         s = s.expandedTo(o->minimumSize());
         ++i;
     }
     return s + n*QSize(spacing(), spacing());
 }

補足説明

ここでは width に対応した height は扱いませんでした。

ここでは QLayoutItem::isEmpty() を無視しました。そのため隠されたウィジェットが見えているかのようにレイアウトします。

複雑なレイアウトの場合、値をキャッシュすることで大幅な速度の向上が望めます。こうした場合、 QLayoutItem::invalidate() をキャッシュが無効となった場合に呼び出すように実装する必要があります。

さらに QLayoutItem::sizeHint() の呼び出しには時間がかかることがあるため、同じ関数の中で後で使う必要がある場合はローカル変数に保持するべきです。

同じ関数の中で、2度同じアイテムの QLayoutItem::setGeometry() twice on the same item in the same function. That can be very expensive if the item has several child widgets, because it must do a complete layout every time. Instead, calculate the geometry and then set it. (This doesn't only apply to layouts, you should do the same if you implement your own resizeEvent().)


Copyright © 2008 Nokia Trademarks
Qt 4.4.3