レイアウト管理
Qtのレイアウトシステムは、ウィジェット内の子ウィジェットを自動的に配置し、利用可能なスペースを効率的に活用できるようにする、シンプルかつ強力な方法を提供します。
はじめに
Qt には、アプリケーションのユーザーインターフェースにおけるウィジェットの配置方法を記述するために使用される一連のレイアウト管理クラスが含まれています。これらのレイアウトは、ウィジェットに割り当てられるスペースの量が変化した際に、ウィジェットの位置やサイズを自動的に調整し、ウィジェットが一貫して配置され、ユーザーインターフェース全体が使いやすさを維持できるようにします。
すべてのQWidget のサブクラスは、レイアウトを使用して子ウィジェットを管理できます。QWidget::setLayout()関数は、ウィジェットにレイアウトを適用します。このようにウィジェットにレイアウトが設定されると、レイアウトは以下のタスクを担当します:
- 子ウィジェットの配置
- ウィンドウの適切なデフォルトサイズ
- ウィンドウの適切な最小サイズ
- サイズ変更の処理
- コンテンツが変更された際の自動更新:
- 子ウィジェットのフォントサイズ、テキスト、その他のコンテンツ
- 子ウィジェットの表示・非表示
- 子ウィジェットの削除
Qtのレイアウトクラス
Qtのレイアウトクラスは、手書きのC++コード向けに設計されており、簡潔さを重視してピクセル単位での寸法指定が可能であるため、理解しやすく、使いやすくなっています。Qt Widgets Designer を使用して作成されたフォームに対して生成されるコードも、これらのレイアウトクラスを使用しています。Qt Widgets Designer は、フォームのデザインを試行する際に便利です。これは、ユーザーインターフェース開発で通常必要となるコンパイル、リンク、実行のサイクルを省略できるためです。
子ウィジェットを水平または垂直に並べる | |
ボタンウィジェットのグループを整理するためのコンテナ | |
入力ウィジェットとその関連ラベルのフォームを管理します | |
QGraphicsAnchorLayout 内の 2 つの項目間のアンカーを表します | |
Graphics View内でウィジェットを互いにアンカーできるレイアウト | |
ウィジェットをグリッド状に配置する | |
タイトル付きのグループボックスフレーム | |
ウィジェットを水平方向に並べる | |
ジオメトリマネージャの基底クラス | |
QLayoutが操作する抽象アイテム | |
水平および垂直方向のサイズ変更ポリシーを記述するレイアウト属性 | |
レイアウト内の空白領域 | |
一度に1つのウィジェットのみが表示されるウィジェットのスタック | |
一度に1つのウィジェットのみが表示されるウィジェットのスタック | |
ウィジェットを垂直方向に並べる | |
ウィジェットを表すレイアウト項目 |
水平、垂直、グリッド、およびフォームレイアウト
ウィジェットを適切にレイアウトする最も簡単な方法は、組み込みのレイアウトマネージャー(QHBoxLayout 、QVBoxLayout 、QGridLayout 、QFormLayout )を使用することです。これらのクラスはQLayout を継承しており、 はQObject (QWidget ではありません)から派生しています。これらは、一連のウィジェットのジオメトリ管理を処理します。より複雑なレイアウトを作成するには、レイアウトマネージャーを互いにネストさせることができます。
- QHBoxLayout は、ウィジェットを左から右へ(右から左の言語の場合は右から左へ)水平な行に配置します。

- QVBoxLayout は、ウィジェットを縦の列に、上から下へと配置します。

- QGridLayout は、ウィジェットを2次元グリッド上に配置します。ウィジェットは複数のセルを占有することができます。

- QFormLayout は、ウィジェットを「ラベル-フィールド」形式の2列レイアウトで配置します。

