Layout-Verwaltung

Das Qt-Layout-System bietet eine einfache und leistungsfähige Möglichkeit, untergeordnete Widgets innerhalb eines Widgets automatisch anzuordnen, um sicherzustellen, dass sie den verfügbaren Platz gut nutzen.

Einführung

Qt enthält eine Reihe von Layout-Verwaltungsklassen, mit denen beschrieben wird, wie Widgets in der Benutzeroberfläche einer Anwendung angeordnet werden. Diese Layouts positionieren Widgets automatisch und passen ihre Größe an, wenn sich der für sie verfügbare Platz ändert, um sicherzustellen, dass sie konsistent angeordnet sind und die Benutzeroberfläche als Ganzes benutzbar bleibt.

Alle QWidget Unterklassen können Layouts verwenden, um ihre Kinder zu verwalten. Die Funktion QWidget::setLayout() wendet ein Layout auf ein Widget an. Wenn ein Layout auf diese Weise auf ein Widget gesetzt wird, übernimmt es die folgenden Aufgaben:

  • Positionierung der untergeordneten Widgets
  • Sinnvolle Standardgrößen für Fenster
  • Sinnvolle Mindestgrößen für Fenster
  • Behandlung von Größenänderungen
  • Automatische Updates, wenn sich Inhalte ändern:
    • Schriftgröße, Text oder andere Inhalte von untergeordneten Widgets
    • Verstecken oder Anzeigen eines untergeordneten Widgets
    • Entfernen von untergeordneten Widgets

Qt's Layout-Klassen

Die Layout-Klassen von Qt wurden für handgeschriebenen C++-Code entwickelt und ermöglichen die Angabe von Maßen in Pixeln, so dass sie einfach zu verstehen und zu verwenden sind. Der Code, der für mit Qt Widgets Designer erstellte Formulare generiert wird, verwendet ebenfalls die Layout-Klassen. Qt Widgets Der Designer ist nützlich, um mit dem Design eines Formulars zu experimentieren, da er den Kompilier-, Verknüpfungs- und Ausführungszyklus vermeidet, der normalerweise bei der Entwicklung von Benutzeroberflächen erforderlich ist.

QBoxLayout

Reiht untergeordnete Widgets horizontal oder vertikal auf

QButtonGroup

Container zur Organisation von Gruppen von Schaltflächen-Widgets

QFormLayout

Verwaltet Formulare von Eingabe-Widgets und ihre zugehörigen Beschriftungen

QGraphicsAnchor

Repräsentiert einen Anker zwischen zwei Elementen in einem QGraphicsAnchorLayout

QGraphicsAnchorLayout

Layout, in dem Widgets in der grafischen Ansicht miteinander verankert werden können

QGridLayout

Legt Widgets in einem Raster aus

QGroupBox

Gruppenrahmen mit einem Titel

QHBoxLayout

reiht Widgets horizontal aneinander

QLayout

Die Basisklasse der Geometriemanager

QLayoutItem

Abstraktes Element, das ein QLayout manipuliert

QSizePolicy

Layout-Attribut, das die horizontale und vertikale Größenanpassung beschreibt

QSpacerItem

Leerer Raum in einem Layout

QStackedLayout

Stapel von Widgets, bei denen jeweils nur ein Widget sichtbar ist

QStackedWidget

Stapel von Widgets, bei denen jeweils nur ein Widget sichtbar ist

QVBoxLayout

Vertikale Aneinanderreihung von Widgets

QWidgetItem

Layoutelement, das ein Widget darstellt

Horizontale, vertikale, Raster- und Formular-Layouts

Der einfachste Weg, Ihren Widgets ein gutes Layout zu geben, ist die Verwendung der eingebauten Layout-Manager: QHBoxLayout, QVBoxLayout, QGridLayout, und QFormLayout. Diese Klassen erben von QLayout, die wiederum von QObject (nicht QWidget) abgeleitet ist. Sie kümmern sich um die Geometrieverwaltung für eine Reihe von Widgets. Um komplexere Layouts zu erstellen, können Sie Layout-Manager ineinander verschachteln.

  • Ein QHBoxLayout ordnet Widgets in einer horizontalen Reihe von links nach rechts an (oder von rechts nach links für Sprachen mit Rechts-nach-Links-Ausrichtung).

  • Ein QVBoxLayout ordnet Widgets in einer vertikalen Spalte an, von oben nach unten.

  • Bei QGridLayout werden die Widgets in einem zweidimensionalen Raster angeordnet. Widgets können mehrere Zellen belegen.

  • Ein QFormLayout zeigt Widgets in einem zweispaltigen, beschreibenden Label-Feld-Stil an.

