一、Text Finder
打开,QT 官方示例,你会发现和文档中介绍的方法不一样,这个示例提供了一个动态加载UI的方法

先看下关键代码:
cpp
static QWidget *loadUiFile(QWidget *parent)
{
QFile file(u":/forms/textfinder.ui"_s);
file.open(QIODevice::ReadOnly);
QUiLoader loader;
return loader.load(&file, parent);
}
//! [4]
//! [5]
static QString loadTextFile()
{
QFile inputFile(u":/forms/input.txt"_s);
inputFile.open(QIODevice::ReadOnly);
QTextStream in(&inputFile);
return in.readAll();
}
//! [5]
//! [0]
TextFinder::TextFinder(QWidget *parent)
: QWidget(parent)
{
QWidget *formWidget = loadUiFile(this);
//! [1]
ui_findButton = findChild<QPushButton*>("findButton");
ui_textEdit = findChild<QTextEdit*>("textEdit");
ui_lineEdit = findChild<QLineEdit*>("lineEdit");
//! [0] //! [1]
//! [2]
QMetaObject::connectSlotsByName(this);
//! [2]
//! [3a]
ui_textEdit->setText(loadTextFile());
//! [3a]
//! [3b]
auto *layout = new QVBoxLayout(this);
layout->addWidget(formWidget);
//! [3b]
//! [3c]
setWindowTitle(tr("Text Finder"));
}
//! [3c]
1、QUiLoader介绍
QUiLoader 是 Qt 中一个强大的工具,用于在运行时动态加载 由 Qt Designer 创建的 .ui 文件。这与更常见的在编译时 通过 uic 工具将 .ui 文件转换为 C++ 头文件的方法形成对比。
下面我将详细解释 QUiLoader 的用法、工作原理、优点、缺点,并提供一个完整的示例。
什么是 QUiLoader?
QUiLoader 是一个类,它可以读取一个 XML 格式的 .ui 文件,并在内存中动态地创建出对应的 Qt 控件(QWidget, QPushButton, QLineEdit 等)及其布局。最终,它会返回一个指向你在 .ui 文件中设计的顶层窗口或容器的指针。
为什么以及何时使用 QUiLoader?
虽然编译时加载(uic)是 Qt 的标准做法,但在某些场景下,QUiLoader 更具优势:
- 插件化架构 :如果你正在开发一个支持插件的应用程序,插件可能需要提供自己的 UI。使用
QUiLoader,插件可以只提供一个.ui文件和一个实现逻辑的共享库(.dll或.so),而无需重新编译主应用程序。主应用程序可以在运行时加载插件的 UI。 - 动态主题 / 皮肤:允许用户在不重启应用的情况下切换整个界面布局或主题。
- 需要分发 UI 而不暴露源码 :你可以分发
.ui文件,让用户或第三方开发者能够修改界面布局,而无需提供或重新编译 C++ 源代码。 - 快速原型设计 :在某些脚本语言绑定(如 PyQt/PySide)中,
QUiLoader是加载.ui文件的标准方式,因为它避免了生成和维护额外的 Python 代码文件。
QUiLoader 的主要缺点
- 性能开销 :与编译时生成的代码相比,
QUiLoader在运行时解析 XML 文件并动态创建对象,这会带来一定的启动性能开销。 - 类型安全 :你无法在编译时获得对
.ui文件中控件的直接访问。你必须在运行时使用findChild()方法来查找控件,这意味着如果控件名称拼写错误,错误只会在运行时才会显现。 - 功能限制 :
QUiLoader无法直接实例化自定义的 C++ 控件(除非你对其进行子类化并注册),也无法直接连接到自定义的槽函数(需要通过信号和槽的名称字符串来连接)。
动态加载 UI 的核心思想是:将 UI 的设计(.ui文件)和程序的逻辑(C++ 代码)分离开。在编译时 ,我们将 .ui 文件的内容打包进最终的可执行程序中;在运行时 ,程序再从内部读取这个 UI 描述,并用 QUiLoader 将其 "画" 出来。
2、使用步骤
步骤 1: 准备 .ui 文件
首先,你需要一个用 Qt Designer 创建的 .ui 文件。在这个例子中,就是 forms/textfinder.ui。
- 创建:在 Qt Creator 中,你可以新建一个 "Qt Designer Form" 文件。
- 设计 :在 Designer 中拖拽控件(如
QPushButton,QLineEdit,QTextEdit)来布局你的界面。 - 命名 :为你需要在代码中操作的控件设置一个有意义的
objectName。例如,在这个例子中:- 按钮的
objectName是findButton。 - 文本框的
objectName是textEdit。 - 输入框的
objectName是lineEdit。
- 按钮的
- 保存 :将文件保存到项目中的
forms目录下,命名为textfinder.ui。
步骤 2: 配置 CMakeLists.txt (告诉构建系统如何处理 UI 文件)
这是最关键的一步。你必须告诉 CMake 这个 .ui 文件是一个需要被特殊处理的 "资源"。
-
找到并链接
UiTools模块QUiLoader类位于UiTools模块中,所以你必须确保项目链接了它。cmake
cpp# 在 find_package 中包含 UiTools find_package(Qt6 REQUIRED COMPONENTS Core Gui UiTools Widgets) # 在 target_link_libraries 中链接 UiTools target_link_libraries(textfinder PUBLIC Qt::Core Qt::Gui Qt::UiTools # <--- 必须链接这个 Qt::Widgets ) -
使用
qt_add_resources将.ui文件嵌入程序 这是将.ui文件打包进可执行文件的核心命令。cmake
cpp# 1. 定义一个变量来存放所有资源文件的路径 set(textfinder_resource_files "forms/input.txt" "forms/textfinder.ui" # <--- 把你的.ui文件加进来 ) # 2. 调用命令,将这些文件嵌入到名为 "textfinder" 的可执行文件中 qt_add_resources(textfinder "textfinder" PREFIX "/" # <--- 定义一个资源路径前缀 FILES ${textfinder_resource_files} )PREFIX "/":这非常重要。它定义了一个虚拟的根目录。在 C++ 代码中,你需要通过":/"+ "文件路径" 来访问它。所以,forms/textfinder.ui的完整资源路径就是":/forms/textfinder.ui"。
-
至此,编译配置完成。当你构建项目时,
forms/textfinder.ui的内容就会被编译进你的textfinder.exe(或其他可执行文件) 中。步骤 3: 编写 C++ 代码 (在运行时加载 UI)
现在,你的程序里已经 "携带" 了 UI 文件,接下来就是在代码中把它 "取出来" 并使用。
-
包含必要的头文件 你需要
QUiLoader来加载,QFile来访问资源。cpp
运行
#include <QUiLoader> #include <QFile> -
创建加载 UI 的函数一个好的实践是将加载逻辑封装在一个函数里。
cpp
运行
cppstatic QWidget *loadUiFile(QWidget *parent) { // 1. 使用资源路径创建 QFile 对象 // 注意路径是 ":/forms/textfinder.ui",它对应了 CMakeLists.txt 中的配置 QFile file(u":/forms/textfinder.ui"_s); // 2. 以只读方式打开文件 // 这是一个好习惯,可以检查文件是否存在或可读 if (!file.open(QIODevice::ReadOnly)) { // 如果失败,可以打印错误信息 qDebug() << "Cannot open the UI file: " << file.errorString(); return nullptr; } // 3. 创建 QUiLoader 对象 QUiLoader loader; // 4. 调用 load() 方法加载UI // 这个方法会解析 .ui 文件的内容,并返回一个包含所有控件的顶层 QWidget 指针 QWidget *formWidget = loader.load(&file, parent); // 5. 关闭文件 file.close(); return formWidget; } -
在主窗口构造函数中使用加载的 UI 在你的主窗口(这里是
TextFinder)的构造函数中,调用上面的函数。cpp
运行
cppTextFinder::TextFinder(QWidget *parent) : QWidget(parent) { // 1. 调用函数加载UI,得到一个 QWidget 指针 QWidget *formWidget = loadUiFile(this); if (!formWidget) { // 如果加载失败,可以直接返回或处理错误 return; } // 2. (关键) 查找UI中的具体控件 // 因为UI是动态加载的,编译器不知道里面有什么,所以需要用 findChild<T>() // 通过控件的 objectName 来查找 ui_findButton = findChild<QPushButton*>("findButton"); ui_textEdit = findChild<QTextEdit*>("textEdit"); ui_lineEdit = findChild<QLineEdit*>("lineEdit"); // 3. 自动连接信号和槽 (可选但推荐) // 这个神奇的函数会自动查找形如 on_<objectName>_<signalName> 的槽函数 // 并将其与对应的控件信号连接。 // 例如,它会找到 on_findButton_clicked() 并连接到 findButton 的 clicked() 信号。 QMetaObject::connectSlotsByName(this); // 4. 将加载的UI放入布局中并显示 auto *layout = new QVBoxLayout(this); layout->addWidget(formWidget); setWindowTitle(tr("Text Finder")); }
总结:完整的工作流程
- 开发者 :在 Qt Designer 中设计
textfinder.ui。 - 开发者 :在
CMakeLists.txt中使用qt_add_resources将textfinder.ui添加到资源列表。 - 构建系统 (CMake + rcc) :在编译项目时,
rcc(Qt 资源编译器) 读取textfinder.ui的内容,将其转换成 C++ 代码,并编译进textfinder可执行文件。 - 用户 :运行
textfinder程序。 - 程序运行时 :
TextFinder构造函数被调用。loadUiFile()函数执行。QFile file(u":/forms/textfinder.ui"_s)从程序内部资源中定位到 UI 数据。QUiLoader loader.load()将 UI 数据解析,并在内存中创建出QPushButton,QLineEdit等控件对象。findChild<...>()通过objectName找到了这些动态创建的控件。connectSlotsByName()自动连接了信号和槽。- 布局被设置,窗口显示出来,用户可以交互。