コードによるウィジェットの配置
以下のコードは、上の最初のスクリーンショットに示されているように、5つのQPushButtons の配置を管理するQHBoxLayout を作成します。
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(window);
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
layout->addWidget(button5);
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(window);
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->show();3つ目のQPushButton は2列にまたがっています。これは、QGridLayout::addWidget() の5番目の引数に 2 を指定することで実現できます。
QFormLayout 1行に2つのウィジェット(通常はQLabel とQLineEdit )を追加して、フォームを作成します。同じ行にQLabel とQLineEdit を追加すると、QLineEdit がQLabel のバディとして設定されます。以下のコードでは、QFormLayout を使用して、3つのQPushButtons と、それに対応するQLineEdit を1行に配置します。
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QLineEdit *lineEdit1 = new QLineEdit();
QPushButton *button2 = new QPushButton("Two");
QLineEdit *lineEdit2 = new QLineEdit();
QPushButton *button3 = new QPushButton("Three");
QLineEdit *lineEdit3 = new QLineEdit();
QFormLayout *layout = new QFormLayout(window);
layout->addRow(button1, lineEdit1);
layout->addRow(button2, lineEdit2);
layout->addRow(button3, lineEdit3);
window->show();レイアウトの使用に関するヒント
レイアウトを使用する場合、子ウィジェットを生成する際に親を指定する必要はありません。レイアウトは、QWidget::setParent() を使用して、ウィジェットを自動的に再親付けし、レイアウトがインストールされているウィジェットの子となるようにします。
注: レイアウト内のウィジェットは 、レイアウト自体の子ではなく、そのレイアウトが配置されているウィジェットの子となります。ウィジェットは、レイアウトではなく、他のウィジェットのみを親とすることができます。
レイアウトに対してaddLayout() を呼び出すことで、レイアウトをネストすることができます。これにより、内側のレイアウトは、挿入されたレイアウトの子となります。
レイアウトへのウィジェットの追加
レイアウトにウィジェットを追加する場合、その処理は次のように行われます。
- すべてのウィジェットには、最初にQWidget::sizePolicy()およびQWidget::sizeHint()に基づいてスペースが割り当てられます。
- ウィジェットのいずれかにストレッチ係数が設定されており、その値がゼロより大きい場合、そのウィジェットにはストレッチ係数に比例してスペースが割り当てられます(詳細は後述)。
- ウィジェットのストレッチ係数がゼロに設定されている場合、他のウィジェットがそのスペースを必要としていない場合にのみ、より多くのスペースが割り当てられます。この場合、Expanding サイズポリシーを持つウィジェットに優先的にスペースが割り当てられます。
- 最小サイズ(最小サイズが指定されていない場合は最小サイズヒント)よりも少ないスペースが割り当てられたウィジェットには、必要なこの最小サイズが割り当てられます。(ウィジェットに最小サイズや最小サイズヒントが設定されていない場合、ストレッチ係数が決定要因となります。)
- 最大サイズよりも多くのスペースが割り当てられたウィジェットには、そのウィジェットが必要とする最大サイズのスペースが割り当てられます。(ウィジェットに最大サイズが設定されていない場合、ストレッチ係数が決定要因となります。)
ストレッチ係数
ウィジェットは通常、ストレッチ係数が設定されていない状態で作成されます。レイアウト内に配置される際、ウィジェットには、QWidget::sizePolicy() の値、または最小サイズヒントのいずれか大きい方の値に基づいて、スペースが割り当てられます。ストレッチ係数は、ウィジェット同士の相対的なスペース配分を変更するために使用されます。
ストレッチ係数を設定せずにQHBoxLayout を使用して3つのウィジェットをレイアウトすると、次のようなレイアウトになります:

各ウィジェットにストレッチ係数を適用すると、それらは比例して配置されます(ただし、最小サイズヒントを下回ることはありません)。例えば、

