在 C++ 应用程序中使用设计器用户界面文件
Qt Widgets Designer 用户界面文件以 XML 格式表示表单的部件树。可以对表单进行处理:
编译时的表单处理
您使用Qt Widgets Designer 创建用户界面组件,并在构建应用程序时使用 Qt 的集成构建工具qmake和uic 为其生成代码。生成的代码包含表单的用户界面对象。它是一个 C++ 结构,包含
- 指向表单部件、布局、布局项、按钮组和操作的指针。
- 一个名为
setupUi()
的成员函数,用于在父窗口部件上构建窗口部件树。 - 一个名为
retranslateUi()
的成员函数,用于处理表单字符串属性的翻译。更多信息,请参阅 "对语言变化做出反应"。
生成的代码可以包含在您的应用程序中并直接使用。或者,你也可以用它来扩展标准 widget 的子类。
编译时处理过的表单可以通过以下方法之一在应用程序中使用:
- 直接方法:构建一个部件作为组件的占位符,并在其中设置用户界面。
- 单一继承法:对表单的基类(如QWidget 或QDialog )进行子类化,并包含表单用户界面对象的私有实例。
- 多继承方法:同时子类化表单的基类和表单的用户界面对象。这样就可以在子类的范围内直接使用表单中定义的部件。
为了演示,我们创建了一个简单的计算器表单应用程序。该程序基于最初的计算器表单示例。
该应用程序由一个源文件main.cpp
和一个用户界面文件组成。
使用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、用户界面和用于所需构建工具的项目文件,您可以对其进行修改。
直接方法
使用直接方法时,我们直接在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 Widget,并包含表单用户界面对象的私有实例。其形式可以是
- 成员变量
- 指针成员变量
使用成员变量
在这种方法中,我们子类化一个 Qt Widget,并在构造函数中设置用户界面。以这种方式使用的组件会将窗体中使用的部件和布局暴露给 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 )
在特殊情况下,例如下面的例子,include 指令使用了相对路径,可以使用qt_add_ui来生成ui_calculatorform.h
文件,而不是依赖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_<object name> - 前缀,以常规方式连接用户界面部件中的信号和插槽。更多信息,请参阅widgets-and-dialogs-with-auto-connect 。
这种方法的优点是可以简单地使用继承来提供基于QWidget 的接口,并将用户界面 widget 变量封装在ui
数据成员中。我们可以使用这种方法在同一 widget 中定义多个用户界面,每个界面都包含在自己的命名空间中,并将它们叠加(或组合)在一起。例如,这种方法可用于从现有表单中创建单独的标签页。
使用指针成员变量
另外,也可以将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
设为公共或受保护一样。
子类的构造函数与单一继承示例中的构造函数执行许多相同的任务:
在这种情况下,用户界面中使用的部件的访问方式与手工创建的部件的访问方式相同。我们不再需要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 方法
在运行时处理表单需要一个包含用户界面文件的资源文件。此外,应用程序需要配置为使用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() 函数:
static QWidget *loadUiFile(QWidget *parent) { QFile file(u":/forms/textfinder.ui"_s); file.open(QIODevice::ReadOnly); QUiLoader loader; return loader.load(&file, parent); }
在运行时使用QtUiTools 构建用户界面的类中,我们可以使用QObject::findChild() 定位表单中的对象。例如,在下面的代码中,我们根据对象名称和部件类型找到了一些部件:
ui_findButton = findChild<QPushButton*>("findButton"); ui_textEdit = findChild<QTextEdit*>("textEdit"); ui_lineEdit = findChild<QLineEdit*>("lineEdit");
在运行时处理表单使开发人员可以自由地更改程序的用户界面,只需更改用户界面文件即可。这在定制程序以满足各种用户需求时非常有用,例如,为支持无障碍访问,可以使用超大图标或不同的配色方案。
自动连接
为编译时或运行时窗体定义的信号和插槽连接既可以手动设置,也可以使用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); }
我们将OK按钮的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(); };
自动信号和插槽连接的另一个例子是带有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.")); } } }
信号和插槽的自动连接既提供了标准的命名规范,也为部件设计人员提供了明确的工作界面。通过提供实现给定界面的源代码,用户界面设计人员可以检查他们的设计是否真正有效,而无需亲自编写代码。
© 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.