目录
注意本博客使用的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的时候,我们构建这个工程。
完事!