Verwenden einer Designer UI-Datei in Ihrer C++-Anwendung
Qt Widgets Designer UI-Dateien stellen den Widget-Baum des Formulars im XML-Format dar. Die Formulare können verarbeitet werden:
- Zur Kompilierzeit, d.h. die Formulare werden in C++-Code umgewandelt, der kompiliert werden kann.
- Zur Laufzeit, d. h. die Formulare werden von der Klasse QUiLoader verarbeitet, die den Widget-Baum dynamisch aufbaut, während sie die XML-Datei parst.
Verarbeitung von Formularen zur Kompilierzeit
Sie erstellen Komponenten der Benutzeroberfläche mit Qt Widgets Designer und verwenden die in Qt integrierten Build-Tools qmake und uic, um Code für sie zu generieren, wenn die Anwendung gebaut wird. Der generierte Code enthält das Benutzeroberflächenobjekt des Formulars. Es ist eine C++ Struktur, die folgendes enthält:
- Zeiger auf die Widgets, Layouts, Layoutelemente, Schaltflächengruppen und Aktionen des Formulars.
- Eine Mitgliedsfunktion namens
setupUi()
, um den Widget-Baum auf dem übergeordneten Widget aufzubauen. - Eine Mitgliedsfunktion namens
retranslateUi()
, die die Übersetzung der String-Eigenschaften des Formulars übernimmt. Weitere Informationen finden Sie unter Reagieren auf Sprachänderungen.
Der generierte Code kann in Ihre Anwendung eingebunden und direkt von dort aus verwendet werden. Alternativ können Sie ihn auch verwenden, um Unterklassen von Standard-Widgets zu erweitern.
Ein zur Kompilierzeit verarbeitetes Formular kann mit einem der folgenden Ansätze in Ihrer Anwendung verwendet werden:
- Direkter Ansatz: Sie konstruieren ein Widget, das als Platzhalter für die Komponente dient, und richten die Benutzeroberfläche darin ein.
- Der Einfachvererbungsansatz: Sie unterklassifizieren die Basisklasse des Formulars (z. B.QWidget oder QDialog) und fügen eine private Instanz des Benutzerschnittstellenobjekts des Formulars ein.
- Der Ansatz der Mehrfachvererbung: Sie unterklassifizieren sowohl die Basisklasse des Formulars als auch das Objekt der Benutzeroberfläche des Formulars. Dadurch können die im Formular definierten Widgets direkt aus dem Bereich der Unterklasse verwendet werden.
Zur Veranschaulichung erstellen wir eine einfache Calculator-Form-Anwendung. Sie basiert auf dem ursprünglichen Calculator Form Beispiel.
Die Anwendung besteht aus einer Quelldatei, main.cpp
, und einer UI-Datei.
Die mit Qt Widgets Designer erstellte Datei calculatorform.ui
ist unten abgebildet:
Wenn Sie CMake
verwenden, um die ausführbare Datei zu erstellen, ist eine CMakeLists.txt
Datei erforderlich:
cmake_minimum_required(VERSION 3.16) project(calculatorform LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) qt_add_executable(calculatorform calculatorform.ui main.cpp) set_target_properties(calculatorform PROPERTIES WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE ) target_link_libraries(calculatorform PUBLIC Qt::Core Qt::Gui Qt::Widgets )
Das Formular ist unter den C++-Quelldateien in qt_add_executable()
aufgeführt. Die Option CMAKE_AUTOUIC
weist CMake
an, das Tool uic
auszuführen, um eine Datei ui_calculatorform.h
zu erstellen, die von den Quelldateien verwendet werden kann.
Wenn Sie qmake
verwenden, um die ausführbare Datei zu erstellen, ist eine .pro
Datei erforderlich:
TEMPLATE = app FORMS = calculatorform.ui SOURCES = main.cpp
Die Besonderheit dieser Datei ist die FORMS
-Deklaration, die qmake
mitteilt, welche Dateien mit uic
verarbeitet werden sollen. In diesem Fall wird die calculatorform.ui
-Datei verwendet, um eine ui_calculatorform.h
-Datei zu erstellen, die von jeder in der SOURCES
-Deklaration aufgeführten Datei verwendet werden kann.
Hinweis: Sie können Qt Creator verwenden, um das Projekt Calculator Form zu erstellen. Es generiert automatisch die main.cpp, UI und eine Projektdatei für das gewünschte Build-Tool, die Sie ändern können.
Der direkte Ansatz
Für den direkten Ansatz binden wir die Datei ui_calculatorform.h
direkt in main.cpp
ein:
#include "ui_calculatorform.h"
Die Funktion main
erstellt das Taschenrechner-Widget, indem sie einen Standard QWidget erstellt, den wir verwenden, um die in der Datei calculatorform.ui
beschriebene Benutzeroberfläche zu hosten.
int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget widget; Ui::CalculatorForm ui; ui.setupUi(&widget); widget.show(); return app.exec(); }
In diesem Fall ist Ui::CalculatorForm
ein Schnittstellenbeschreibungsobjekt aus der Datei ui_calculatorform.h
, das alle Widgets des Dialogs und die Verbindungen zwischen seinen Signalen und Slots einrichtet.
Der direkte Ansatz bietet eine schnelle und einfache Möglichkeit, einfache, in sich geschlossene Komponenten in Ihren Anwendungen zu verwenden. Die mit Qt Widgets Designer erstellten Komponenten erfordern jedoch oft eine enge Integration mit dem restlichen Anwendungscode. Zum Beispiel wird der oben angegebene CalculatorForm
Code kompiliert und ausgeführt, aber die QSpinBox Objekte interagieren nicht mit QLabel, da wir einen benutzerdefinierten Slot benötigen, um die Add-Operation auszuführen und das Ergebnis in QLabel anzuzeigen. Um dies zu erreichen, müssen wir den Ansatz der Einfachvererbung verwenden.
Der Ansatz der Einfachvererbung
Um den Ansatz der Einfachvererbung zu verwenden, subclassifizieren wir ein Standard-Qt-Widget und fügen eine private Instanz des Benutzeroberflächenobjekts des Formulars ein. Dies kann in der Form von:
- Eine Mitgliedsvariable
- Eine Zeiger-Member-Variable
Verwendung einer Member-Variable
Bei diesem Ansatz wird ein Qt-Widget subklassifiziert und die Benutzeroberfläche im Konstruktor eingerichtet. Komponenten, die auf diese Weise verwendet werden, stellen der Qt Widget-Unterklasse die im Formular verwendeten Widgets und Layouts zur Verfügung und bieten ein Standardsystem zur Herstellung von Signal- und Slot-Verbindungen zwischen der Benutzeroberfläche und anderen Objekten in Ihrer Anwendung. Die generierte Ui::CalculatorForm
Struktur ist ein Mitglied der Klasse.
Dieser Ansatz wird im Beispiel des Taschenrechnerformulars verwendet.
Um sicherzustellen, dass wir die Benutzeroberfläche verwenden können, müssen wir die Header-Datei einbinden, die uic
generiert, bevor wir auf Ui::CalculatorForm
verweisen:
#include "ui_calculatorform.h"
Die Projektdatei muss aktualisiert werden, um calculatorform.h
einzuschließen. Für CMake
:
qt_add_executable(calculatorform calculatorform.cpp calculatorform.h calculatorform.ui main.cpp )
In bestimmten Fällen, wie z.B. im folgenden Beispiel, wo die Include-Direktive einen relativen Pfad verwendet, kann qt_add_ui verwendet werden, um die Datei ui_calculatorform.h
zu generieren, anstatt sich auf AUTOUIC zu verlassen.
Wann man qt_add_ui gegenüber AUTOUIC vorzieht
#include "src/files/ui_calculatorform.h"
qt_add_ui(calculatorform SOURCES calculatorform.ui INCLUDE_PREFIX src/files)
Für qmake
:
HEADERS = calculatorform.h
Die Unterklasse wird auf folgende Weise definiert:
class CalculatorForm : public QWidget { Q_OBJECT public: explicit CalculatorForm(QWidget *parent = nullptr); private slots: void updateResult(); private: Ui::CalculatorForm ui; };
Das wichtigste Merkmal der Klasse ist das private ui
Objekt, das den Code zum Einrichten und Verwalten der Benutzeroberfläche bereitstellt.
Der Konstruktor der Unterklasse konstruiert und konfiguriert alle Widgets und Layouts für den Dialog, indem er einfach die Funktion setupUi()
des Objekts ui
aufruft. Sobald dies geschehen ist, kann die Benutzeroberfläche nach Bedarf geändert werden.
CalculatorForm::CalculatorForm(QWidget *parent) : QWidget(parent) { ui.setupUi(this); connect(ui.inputSpinBox1, &QSpinBox::valueChanged, this, &CalculatorForm::updateResult); connect(ui.inputSpinBox2, &QSpinBox::valueChanged, this, &CalculatorForm::updateResult); }
Wir können Signale und Slots in den Widgets der Benutzeroberfläche auf die übliche Weise verbinden, indem wir das Präfix on_<Objektname> - hinzufügen. Für weitere Informationen, siehe widgets-and-dialogs-with-auto-connect.
Die Vorteile dieses Ansatzes sind die einfache Verwendung von Vererbung, um eine QWidget-basierte Schnittstelle bereitzustellen, und die Kapselung der Variablen des Benutzeroberflächen-Widgets innerhalb des ui
-Datenelements. Wir können diese Methode verwenden, um eine Reihe von Benutzeroberflächen innerhalb desselben Widgets zu definieren, von denen jede in ihrem eigenen Namensraum enthalten ist, und sie überlagern (oder zusammensetzen). Auf diese Weise lassen sich z. B. einzelne Registerkarten aus vorhandenen Formularen erstellen.
Verwendung einer Zeiger-Member-Variable
Alternativ kann die Struktur Ui::CalculatorForm
als Zeigermitglied der Klasse angelegt werden. Der Header sieht dann wie folgt aus:
namespace Ui { class CalculatorForm; } class CalculatorForm : public QWidget ... virtual ~CalculatorForm(); ... private: Ui::CalculatorForm *ui; ...
Die entsprechende Quelldatei sieht wie folgt aus:
#include "ui_calculatorform.h" CalculatorForm::CalculatorForm(QWidget *parent) : QWidget(parent), ui(new Ui::CalculatorForm) { ui->setupUi(this); } CalculatorForm::~CalculatorForm() { delete ui; }
Der Vorteil dieses Ansatzes ist, dass das Objekt der Benutzeroberfläche vordeklariert werden kann, was bedeutet, dass wir die generierte Datei ui_calculatorform.h
nicht in den Header aufnehmen müssen. Das Formular kann dann geändert werden, ohne die abhängigen Quelldateien neu zu kompilieren. Dies ist besonders wichtig, wenn die Klasse Binärkompatibilitätsbeschränkungen unterliegt.
Wir empfehlen diesen Ansatz generell für Bibliotheken und große Anwendungen. Weitere Informationen finden Sie unter Erstellen gemeinsamer Bibliotheken.
Der Ansatz der Mehrfachvererbung
Mit Qt Widgets Designer erstellte Formulare können zusammen mit einer auf QWidget basierenden Standardklasse unterklassifiziert werden. Dieser Ansatz macht alle im Formular definierten Komponenten der Benutzeroberfläche direkt im Rahmen der Unterklasse zugänglich und ermöglicht es, Signal- und Slotverbindungen auf die übliche Weise mit der Funktion connect() herzustellen.
Wir müssen die Header-Datei, die uic
aus der Datei calculatorform.ui
generiert, wie folgt einbinden:
#include "ui_calculatorform.h"
Die Klasse wird auf ähnliche Weise definiert wie beim Ansatz der Einfachvererbung, nur dass wir diesmal sowohl von QWidget als auch von Ui::CalculatorForm
erben, und zwar wie folgt:
class CalculatorForm : public QWidget, private Ui::CalculatorForm { Q_OBJECT public: explicit CalculatorForm(QWidget *parent = nullptr); private slots: void on_inputSpinBox1_valueChanged(int value); void on_inputSpinBox2_valueChanged(int value); };
Wir vererben Ui::CalculatorForm
privat, um sicherzustellen, dass die Objekte der Benutzeroberfläche in unserer Unterklasse privat sind. Wir können sie auch mit den Schlüsselwörtern public
oder protected
vererben, genauso wie wir ui
im vorherigen Fall öffentlich oder geschützt machen konnten.
Der Konstruktor für die Unterklasse führt viele der gleichen Aufgaben aus wie der Konstruktor, der im Beispiel der Einfachvererbung verwendet wurde:
In diesem Fall kann auf die Widgets, die in der Benutzeroberfläche verwendet werden, auf die gleiche Weise zugegriffen werden wie auf ein Widget, das im Code von Hand erstellt wurde. Wir benötigen das Präfix ui
nicht mehr, um auf sie zuzugreifen.
Reagieren auf Sprachänderungen
Qt benachrichtigt Anwendungen, wenn sich die Sprache der Benutzeroberfläche ändert, indem es ein Ereignis vom Typ QEvent::LanguageChange sendet. Um die Mitgliedsfunktion retranslateUi()
des Benutzeroberflächenobjekts aufzurufen, reimplementieren wir QWidget::changeEvent()
in der Formularklasse, wie folgt:
void CalculatorForm::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } }
Verarbeitung von Formularen während der Laufzeit
Alternativ können Formulare auch zur Laufzeit verarbeitet werden, um dynamisch generierte Benutzeroberflächen zu erzeugen. Dazu kann das Modul QtUiTools verwendet werden, das die Klasse QUiLoader für die Verarbeitung von Formularen bereitstellt, die mit Qt Widgets Designer erstellt wurden.
Der UiTools-Ansatz
Für die Verarbeitung von Formularen zur Laufzeit ist eine Ressourcendatei erforderlich, die eine UI-Datei enthält. Außerdem muss die Anwendung für die Verwendung des Moduls QtUiTools konfiguriert werden. Dies geschieht durch Einfügen der folgenden Deklarationen in eine CMake
Projektdatei, wobei sichergestellt wird, dass die Anwendung entsprechend kompiliert und gelinkt wird.
find_package(Qt6 REQUIRED COMPONENTS Core Gui UiTools Widgets) target_link_libraries(textfinder PUBLIC Qt::Core Qt::Gui Qt::UiTools Qt::Widgets )
Für qmake
:
QT += uitools
Die Klasse QUiLoader stellt ein Formloader-Objekt zur Verfügung, um die Benutzeroberfläche zu erstellen. Diese Benutzeroberfläche kann von einem beliebigen QIODevice, z. B. einem QFile Objekt, abgerufen werden, um ein in der Ressourcendatei eines Projekts gespeichertes Formular zu erhalten. Die Funktion QUiLoader::load() konstruiert das Formular-Widget unter Verwendung der in der Datei enthaltenen Beschreibung der Benutzeroberfläche.
Die QtUiTools Modulklassen können mit der folgenden Direktive eingebunden werden:
#include <QtUiTools>
Die Funktion QUiLoader::load() wird aufgerufen, wie in diesem Code aus dem Text Finder-Beispiel gezeigt:
static QWidget *loadUiFile(QWidget *parent) { QFile file(u":/forms/textfinder.ui"_s); file.open(QIODevice::ReadOnly); QUiLoader loader; return loader.load(&file, parent); }
In einer Klasse, die QtUiTools verwendet, um ihre Benutzeroberfläche zur Laufzeit zu erstellen, können wir Objekte im Formular mit QObject::findChild() finden. Im folgenden Code werden beispielsweise einige Komponenten anhand ihrer Objektnamen und Widget-Typen gefunden:
ui_findButton = findChild<QPushButton*>("findButton"); ui_textEdit = findChild<QTextEdit*>("textEdit"); ui_lineEdit = findChild<QLineEdit*>("lineEdit");
Die Verarbeitung von Formularen zur Laufzeit gibt dem Entwickler die Freiheit, die Benutzeroberfläche eines Programms zu ändern, indem er einfach die UI-Datei ändert. Dies ist nützlich bei der Anpassung von Programmen an verschiedene Benutzerbedürfnisse, wie z. B. besonders große Symbole oder ein anderes Farbschema zur Unterstützung der Barrierefreiheit.
Automatische Verbindungen
Die Verbindungen zwischen Signalen und Slots, die für Kompilierzeit- oder Laufzeitformulare definiert sind, können entweder manuell oder automatisch eingerichtet werden, indem die Fähigkeit von QMetaObject genutzt wird, Verbindungen zwischen Signalen und entsprechend benannten Slots herzustellen.
Wenn wir in einem QDialog die vom Benutzer eingegebenen Informationen verarbeiten wollen, bevor sie akzeptiert werden, müssen wir das clicked()-Signal der OK-Schaltfläche mit einem benutzerdefinierten Slot in unserem Dialog verbinden. Wir zeigen zunächst ein Beispiel für einen Dialog, in dem der Slot von Hand verbunden wird, und vergleichen ihn dann mit einem Dialog, der eine automatische Verbindung verwendet.
Ein Dialog ohne Auto-Connect
Wir definieren den Dialog auf die gleiche Weise wie zuvor, fügen aber jetzt zusätzlich zum Konstruktor einen Slot ein:
class ImageDialog : public QDialog, private Ui::ImageDialog { Q_OBJECT public: explicit ImageDialog(QWidget *parent = nullptr); private slots: void checkValues(); };
Der Slot checkValues()
wird verwendet, um die vom Benutzer angegebenen Werte zu validieren.
Im Konstruktor des Dialogs richten wir die Widgets wie zuvor ein und verbinden das Signal clicked() der Schaltfläche Abbrechen mit dem Slot reject() des Dialogs. Wir deaktivieren auch die Eigenschaft autoDefault in beiden Schaltflächen, um sicherzustellen, dass das Dialogfeld nicht mit der Art und Weise interferiert, wie die Zeilenbearbeitung Return-Tastenereignisse behandelt:
ImageDialog::ImageDialog(QWidget *parent) : QDialog(parent) { setupUi(this); okButton->setAutoDefault(false); cancelButton->setAutoDefault(false); ... connect(okButton, &QAbstractButton::clicked, this, &ImageDialog::checkValues); }
Wir verbinden das clicked()-Signal der OK-Schaltfläche mit dem checkValues()-Slot des Dialogs, den wir wie folgt implementieren:
void ImageDialog::checkValues() { if (nameLineEdit->text().isEmpty()) { QMessageBox::information(this, tr("No Image Name"), tr("Please supply a name for the image."), QMessageBox::Cancel); } else { accept(); } }
Dieser benutzerdefinierte Slot tut das Minimum, das notwendig ist, um sicherzustellen, dass die vom Benutzer eingegebenen Daten gültig sind - er akzeptiert die Eingabe nur, wenn ein Name für das Bild angegeben wurde.
Widgets und Dialoge mit Auto-Connect
Obwohl es einfach ist, einen benutzerdefinierten Slot im Dialog zu implementieren und ihn im Konstruktor zu verbinden, könnten wir stattdessen die automatischen Verbindungsmöglichkeiten von QMetaObject nutzen, um das clicked()-Signal der OK-Schaltfläche mit einem Slot in unserer Unterklasse zu verbinden. uic
generiert automatisch Code in der setupUi()
Funktion des Dialogs, um dies zu tun, so dass wir nur einen Slot mit einem Namen deklarieren und implementieren müssen, der einer Standardkonvention folgt:
void on_<object name>_<signal name>(<signal parameters>);
Hinweis: Beim Umbenennen von Widgets im Formular müssen die Slot-Namen entsprechend angepasst werden, was zu einem Wartungsproblem werden kann. Aus diesem Grund raten wir davon ab, dies in neuem Code zu verwenden.
Mit dieser Konvention können wir einen Slot definieren und implementieren, der auf Mausklicks auf die Schaltfläche OK reagiert:
class ImageDialog : public QDialog, private Ui::ImageDialog { Q_OBJECT public: explicit ImageDialog(QWidget *parent = nullptr); private slots: void on_okButton_clicked(); };
Ein weiteres Beispiel für die automatische Verbindung von Signal und Steckplatz wäre der Text Finder mit seinem on_findButton_clicked()
Steckplatz.
Wir verwenden das System von QMetaObject, um Signal- und Steckplatzverbindungen zu aktivieren:
QMetaObject::connectSlotsByName(this);
Dies ermöglicht uns, den Slot wie unten gezeigt zu implementieren:
void TextFinder::on_findButton_clicked() { QString searchString = ui_lineEdit->text(); QTextDocument *document = ui_textEdit->document(); bool found = false; // undo previous change (if any) document->undo(); if (searchString.isEmpty()) { QMessageBox::information(this, tr("Empty Search Field"), tr("The search field is empty. " "Please enter a word and click Find.")); } else { QTextCursor highlightCursor(document); QTextCursor cursor(document); cursor.beginEditBlock(); ... cursor.endEditBlock(); if (found == false) { QMessageBox::information(this, tr("Word Not Found"), tr("Sorry, the word cannot be found.")); } } }
Die automatische Verbindung von Signalen und Slots bietet sowohl eine Standard-Namenskonvention als auch eine explizite Schnittstelle für Widget-Designer, mit der sie arbeiten können. Durch die Bereitstellung von Quellcode, der eine bestimmte Schnittstelle implementiert, können Designer von Benutzeroberflächen überprüfen, ob ihre Entwürfe tatsächlich funktionieren, ohne selbst Code schreiben zu müssen.
© 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.