Anordnen von Widgets im Code

Der folgende Code erstellt ein QHBoxLayout, das die Geometrie von fünf QPushButtons verwaltet, wie auf dem ersten Screenshot oben gezeigt:

    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();

Der Code für QVBoxLayout ist bis auf die Zeile, in der das Layout erstellt wird, identisch. Der Code für QGridLayout ist ein wenig anders, weil wir die Zeilen- und Spaltenposition des untergeordneten Widgets angeben müssen:

    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();

Das dritte QPushButton erstreckt sich über 2 Spalten. Dies ist möglich, indem man 2 als fünftes Argument für QGridLayout::addWidget() angibt.

QFormLayout fügt zwei Widgets in einer Zeile hinzu, in der Regel ein QLabel und ein QLineEdit, um Formulare zu erstellen. Wenn Sie ein QLabel und ein QLineEdit in derselben Zeile hinzufügen, wird das QLineEdit als Partner des QLabel festgelegt. Der folgende Code verwendet QFormLayout, um drei QPushButtons und ein entsprechendes QLineEdit in einer Zeile zu platzieren.

    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();

Tipps zur Verwendung von Layouts

Wenn Sie ein Layout verwenden, müssen Sie beim Erstellen der untergeordneten Widgets kein übergeordnetes Element übergeben. Das Layout ordnet die Widgets (mit QWidget::setParent()) automatisch so an, dass sie Kinder des Widgets sind, auf dem das Layout installiert ist.

Hinweis: Widgets in einem Layout sind Kinder des Widgets, auf dem das Layout installiert ist, nicht des Layouts selbst. Widgets können nur andere Widgets als Eltern haben, nicht Layouts.

Sie können Layouts mit addLayout() in einem Layout verschachteln; das innere Layout wird dann zu einem Kind des Layouts, in das es eingefügt wird.

Hinzufügen von Widgets zu einem Layout

Wenn Sie Widgets zu einem Layout hinzufügen, funktioniert der Layoutprozess wie folgt:

  1. Allen Widgets wird zunächst ein Platz entsprechend ihrer QWidget::sizePolicy() und QWidget::sizeHint() zugewiesen.
  2. Wenn für eines der Widgets Dehnungsfaktoren mit einem Wert größer als Null eingestellt sind, wird ihnen proportional zu ihrem Dehnungsfaktor Platz zugewiesen (siehe unten).
  3. Wenn für eines der Widgets der Dehnungsfaktor auf Null gesetzt ist, erhalten sie nur dann mehr Platz, wenn keine anderen Widgets den Platz benötigen. Von diesen Widgets wird der Platz zuerst den Widgets mit einer Expanding Größenpolitik zugewiesen.
  4. Allen Widgets, denen weniger Platz zugewiesen wird als ihre Mindestgröße (oder der Hinweis auf die Mindestgröße, wenn keine Mindestgröße angegeben ist), wird die von ihnen benötigte Mindestgröße zugewiesen. (Widgets müssen keine Mindestgröße oder Mindestgrößenangabe haben, in diesem Fall ist der Streckungsfaktor der entscheidende Faktor).
  5. Allen Widgets, denen mehr Platz als ihre maximale Größe zugewiesen wird, wird die maximale Größe zugewiesen, die sie benötigen. (Widgets müssen keine Maximalgröße haben, in diesem Fall ist der Streckungsfaktor der bestimmende Faktor).

Streckungs-Faktoren

Widgets werden normalerweise ohne Streckungsfaktor erstellt. Wenn sie in einem Layout angeordnet werden, erhalten die Widgets einen Anteil des Platzes entsprechend ihrem QWidget::sizePolicy() oder ihrem Hinweis auf die Mindestgröße, je nachdem, welcher Wert größer ist. Dehnungsfaktoren werden verwendet, um zu ändern, wie viel Platz den Widgets im Verhältnis zueinander zugewiesen wird.

Wenn drei Widgets unter Verwendung von QHBoxLayout ohne Streckfaktoren angeordnet werden, ergibt sich ein Layout wie dieses:

Three widgets in a row

Wenn wir die Streckfaktoren auf jedes Widget anwenden, werden sie im Verhältnis zueinander angeordnet (aber niemals kleiner als ihr Mindestgrößenhinweis), z. B.

Three widgets with different stretch factors in a row

Benutzerdefinierte Widgets in Layouts

