本页内容

在 C++ 应用程序中使用Qt Widgets Designer UI 文件

Qt Widgets Designer UI 文件以 XML 格式表示表单的控件树。这些表单可以进行以下处理:

  • 在编译时,这意味着表单会被转换为可编译的 C++ 代码。
  • 运行时,即由QUiLoader 类处理表单,该类在解析 XML 文件的同时动态构建控件树。

编译时表单处理

您可以使用Qt Widgets Designer 创建用户界面组件,并在构建应用程序时利用Qt的集成构建工具qmake和uic为其生成代码。生成的代码包含表单的用户界面对象。这是一个C++结构体,其中包含:

  • 指向表单中控件、布局、布局项、按钮组和动作的指针。
  • 一个名为 `setupUi() ` 的成员函数,用于在父控件上构建控件树。
  • 一个名为retranslateUi() 的成员函数,用于处理表单字符串属性的翻译。有关详细信息,请参阅《响应语言变更》。

生成的代码可以包含在您的应用程序中,并直接在其中使用。此外,您还可以使用它来扩展标准控件的子类。

您可以在应用程序中通过以下任一方法使用经过编译时处理的表单:

  • 直接方法:您创建一个控件作为该组件的占位符,并在其中设置用户界面。
  • 单继承方法:您继承表单的基类(例如QWidget 或QDialog ),并包含该表单用户界面对象的一个私有实例。
  • 多重继承法:同时继承表单的基类和表单用户界面对象。这样,表单中定义的控件就可以在子类的作用域内直接使用。

为了演示,我们将创建一个简单的“计算器表单”应用程序。它基于原始的“计算器表单”示例。

该应用程序由一个源文件main.cpp 和一个UI文件组成。

以下是使用Qt Widgets Designer 设计的calculatorform.ui 文件:

显示计算器布局的表单编辑器截图

使用CMake 构建可执行文件时,需要一个CMakeLists.txt 文件:

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
)

该表单列在qt_add_executable() 中的C++源文件列表中。选项CMAKE_AUTOUIC 会指示CMake 运行uic 工具,以生成可供源文件使用的ui_calculatorform.h 文件。

使用qmake 构建可执行文件时,需要提供.pro 文件:

TEMPLATE    = app
FORMS       = calculatorform.ui
SOURCES     = main.cpp

该文件的特殊之处在于其中的FORMS 声明,它会告知qmake 应使用uic 处理哪些文件。在此情况下,calculatorform.ui 文件用于生成ui_calculatorform.h 文件,该文件可供SOURCES 声明中列出的任何文件使用。

注意:您可以 使用Qt Creator 来创建“计算器表单”项目。它会自动生成 main.cpp、UI 以及适用于所需构建工具的项目文件,您可以对其进行修改。

直接方法

要使用直接方法,我们需在main.cpp 中直接包含ui_calculatorform.h 文件:

#include "ui_calculatorform.h"

main 函数通过构建一个标准的QWidget 来创建计算器控件,我们用它来承载由calculatorform.ui 文件描述的用户界面。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget widget;
    Ui::CalculatorForm ui;
    ui.setupUi(&widget);

    widget.show();
    return app.exec();
}

在此情况下,Ui::CalculatorForm 是来自ui_calculatorform.h 文件的一个界面描述对象,它负责设置对话框中的所有控件及其信号与槽之间的连接。

直接方法提供了一种快速简便的方式,可在您的应用程序中使用简单且自包含的组件。然而,使用Qt Widgets Designer 创建的组件通常需要与应用程序的其他代码紧密集成。 例如,上面提供的CalculatorForm 代码虽然可以编译并运行,但QSpinBox 对象无法与QLabel 进行交互,因为我们需要一个自定义槽来执行加法运算,并在QLabel 中显示结果。要实现这一点,我们需要使用单继承方法。

单继承方法

要使用单继承方法,我们需要继承一个标准的 Qt Widgets 控件,并包含表单用户界面对象的一个私有实例。具体实现形式如下:

  • 成员变量
  • 指针成员变量

使用成员变量

在此方法中,我们继承一个 Qt Widgets 控件并从构造函数内部设置用户界面。采用这种方式的组件将表单中使用的控件和布局暴露给 Qt Widgets 控件子类,并提供了一个标准系统,用于在用户界面与应用程序中的其他对象之间建立信号与槽连接。 生成的Ui::CalculatorForm 结构是该类的成员。

“计算器表单”示例中采用了这种方法。

为了确保能够使用该用户界面,在引用Ui::CalculatorForm 之前,我们需要包含uic 生成的头文件:

#include "ui_calculatorform.h"

必须更新项目文件以包含calculatorform.h 。对于CMake :

