レイアウト管理

Qt レイアウトシステムは、ウィジェット内の子ウィジェットを自動的に配置し、利用可能なスペースを有効に活用するためのシンプルで強力な方法を提供します。

はじめに

Qt には、アプリケーションのユーザーインターフェイスでウィジェットをどのようにレイアウトするかを記述するためのレイアウト管理クラスがあります。これらのレイアウトは、利用可能なスペースが変更されると自動的にウィジェットの位置やサイズを変更し、一貫した配置とユーザインタフェース全体の使いやすさを維持します。

すべてのQWidget サブクラスは、子クラスを管理するためにレイアウトを使用できます。QWidget::setLayout() 関数は、ウィジェットにレイアウトを適用します。この方法でウィジェットにレイアウトが設定されると、以下のタスクを担当します:

  • 子ウィジェットの配置
  • ウィンドウの適切なデフォルトサイズ
  • ウィンドウの適切な最小サイズ
  • リサイズの処理
  • コンテンツ変更時の自動更新
    • 子ウィジェットのフォントサイズ、テキスト、その他のコンテンツ
    • 子ウィジェットの非表示/表示
    • 子ウィジェットの削除

Qtのレイアウトクラス

Qt のレイアウト・クラスは、手書きの C++ コード用に設計されたもので、寸法をピクセル単位で指定できるため、理解しやすく使いやすくなっています。Qt Widgets Designerを使用して作成されたフォームのコードもレイアウトクラスを使用しています。Qt Widgets Designerは、ユーザーインターフェースの開発で通常発生するコンパイル、リンク、実行のサイクルを回避できるため、フォームのデザインを試す際に使用すると便利です。

QBoxLayout

子ウィジェットを水平または垂直に並べる

QButtonGroup

ボタンウィジェットのグループを整理するコンテナ

QFormLayout

入力ウィジェットのフォームと関連するラベルを管理する

QGraphicsAnchor

QGraphicsAnchorLayout内の2つのアイテム間のアンカーを表します。

QGraphicsAnchorLayout

グラフィックス・ビューでウィジェット同士をアンカーするためのレイアウト

QGridLayout

ウィジェットをグリッドにレイアウトする

QGroupBox

タイトル付きグループボックスフレーム

QHBoxLayout

ウィジェットを水平に並べる

QLayout

ジオメトリ・マネージャの基本クラス

QLayoutItem

QLayoutが操作する抽象項目

QSizePolicy

水平方向と垂直方向のサイズ変更ポリシーを記述するレイアウト属性

QSpacerItem

レイアウト内の空白

QStackedLayout

一度に1つのウィジェットしか表示されないウィジェットのスタック

QStackedWidget

一度に 1 つのウィジェットだけが表示されるウィジェットのスタック

QVBoxLayout

ウィジェットを垂直に並べる

QWidgetItem

ウィジェットを表すレイアウト項目

水平、垂直、グリッド、フォームレイアウト

ウィジェットに良いレイアウトを与える最も簡単な方法は、組み込みのレイアウトマネージャを使用することです:QHBoxLayout QVBoxLayoutQGridLayoutQFormLayout 。これらのクラスは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つのウィジェットが追加されます。一般的には、 と を追加してフォームを作成します。同じ行に と を追加すると、 が のバディとして設定されます。次のコードは、 を使って、3つの と、それに対応する を行に配置します。QLabel QLineEdit QLabel QLineEdit QLineEdit QLabel QFormLayout QPushButtons QLineEdit

    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() 、レイアウトを入れ子にすることができます。その場合、内側のレイアウトは、挿入されたレイアウトの子になります。

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

レイアウトにウィジェットを追加する場合、レイアウト処理は次のように動作します:

  1. すべてのウィジェットは、最初にQWidget::sizePolicy() とQWidget::sizeHint() に応じたスペースが割り当てられます。
  2. ウィジェットにストレッチ・ファクタが設定されており、その値がゼロより大きい場合、ストレッチ・ファクタに比例してスペースが割り当てられます(以下で説明します)。
  3. ストレッチファクターがゼロに設定されているウィジェットは、他のウィジェットがスペースを必要としない場合のみ、より多くのスペースを獲得できます。このうち、Expanding サイズポリシーを持つウィジェットに最初に領域が割り当てられます。
  4. 最小サイズ (または最小サイズが指定されていない場合は最小サイズのヒント) よりも小さいスペースが割り当てられたウィジェットには、必要な最小サイズが割り当てられます。(ウィジェットは最小サイズまたは最小サイズヒントを持つ必要はありません。その場合、ストレッチファクターが決定要因となります)。
  5. 最大サイズよりも大きなスペースが割り当てられたウィジェットには、必要な最大サイズのスペースが割り当てられます。(ウィジェットは最大サイズを持つ必要はありません。その場合、ストレッチファクターが決定要因となります)。