Wenn Sie Ihre eigene Widget-Klasse erstellen, sollten Sie auch deren Layout-Eigenschaften mitteilen. Wenn das Widget eines der Layouts von Qt verwendet, ist dies bereits erledigt. Wenn das Widget keine untergeordneten Widgets hat oder ein manuelles Layout verwendet, können Sie das Verhalten des Widgets mit einem oder allen der folgenden Mechanismen ändern:

Rufen Sie QWidget::updateGeometry() auf, wenn sich der Größenhinweis, der Hinweis auf die Mindestgröße oder die Größenrichtlinie ändert. Dies wird eine Neuberechnung des Layouts verursachen. Mehrere aufeinanderfolgende Aufrufe von QWidget::updateGeometry() führen nur zu einer Neuberechnung des Layouts.

Wenn die bevorzugte Höhe Ihres Widgets von seiner tatsächlichen Breite abhängt (z.B. ein Etikett mit automatischem Wortumbruch), setzen Sie das height-for-width Flag in der size policy des Widgets und implementieren Sie QWidget::heightForWidth() neu.

Selbst wenn Sie QWidget::heightForWidth() implementieren, ist es immer noch eine gute Idee, einen vernünftigen sizeHint() bereitzustellen.

Weitere Hinweise zur Implementierung dieser Funktionen finden Sie im Qt Quarterly-Artikel Höhe gegen Breite tauschen.

Layout-Probleme

Die Verwendung von Rich-Text in einem Label-Widget kann einige Probleme mit dem Layout des übergeordneten Widgets verursachen. Die Probleme entstehen durch die Art und Weise, wie Rich-Text von Qt's Layout-Managern behandelt wird, wenn das Label mit einem Zeilenumbruch versehen ist.

In bestimmten Fällen wird das übergeordnete Layout in den QLayout::FreeResize-Modus versetzt, was bedeutet, dass es das Layout seines Inhalts nicht so anpasst, dass es in kleine Fenster passt, oder sogar verhindert, dass der Benutzer das Fenster zu klein macht, um es zu benutzen. Dies kann durch die Unterklassifizierung der problematischen Widgets und die Implementierung geeigneter sizeHint() und minimumSizeHint() Funktionen umgangen werden.

In einigen Fällen ist es relevant, wenn ein Layout zu einem Widget hinzugefügt wird. Wenn Sie das Widget eines QDockWidget oder eines QScrollArea (mit QDockWidget::setWidget() und QScrollArea::setWidget()) einstellen, muss das Layout bereits für das Widget eingestellt worden sein. Ist dies nicht der Fall, wird das Widget nicht sichtbar sein.

Manuelles Layout

Wenn Sie ein einzigartiges spezielles Layout erstellen möchten, können Sie auch ein benutzerdefiniertes Widget wie oben beschrieben erstellen. Reimplementieren Sie QWidget::resizeEvent(), um die erforderliche Größenverteilung zu berechnen, und rufen Sie setGeometry() für jedes Kind auf.

Das Widget wird ein Ereignis des Typs QEvent::LayoutRequest erhalten, wenn das Layout neu berechnet werden muss. Reimplementieren Sie QWidget::event(), um QEvent::LayoutRequest Ereignisse zu behandeln.

Wie man einen benutzerdefinierten Layout-Manager schreibt

Eine Alternative zum manuellen Layout besteht darin, einen eigenen Layout-Manager zu schreiben, indem Sie QLayout unterklassifizieren. Das Flow-Layout-Beispiel zeigt, wie man das macht.

Hier stellen wir ein Beispiel im Detail vor. Die Klasse CardLayout ist von dem gleichnamigen Java-Layoutmanager inspiriert. Sie ordnet die Elemente (Widgets oder verschachtelte Layouts) übereinander an, wobei jedes Element um QLayout::spacing() versetzt ist.

Um Ihre eigene Layout-Klasse zu schreiben, müssen Sie Folgendes definieren:

  • Eine Datenstruktur zum Speichern der vom Layout behandelten Elemente. Jedes Element ist ein QLayoutItem. In diesem Beispiel wird ein QList verwendet.
  • addItem(), wie man Elemente zum Layout hinzufügt.
  • setGeometry(), wie das Layout ausgeführt werden soll.
  • sizeHint(), die bevorzugte Größe des Layouts.
  • itemAt(), wie man über das Layout iteriert.
  • takeAt(), wie man Elemente aus dem Layout entfernt.

In den meisten Fällen werden Sie auch minimumSize() implementieren.

