目录
注意本博客使用的solution并不是最简单的方案,这是为了体验一下QUiTools和Qt6所支持的QuiLoader所写。这个计算器特别简单:

当你点击中间的"+"时可以切换运算符号,左右两侧都是简单的SpinBox,当你点击=或者变动数字或者变动运算符的时候会自动刷新结果。
添加一个全新的工程
很简单,我们使用的是基于CMake构建系统的一个小Demo,所以需要在构建的时候选择CMake构建系统。
啥是QUiLoader
我们知道,当我们使用QWidget编程的时候,基本的流程是先根据一定的需求设计Ui,当我们保存的时候,Qt将会解析我们的.ui文件(本质上是一个xml文件)得到控件树,然后生成对应的CPP模板代码进行解析(当我们默认的添加一组ui/h文件的时候),那么问题来了,我们可以自己控制解析吗?在之前没有QUiLoader的时候,我们可能需要从头开始,现在不需要了,用人家造好的轮子就行。
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
{
QUiLoader loader;
QFile file(":/forms/myform.ui");
file.open(QFile::ReadOnly);
QWidget *myWidget = loader.load(&file, this);
file.close();
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(myWidget);
setLayout(layout);
}
这个来自官方的超级迷你Demo就是说明了这个类的使用方法。他就是读取一个UI文件返回解析的Widget。

我们先大概设计这个MainWindow出来:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class QSpinBox;
class QPushButton;
class QLabel;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
std::optional<QWidget*> loadCalculatorAsDef();
void anaylzeWidgets(); // 解析得到的QWidget
void initOps(); // 初始化操作符映射池
void handleOpsChange(); // 处理符号变化
void handleResChange(); // 处理数字变化
~MainWindow();
private:
Ui::MainWindow *ui;
QWidget* calculator; // 代劳指针,这个指针将会维护我们拿到的Widget
QMap<QString, std::function<int(int, int)>> ops; // 操作池
QStringList opsStr{"+", "-", "x", "/"}; // 支持的操作符
QSpinBox* spbx1; /// 下面开始都是代劳的操作指针
QSpinBox* spbx2;
QPushButton* op_btns;
QPushButton* res_btn;
QLabel* res_label;
int opIndex{0}; // 当前正在运行的操作符号薄记
};
#endif // MAINWINDOW_H
下面我们开始考虑基本的加载逻辑:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
initOps();
if(!loadCalculatorAsDef().has_value()) // 我们能不能加载这个Widget?
Error("Do nothing :("); // 小Demo,装死就行
else
anaylzeWidgets(); // 分析这个Widget
}
anaylzeWidgets()这个我们自己编写的函数TIPS:不熟悉下面的findChildren操作的同志们查找文档,他就是使用空间名称反向查找空间的
void MainWindow::anaylzeWidgets()
{
spbx1 = calculator->findChild<QSpinBox*>("operatorSpinBox1");
if(!spbx1){
Error("Can not find left SpinBox");
return;
}
spbx2 = calculator->findChild<QSpinBox*>("operatorSpinBox2");
if(!spbx2){
Error("Can not find right SpinBox");
return;
}
op_btns = calculator->findChild<QPushButton*>("operatorSwitcher_btn");
if(!op_btns){
Error("Can not find operator btn");
return;
}
res_btn = calculator->findChild<QPushButton*>("get_res_btn");
if(!res_btn){
Error("Can not find the res btn");
return;
}
res_label = calculator->findChild<QLabel*>("result");
if(!res_label){
Error("Can not find the res label");
return;
}
connect(op_btns, &QPushButton::clicked, this, &MainWindow::handleOpsChange);
connect(res_btn, &QPushButton::clicked, this, &MainWindow::handleResChange);
connect(spbx1, &QSpinBox::valueChanged, this, &MainWindow::handleResChange);
connect(spbx2, &QSpinBox::valueChanged, this, &MainWindow::handleResChange);
}
好,那么我们怎么拿到Widget呢?简单的仿写一个
std::optional<QWidget*> MainWindow::loadCalculatorAsDef()
{
QUiLoader calcloader;
QFile file(UI_PATH); // 这个UIPath就是我们的ui文件所在位置
file.open(QIODevice::ReadOnly);
if(!file.isOpen()){ // 文件打不打得开?
Error("Can not find demo ui");
return {};
}
QWidget* widget = calcloader.load(&file, this); // 能不能有效的从这个UI文件中加载一个Widget?
if(!widget){
Error("Can not analyze the ui file");
return {};
}
this->calculator = widget;
qDebug() << calcloader.availableWidgets(); // 查看支持的Widget
return {widget};
}
剩下的就是写拿到的控件逻辑了:
void MainWindow::initOps()
{
ops.insert(opsStr[0], [=](int x, int y)->int{return x + y;});
ops.insert(opsStr[1], [=](int x, int y)->int{return x - y;});
ops.insert(opsStr[2], [=](int x, int y)->int{return x * y;});
ops.insert(opsStr[3], [=](int x, int y)->int{return x/y;});
}
void MainWindow::handleOpsChange()
{
this->opIndex++;
opIndex %= ops.size();
op_btns->setText(opsStr[opIndex]);
handleResChange();
}
void MainWindow::handleResChange()
{
auto func = ops.value(opsStr[opIndex]);
auto val1 = spbx1->value();
auto val2 = spbx2->value();
if(opIndex == 3 && val2 == 0){
res_label->setText("NUL");
return;
}
auto res = func(val1, val2);
res_label->setText(QString::number(res));
}
运行这个工程
我们要留意到每一个类使用的时候,要不要添加依赖:需要的!
Header: |
#include <QUiLoader> |
|---|---|
CMake: |
find_package(Qt6 REQUIRED COMPONENTS UiTools) target_link_libraries(mytarget PRIVATE Qt6::UiTools) |
qmake: |
QT += uitools |
Inherits: |
QObject |
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui UiTools Widgets)
target_link_libraries(
QUiLoader PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::UiTools
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui) # 加一些依赖
当我们修改好CMakeLists的时候,我们构建这个工程。

完事!