レイアウト内のカスタムウィジェット
独自のウィジェットクラスを作成する際は、そのレイアウトプロパティも指定する必要があります。ウィジェットがQtのレイアウトのいずれかを使用している場合は、これはすでに処理されています。ウィジェットに子ウィジェットがない場合や、手動レイアウトを使用している場合は、以下のメカニズムのいずれか、またはすべてを使用して、ウィジェットの挙動を変更することができます:
- QWidget::sizeHint() を再実装して、ウィジェットの推奨サイズを返す。
- QWidget::minimumSizeHint() を再実装し、ウィジェットが持つことのできる最小サイズを返す。
- QWidget::setSizePolicy() を呼び出し、ウィジェットに必要なスペースを指定します。
サイズヒント、最小サイズヒント、またはサイズポリシーが変更されるたびに、QWidget::updateGeometry() を呼び出してください。これにより、レイアウトの再計算が行われます。QWidget::updateGeometry() を連続して複数回呼び出しても、レイアウトの再計算は 1 回のみ行われます。
ウィジェットの推奨高さが実際の幅に依存する場合(例:自動改行機能を持つラベルなど)、ウィジェットの `size policy ` 内で `height-for-width ` フラグを設定し、`QWidget::heightForWidth()` を再実装してください。
QWidget::heightForWidth() を実装する場合でも、適切な sizeHint() を指定しておくことをお勧めします。
これらの関数を実装する際の詳しい指針については、Qt Quarterlyの記事「Trading Height for Width」を参照してください。
レイアウトに関する問題
ラベルウィジェットでリッチテキストを使用すると、その親ウィジェットのレイアウトに問題が生じる場合があります。この問題は、ラベルで文字の折り返しが行われる際、Qtのレイアウトマネージャーによるリッチテキストの処理方法に起因して発生します。
場合によっては、親レイアウトが QLayout::FreeResize モードに設定されることがあります。これは、コンテンツのレイアウトを小さなウィンドウ内に収めるように調整しないことを意味し、場合によっては、ユーザーがウィンドウを操作不能なほど小さくすることを防ぐことさえあります。この問題は、問題のあるウィジェットをサブクラス化し、適切なsizeHint() およびminimumSizeHint() 関数を実装することで解決できます。
場合によっては、ウィジェットにレイアウトが追加されるタイミングが重要になります。QDockWidget やQScrollArea のウィジェットを設定する際(QDockWidget::setWidget()およびQScrollArea::setWidget()を使用)、そのウィジェットにはすでにレイアウトが設定されている必要があります。そうでない場合、ウィジェットは表示されません。
手動レイアウト
独自の特別なレイアウトを作成する場合は、前述のようにカスタムウィジェットを作成することもできます。QWidget::resizeEvent() を再実装して必要なサイズ配分を計算し、各子要素に対してsetGeometry() を呼び出してください。
レイアウトの再計算が必要になった際、ウィジェットにはQEvent::LayoutRequest 型のイベントが送信されます。QEvent::LayoutRequest イベントを処理するには、QWidget::event()を再実装してください。
カスタムレイアウトマネージャーの作成方法
手動によるレイアウトの代わりに、QLayout をサブクラス化して独自のレイアウトマネージャを作成することもできます。Flow Layoutの例では、その方法について説明しています。
ここでは、その例を詳しく紹介します。CardLayout クラスは、同名のJavaレイアウトマネージャーを参考にしています。このクラスは、各アイテム(ウィジェットやネストされたレイアウト)を互いに重なるように配置し、各アイテムはQLayout::spacing()によってオフセットされます。
独自のレイアウトクラスを作成するには、以下を定義する必要があります:
- レイアウトが扱うアイテムを格納するためのデータ構造。各アイテムは `QLayoutItem` です。この例では `QList ` を使用します。
- addItem():レイアウトにアイテムを追加する方法を定義します。
- setGeometry(), レイアウトを実行する方法。
- sizeHint(), レイアウトの推奨サイズ。
- itemAt(), レイアウトを反復処理する方法。
- takeAt(), レイアウトからアイテムを削除する方法。
ほとんどの場合、minimumSize() も実装することになります。
ヘッダーファイル (card.h)
#ifndef CARD_H
#define CARD_H
#include <QtWidgets>
#include <QList>
class CardLayout : public QLayout
{
public:
CardLayout(int spacing): QLayout()
{ setSpacing(spacing); }
CardLayout(int spacing, QWidget *parent): QLayout(parent)
{ setSpacing(spacing); }
~CardLayout();
void addItem(QLayoutItem *item) override;
QSize sizeHint() const override;
QSize minimumSize() const override;
int count() const override;
QLayoutItem *itemAt(int) const override;
QLayoutItem *takeAt(int) override;
void setGeometry(const QRect &rect) override;
private:
QList<QLayoutItem *> m_items;
};
#endif実装ファイル(card.cpp )
//#include "card.h"まず、リスト内の要素数を取得するためにcount() を定義します。
int CardLayout::count() const
{
// QList::size() returns the number of QLayoutItems in m_items
return m_items.size();
}次に、レイアウトを反復処理する2つの関数、itemAt() とtakeAt() を定義します。これらの関数は、ウィジェットの削除を処理するためにレイアウトシステム内部で使用されます。また、アプリケーション開発者も利用可能です。
itemAt() 指定されたインデックスの項目を返します。takeAt() は、指定されたインデックスの項目を削除し、それを返します。この例では、リストのインデックスをレイアウトのインデックスとして使用しています。より複雑なデータ構造を使用する場合、項目の線形順序を定義するために、さらに手間がかかる可能性があります。
QLayoutItem *CardLayout::itemAt(int idx) const
{
// QList::value() performs index checking, and returns nullptr if we are
// outside the valid range
return m_items.value(idx);
}
QLayoutItem *CardLayout::takeAt(int idx)
{
// QList::take does not do index checking
return idx >= 0 && idx < m_items.size() ? m_items.takeAt(idx) : 0;
}addItem() レイアウト項目のデフォルトの配置戦略を実装します。この関数は必ず実装する必要があります。この関数は、QLayout::add() や、レイアウトを親として受け取るQLayout のコンストラクタによって使用されます。レイアウトにパラメータを必要とする高度な配置オプションがある場合は、QGridLayout::addItem()、QGridLayout::addWidget()、QGridLayout::addLayout() の行および列をまたぐオーバーロードなど、追加のアクセス関数を提供する必要があります。
void CardLayout::addItem(QLayoutItem *item)
{
m_items.append(item);
}レイアウトは、追加されたアイテムの処理を引き継ぎます。QLayoutItem はQObject を継承していないため、アイテムは手動で削除する必要があります。デストラクタでは、takeAt() を使用してリストから各アイテムを取り出し、その後削除します。
CardLayout::~CardLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}setGeometry() 関数は、実際にレイアウトを実行します。引数として渡される矩形には、margin() は含まれません。必要に応じて、アイテム間の距離としてspacing() を使用してください。
void CardLayout::setGeometry(const QRect &r)
{
QLayout::setGeometry(r);
if (m_items.size() == 0)
return;
int w = r.width() - (m_items.count() - 1) * spacing();
int h = r.height() - (m_items.count() - 1) * spacing();
int i = 0;
while (i < m_items.size()) {
QLayoutItem *o = m_items.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 = m_items.count();
if (n > 0)
s = QSize(100, 70); //start with a nice default size
int i = 0;
while (i < n) {
QLayoutItem *o = m_items.at(i);
s = s.expandedTo(o->sizeHint());
++i;
}
return s + n * QSize(spacing(), spacing());
}
QSize CardLayout::minimumSize() const
{
QSize s(0, 0);
int n = m_items.count();
int i = 0;
while (i < n) {
QLayoutItem *o = m_items.at(i);
s = s.expandedTo(o->minimumSize());
++i;
}
return s + n * QSize(spacing(), spacing());
}補足
- このカスタムレイアウトは、幅に対する高さを処理しません。
- QLayoutItem::isEmpty() は無視されます。つまり、レイアウトでは非表示のウィジェットも表示されているものとして扱われます。
- 複雑なレイアウトの場合、計算された値をキャッシュすることで速度を大幅に向上させることができます。その場合は、QLayoutItem::invalidate() を実装して、キャッシュされたデータが変更されたことをマークしてください。
- QLayoutItem::sizeHint() などの呼び出しは、処理負荷が高くなる場合があります。そのため、同じ関数内で後で再びその値が必要になる場合は、その値をローカル変数に格納しておくべきです。
- 同じ関数内で、同じアイテムに対してQLayoutItem::setGeometry()を2回呼び出してはいけません。アイテムに複数の子ウィジェットがある場合、この呼び出しは非常にコストが高くなる可能性があります。なぜなら、レイアウトマネージャーが毎回完全なレイアウトを実行しなければならないからです。 その代わりに、ジオメトリを計算してから設定してください。(これはレイアウトだけに当てはまるわけではありません。たとえば、独自の resizeEvent() を実装する場合なども、同様に処理すべきです。)
レイアウトの例
多くのQt Widgets の例ではすでにレイアウトが使用されていますが、さまざまなレイアウトを紹介するための例もいくつか存在します。
この例では、シグナルとスロットを使用して電卓ウィジェットの機能を実装する方法、および QGridLayout を使用して子ウィジェットをグリッド上に配置する方法を示しています。 | |
「カレンダーウィジェット」の例では、QCalendarWidget の使用法を示しています。 | |
さまざまなウィンドウサイズに合わせてウィジェットを配置する方法を示しています。 | |
QPainter におけるコンポジションモードの仕組みを説明しています。 | |
「Menus」の例では、メインウィンドウアプリケーションでメニューをどのように使用できるかを示しています。 | |
「Simple Tree Model」の例では、Qt の標準ビュークラスで階層モデルを使用する方法を示しています。 |
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.