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.
Reiht untergeordnete Widgets horizontal oder vertikal auf | |
Container zur Organisation von Gruppen von Schaltflächen-Widgets | |
Verwaltet Formulare von Eingabe-Widgets und ihre zugehörigen Beschriftungen | |
Repräsentiert einen Anker zwischen zwei Elementen in einem QGraphicsAnchorLayout | |
Layout, in dem Widgets in der grafischen Ansicht miteinander verankert werden können | |
Legt Widgets in einem Raster aus | |
Gruppenrahmen mit einem Titel | |
reiht Widgets horizontal aneinander | |
Die Basisklasse der Geometriemanager | |
Abstraktes Element, das ein QLayout manipuliert | |
Layout-Attribut, das die horizontale und vertikale Größenanpassung beschreibt | |
Leerer Raum in einem Layout | |
Stapel von Widgets, bei denen jeweils nur ein Widget sichtbar ist | |
Stapel von Widgets, bei denen jeweils nur ein Widget sichtbar ist | |
Vertikale Aneinanderreihung von Widgets | |
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:
- Allen Widgets wird zunächst ein Platz entsprechend ihrer QWidget::sizePolicy() und QWidget::sizeHint() zugewiesen.
- 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).
- 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.
- 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).
- 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:
Wenn wir die Streckfaktoren auf jedes Widget anwenden, werden sie im Verhältnis zueinander angeordnet (aber niemals kleiner als ihr Mindestgrößenhinweis), z. B.
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:
- Reimplementieren Sie QWidget::sizeHint(), um die bevorzugte Größe des Widgets zurückzugeben.
- Reimplementieren Sie QWidget::minimumSizeHint(), um die kleinste Größe zurückzugeben, die das Widget haben kann.
- Rufen Sie QWidget::setSizePolicy() auf, um den Platzbedarf des Widgets anzugeben.
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.
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. | |
Das Kalender-Widget-Beispiel zeigt die Verwendung von QCalendarWidget. | |
Zeigt, wie man Widgets für verschiedene Fenstergrößen anordnet. | |
Zeigt, wie Kompositionsmodi in QPainter funktionieren. | |
Das Beispiel Menus demonstriert, wie Menüs in einer Hauptfensteranwendung verwendet werden können. | |
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.