Qt 小白成长系列 2 QUiLoader 动态加载UI

一、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。例如,在这个例子中:
    • 按钮的 objectNamefindButton
    • 文本框的 objectNametextEdit
    • 输入框的 objectNamelineEdit
  • 保存 :将文件保存到项目中的 forms 目录下,命名为 textfinder.ui
步骤 2: 配置 CMakeLists.txt (告诉构建系统如何处理 UI 文件)

这是最关键的一步。你必须告诉 CMake 这个 .ui 文件是一个需要被特殊处理的 "资源"。

  1. 找到并链接 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
    )
  2. 使用 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"
  3. 至此,编译配置完成。当你构建项目时,forms/textfinder.ui 的内容就会被编译进你的 textfinder.exe (或其他可执行文件) 中。

    步骤 3: 编写 C++ 代码 (在运行时加载 UI)

    现在,你的程序里已经 "携带" 了 UI 文件,接下来就是在代码中把它 "取出来" 并使用。

  1. 包含必要的头文件 你需要 QUiLoader 来加载,QFile 来访问资源。

    cpp

    运行

    复制代码
    #include <QUiLoader>
    #include <QFile>
  2. 创建加载 UI 的函数一个好的实践是将加载逻辑封装在一个函数里。

    cpp

    运行

    cpp 复制代码
    static 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;
    }
  3. 在主窗口构造函数中使用加载的 UI 在你的主窗口(这里是 TextFinder)的构造函数中,调用上面的函数。

    cpp

    运行

    cpp 复制代码
    TextFinder::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"));
    }

总结:完整的工作流程

  1. 开发者 :在 Qt Designer 中设计 textfinder.ui
  2. 开发者 :在 CMakeLists.txt 中使用 qt_add_resourcestextfinder.ui 添加到资源列表。
  3. 构建系统 (CMake + rcc) :在编译项目时,rcc (Qt 资源编译器) 读取 textfinder.ui 的内容,将其转换成 C++ 代码,并编译进 textfinder 可执行文件。
  4. 用户 :运行 textfinder 程序。
  5. 程序运行时
    • TextFinder 构造函数被调用。
    • loadUiFile() 函数执行。
    • QFile file(u":/forms/textfinder.ui"_s) 从程序内部资源中定位到 UI 数据。
    • QUiLoader loader.load() 将 UI 数据解析,并在内存中创建出 QPushButton, QLineEdit 等控件对象。
    • findChild<...>() 通过 objectName 找到了这些动态创建的控件。
    • connectSlotsByName() 自动连接了信号和槽。
    • 布局被设置,窗口显示出来,用户可以交互。
相关推荐
幸福的达哥19 小时前
PyQt5多线程UI更新方法
python·qt·ui
会一点设计1 天前
金牌年度汇报PPT的逻辑框架与模板范例!2026全新整理
ui·powerpoint·ux·ppt
●VON1 天前
从系统亮度监听到 UI 重绘:Flutter for OpenHarmony TodoList 深色模式的端到端响应式实现
学习·flutter·ui·openharmony·布局·von
XPii2 天前
Photoshop应用——将图片变为素描效果
ui·photoshop
雨季6662 天前
构建 OpenHarmony 文本高亮关键词标记器:用纯字符串操作实现智能标注
开发语言·javascript·flutter·ui·ecmascript·dart
雨季6662 天前
构建 OpenHarmony 应用内消息通知模拟器:用纯 UI 演示通知流
flutter·ui·自动化·dart
UI设计兰亭妙微2 天前
兰亭妙微:以HTML前端、UI/交互/图标设计赋能数字孪生与大屏设计新标杆
前端·ui·用户体验设计
信徒favor2 天前
我用AI一次性生成3种UI原型(附提示词)
ui
ll_god3 天前
android compose ui 结合 ViewModel适配方案
android·ui
SoraLuna3 天前
KuiklyUI for OpenHarmony 实战 01:源码构建与运行(Mac)
macos·ui·鸿蒙