ストレッチファクター

ウィジェットは通常、ストレッチ・ファクターを設定せずに作成されます。レイアウトに配置されると、ウィジェットには、QWidget::sizePolicy() または最小サイズのヒントのうち、大きい方に応じたスペースが与えられます。ストレッチ・ファクターは、ウィジェットが互いに対してどれだけのスペースを与えるかを変更するために使用されます。

QHBoxLayout 、ストレッチファクターを設定せずに3つのウィジェットをレイアウトすると、次のようなレイアウトになります:

Three widgets in a row

各ウィジェットにストレッチファクターを適用すると、比例してレイアウトされます(ただし、最小サイズのヒントより小さくなることはありません)。

Three widgets with different stretch factors in a row

カスタムウィジェットのレイアウト

独自のウィジェットクラスを作成する場合、そのレイアウトプロパティも伝える必要があります。ウィジェットがQtのレイアウトのいずれかを使用する場合、これはすでに処理されています。ウィジェットが子ウィジェットを持たない場合、または手動レイアウトを使用する場合、以下のメカニズムのいずれか、またはすべてを使用してウィジェットの動作を変更できます:

  • QWidget::sizeHint()を再実装して、ウィジェットの優先サイズを返します。
  • QWidget::minimumSizeHint()を再実装して、ウィジェットが持つことのできる最小サイズを返す。
  • QWidget::setSizePolicy()を呼び出して、ウィジェットのスペース要件を指定する。

サイズヒント、最小サイズヒント、またはサイズポリシーが変更されるたびに、QWidget::updateGeometry() を呼び出します。これにより、レイアウトの再計算が行われます。QWidget::updateGeometry() を連続して複数回コールしても、レイアウトの再計算は 1 回のみです。

ウィジェットの好ましい高さがその実際の幅に依存する場合(例えば、自動単語分割機能を持つラベル)、ウィジェットのsize policyheight-for-width フラグを設定し、QWidget::heightForWidth() を再実装してください。

QWidget::heightForWidth()を実装する場合でも、妥当なsizeHint()を提供することをお勧めします。

これらの関数を実装する際の詳しいガイダンスについては、Qt Quarterly の記事「Trading Height for Width」を参照してください。

レイアウトの問題

ラベルウィジェットでリッチテキストを使用すると、親ウィジェットのレイアウトに問題が発生することがあります。問題は、ラベルがワードラップされたときに Qt のレイアウトマネージャがリッチテキストを処理する方法によって発生します。

特定のケースでは、親レイアウトはQLayout::FreeResizeモードになり、小さなサイズのウィンドウに収まるようにコンテンツのレイアウトを適応しません。これは、問題のあるウィジェットをサブクラス化し、適切なsizeHint() とminimumSizeHint() 関数を実装することで克服できます。

場合によっては、レイアウトがウィジェットに追加されるときに関連します。QDockWidget 、またはQScrollAreaQDockWidget::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);
}

追加されたアイテムの責任はレイアウトが引き継ぐ。QLayoutItemQObject を継承していないので、手動で項目を削除しなければならない。デストラクタでは、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ウィジェットの例では既にレイアウトを使用していますが、様々なレイアウトを紹介する例がいくつかあります。

Calculator Example

この例では、電卓ウィジェットの機能を実装するためにシグナルとスロットを使用する方法と、グリッドに子ウィジェットを配置するためにQGridLayoutを使用する方法を示します。

Calendar Widget Example

カレンダーウィジェットの例では、QCalendarWidgetの使い方を示しています。

Flow Layout Example

異なるウィンドウ・サイズ用にウィジェットを配置する方法を示します。

Image Composition Example

QPainterでのコンポジションモードの動作を示します。

Menus Example

Menusの例では、メインウィンドウアプリケーションでのメニューの使用方法を示します。

Simple Tree Model Example

Simple Tree Model の例では、Qt の標準ビュークラスで階層モデルを使用する方法を示します。

©2024 The Qt Company Ltd. ここに含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。