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:

CalculatorForm::CalculatorForm(QWidget *parent)
    : QWidget(parent)
{
    setupUi(this);
}

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.