Die Header-Datei (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

Die Implementierungsdatei (card.cpp)

//#include "card.h"

Zuerst definieren wir count(), um die Anzahl der Elemente in der Liste zu ermitteln.

int CardLayout::count() const
{
    // QList::size() returns the number of QLayoutItems in m_items
    return m_items.size();
}

Dann definieren wir zwei Funktionen, die über das Layout iterieren: itemAt() und takeAt(). Diese Funktionen werden intern vom Layoutsystem verwendet, um die Löschung von Widgets zu behandeln. Sie sind auch für Anwendungsprogrammierer verfügbar.

itemAt() gibt das Element mit dem angegebenen Index zurück. takeAt() entfernt das Element mit dem angegebenen Index und gibt es zurück. In diesem Fall wird der Listenindex als Layout-Index verwendet. In anderen Fällen, in denen wir eine komplexere Datenstruktur haben, müssen wir möglicherweise mehr Aufwand betreiben, um eine lineare Reihenfolge für die Elemente zu definieren.

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() implementiert die Standardplatzierungsstrategie für Layoutelemente. Diese Funktion muss implementiert werden. Sie wird von QLayout::add() verwendet, vom QLayout Konstruktor, der ein Layout als Elternteil nimmt. Wenn Ihr Layout erweiterte Platzierungsoptionen hat, die Parameter erfordern, müssen Sie zusätzliche Zugriffsfunktionen wie die zeilen- und spaltenübergreifenden Überladungen von QGridLayout::addItem(), QGridLayout::addWidget() und QGridLayout::addLayout() bereitstellen.

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

Das Layout übernimmt die Verantwortung für die hinzugefügten Elemente. Da QLayoutItem nicht von QObject erbt, müssen wir die Elemente manuell löschen. Im Destruktor entfernen wir jedes Element mit takeAt() aus der Liste und löschen es dann.

CardLayout::~CardLayout()
{
     QLayoutItem *item;
     while ((item = takeAt(0)))
         delete item;
}

Die Funktion setGeometry() führt das eigentliche Layout durch. Das als Argument übergebene Rechteck enthält nicht margin(). Falls relevant, verwenden Sie spacing() als Abstand zwischen den Elementen.

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() und minimumSize() sind in der Regel sehr ähnlich implementiert. Die von beiden Funktionen zurückgegebenen Größen sollten spacing(), aber nicht margin() enthalten.

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());
}

Weitere Hinweise

  • Dieses benutzerdefinierte Layout behandelt die Höhe nicht als Breite.
  • Wir ignorieren QLayoutItem::isEmpty(); das bedeutet, dass das Layout versteckte Widgets als sichtbar behandelt.
  • Bei komplexen Layouts kann die Geschwindigkeit durch das Zwischenspeichern berechneter Werte erheblich gesteigert werden. In diesem Fall implementieren Sie QLayoutItem::invalidate(), um die zwischengespeicherten Daten als "dirty" zu kennzeichnen.
  • Der Aufruf von QLayoutItem::sizeHint() usw. kann teuer sein. Daher sollten Sie den Wert in einer lokalen Variablen speichern, wenn Sie ihn später in derselben Funktion wieder benötigen.
  • Sie sollten QLayoutItem::setGeometry() nicht zweimal mit demselben Element in derselben Funktion aufrufen. Dieser Aufruf kann sehr teuer werden, wenn das Element mehrere untergeordnete Widgets hat, da der Layout-Manager jedes Mal ein komplettes Layout durchführen muss. Berechnen Sie stattdessen die Geometrie und setzen Sie sie dann. (Dies gilt nicht nur für Layouts, Sie sollten dasselbe tun, wenn Sie z.B. Ihr eigenes resizeEvent() implementieren).

Layout-Beispiele

Viele Beispiele von Qt Widgets verwenden bereits Layouts, aber es gibt auch einige Beispiele, die verschiedene Layouts zeigen.

Calculator Example

Das Beispiel zeigt, wie man Signale und Slots verwendet, um die Funktionalität eines Taschenrechner-Widgets zu implementieren, und wie man QGridLayout verwendet, um untergeordnete Widgets in einem Raster zu platzieren.

Calendar Widget Example

Das Kalender-Widget-Beispiel zeigt die Verwendung von QCalendarWidget.

Flow Layout Example

Zeigt, wie man Widgets für verschiedene Fenstergrößen anordnet.

Image Composition Example

Zeigt, wie Kompositionsmodi in QPainter funktionieren.

Menus Example

Das Beispiel Menus demonstriert, wie Menüs in einer Hauptfensteranwendung verwendet werden können.

Simple Tree Model Example

Das Simple Tree Model Beispiel zeigt, wie man ein hierarchisches Modell mit den Standard-View-Klassen von Qt verwendet.

© 2025 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.