qt_add_executable(calculatorform
    calculatorform.cpp calculatorform.h calculatorform.ui
    main.cpp
)

在特定情况下,例如下例中包含指令使用了相对路径,可以使用qt_add_ui()来生成ui_calculatorform.h 文件,而不是依赖AUTOUIC。

何时应优先使用 qt_add_ui 而不是 AUTOUIC

#include "src/files/ui_calculatorform.h"
qt_add_ui(calculatorform SOURCES calculatorform.ui
          INCLUDE_PREFIX src/files)

关于qmake :

HEADERS     = calculatorform.h

该子类的定义如下:

class CalculatorForm : public QWidget
{
    Q_OBJECT

public:
    explicit CalculatorForm(QWidget *parent = nullptr);

private slots:
    void updateResult();

private:
    Ui::CalculatorForm ui;
};

该类的一个重要特征是其私有对象ui ,该对象提供了用于设置和管理用户界面的代码。

子类的构造函数仅需调用ui 对象的setupUi() 函数,即可为对话框构建并配置所有控件和布局。完成此操作后,即可根据需要修改用户界面。

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

我们可以像往常一样,通过添加 on_<对象名称> 前缀,连接用户界面控件中的信号和槽。有关更多信息,请参阅widgets-and-dialogs-with-auto-connect。

这种方法的优势在于:它通过简单的继承方式提供了基于 `QWidget` 的接口,并将用户界面控件的变量封装在 `ui ` 数据成员中。 我们可以利用此方法在同一个控件内定义多个用户界面,每个界面都包含在各自的命名空间中,并将其叠加(或组合)在一起。例如,此方法可用于从现有表单中创建独立的选项卡。

使用指针成员变量

此外,还可以将Ui::CalculatorForm 结构体设为类的指针成员。此时,头文件将如下所示:

namespace Ui {
    class CalculatorForm;
}

class CalculatorForm : public QWidget
...
virtual ~CalculatorForm();
...
private:
    Ui::CalculatorForm *ui;
...

相应的源文件如下所示:

#include "ui_calculatorform.h"

CalculatorForm::CalculatorForm(QWidget *parent) :
    QWidget(parent), ui(new Ui::CalculatorForm)
{
    ui->setupUi(this);
}

CalculatorForm::~CalculatorForm()
{
    delete ui;
}

这种方法的优势在于,用户界面对象可以进行前向声明,这意味着我们无需在头文件中包含生成的ui_calculatorform.h 文件。这样,无需重新编译依赖的源文件即可更改表单。如果该类受二进制兼容性限制,这一点尤为重要。

我们通常建议在库和大型应用程序中采用此方法。有关更多信息,请参阅《创建共享库》。

多重继承方法

使用Qt Widgets Designer 创建的表单可以与基于标准QWidget 的类一起进行子类化。这种方法使得表单中定义的所有用户界面组件在子类的作用域内均可直接访问,并允许通过connect()函数以常规方式建立信号与槽的连接。

我们需要包含由uic 根据calculatorform.ui 文件生成的头文件,如下所示:

#include "ui_calculatorform.h"

该类的定义方式与单继承方法类似,不同之处在于这次我们同时继承了QWidget 和Ui::CalculatorForm ,如下所示:

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

我们采用私有继承方式继承Ui::CalculatorForm ,以确保子类中的用户界面对象为私有。我们也可以使用public 或protected 关键字进行继承,这与之前将ui 设为public或protected的情况类似。

子类的构造函数执行了与单继承示例中构造函数相同的许多任务:

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

在此情况下,用户界面中使用的控件可以像手动在代码中创建的控件一样被访问。我们不再需要使用ui 前缀来访问它们。

应对语言变更

当用户界面语言发生变化时,Qt 会通过发送类型为QEvent::LanguageChange 的事件来通知应用程序。为了调用用户界面对象的成员函数retranslateUi() ,我们在表单类中重写了QWidget::changeEvent() ,具体如下:

void CalculatorForm::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
   }
}

运行时表单处理

此外,表单还可以在运行时进行处理,从而生成动态生成的用户界面。这可以通过QtUiTools 模块来实现,该模块提供了QUiLoader 类来处理使用Qt Widgets Designer 创建的表单。

UiTools 方法

要在运行时处理表单,必须有一个包含 UI 文件的资源文件。此外,还需配置应用程序以使用QtUiTools 模块。具体操作是在CMake 项目文件中加入以下声明,并确保应用程序已正确编译和链接。

find_package(Qt6 REQUIRED COMPONENTS Core Gui UiTools Widgets)
target_link_libraries(textfinder PUBLIC
    Qt::Core
    Qt::Gui
    Qt::UiTools
    Qt::Widgets
)

对于qmake :

QT += uitools

QUiLoader 类提供了一个表单加载器对象,用于构建用户界面。该用户界面可从任何QIODevice (例如QFile 对象)中获取,从而获取存储在项目资源文件中的表单。QUiLoader::load()函数会利用文件中包含的用户界面描述来构建表单控件。

可通过以下指令引入QtUiTools 模块的类:

#include <QtUiTools>

QUiLoader::load() 函数的调用方式如“文本查找器”示例中的代码所示:

staticQWidget*loadUiFile(QWidget*parent)
{
    QFile file(u":/forms/textfinder.ui"_s);
    if(!file.open(QIODevice::ReadOnly))
        qFatal("Cannot open resource file");

   returnQUiLoader().load(&file,parent);
}

在运行时使用 `QtUiTools ` 构建用户界面的类中,我们可以使用 `QObject::findChild()` 定位表单中的对象。例如,在下面的代码中,我们根据对象名称和控件类型定位了一些组件:

    ui_findButton = findChild<QPushButton*>("findButton");
    ui_textEdit = findChild<QTextEdit*>("textEdit");
    ui_lineEdit = findChild<QLineEdit*>("lineEdit");

在运行时处理表单,使开发者只需修改 UI 文件,即可自由更改程序的用户界面。这在根据各种用户需求定制程序时非常有用,例如为辅助功能支持提供超大图标或不同的配色方案。

自动连接

针对编译时或运行时表单定义的信号与插槽连接,既可以手动设置,也可以利用QMetaObject 在信号与命名恰当的插槽之间建立连接的功能,实现自动连接。

通常,在QDialog 中,如果我们希望在接受用户输入的信息之前对其进行处理,就需要将“确定”按钮的clicked()信号连接到对话框中的自定义槽上。我们将首先展示一个手动连接该槽的对话框示例,然后将其与使用自动连接的对话框进行比较。

不使用自动连接的对话框

我们以与之前相同的方式定义对话框,但现在除了构造函数之外,还添加了一个槽:

class ImageDialog : public QDialog, private Ui::ImageDialog
{
    Q_OBJECT

public:
    explicit ImageDialog(QWidget *parent = nullptr);

private slots:
    void checkValues();
};

checkValues() 槽将用于验证用户提供的值。

在对话框的构造函数中,我们像之前一样设置控件,并将“取消”按钮的clicked()信号连接到对话框的reject()槽。此外,我们还禁用了两个按钮中的autoDefault 属性,以确保对话框不会干扰行编辑框处理回车键事件的方式:

ImageDialog::ImageDialog(QWidget *parent)
    : QDialog(parent)
{
    setupUi(this);
    okButton->setAutoDefault(false);
    cancelButton->setAutoDefault(false);
    ...
    connect(okButton, &QAbstractButton::clicked, this, &ImageDialog::checkValues);
}

我们将“确定”按钮的clicked()信号连接到对话框的checkValues()槽,该槽的实现如下:

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

这个自定义槽仅执行确保用户输入数据有效的最低限度操作——只有当图像被赋予了名称时,才会接受该输入。

支持自动连接的控件和对话框

虽然在对话框中实现自定义槽并在构造函数中进行连接很简单,但我们也可以利用QMetaObject 的自动连接功能,将“确定”按钮的clicked()信号连接到子类的某个槽上。uic 会自动在对话框的setupUi() 函数中生成实现此功能的代码,因此我们只需声明并实现一个名称符合标准规范的槽函数即可:

void on_<object name>_<signal name>(<signal parameters>);

注意:当 重命名表单中的控件时 ,槽的名称也需要相应调整,这可能会成为维护难题。因此,我们建议在新代码中不要使用此功能。

遵循此约定,我们可以定义并实现一个槽,用于响应“确定”按钮上的鼠标点击:

class ImageDialog : public QDialog, private Ui::ImageDialog
{
    Q_OBJECT

public:
    explicit ImageDialog(QWidget *parent = nullptr);

private slots:
    void on_okButton_clicked();
};

自动信号与插槽连接的另一个示例是“文本查找器”(Text Finder)及其on_findButton_clicked() 插槽。

我们利用QMetaObject 的机制来实现信号与插槽的连接:

    QMetaObject::connectSlotsByName(this);

这使我们能够实现如下所示的插槽:

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."));
        }
    }
}

信号与槽的自动连接既提供了标准的命名约定,也为控件设计师提供了明确的接口作为设计依据。通过提供实现给定接口的源代码,用户界面设计师无需亲自编写代码,即可验证其设计是否真正有效。

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