QT高级编程技巧(一)-- 编写高效的signal & slot通信代码
关于QT的线程通信,我们都会想到signal & slot机制。先回顾下利用signal & slot机制实现控件消息处理的方法。
控件消息处理
假设我们的主界面上有一个使用ui->btn指向的QPushButton对象,要实现该对象的clicked消息处理,可以在主界面对象MainWindow上添加一个slot方法onBtnClicked,并在其构造函数中使用connect方法与ui->btn的clicked消息进行绑定,如下:
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new ui::MainWindow) { ui->setupUi(this); // 方法一,推荐使用 connect(ui->btn, &QPushButton::clicked,
this, &MainWindow::onBtnClicked); // 方法二 //connect(ui->btn, SIGNAL(clicked),
this, SLOT(onBtnClicked)); … }
void MainWindow::onBtnClicked(void)
{
doSomething();
}
事实上这种方法很繁杂。单纯为了处理一个按钮的消息我们就要新建一个slot方法,当界面控件多了的时候,会使得代码臃肿不堪(即使你可以设计成多个控件消息共享一个槽函数)。
在新版的支持C++11的QT中,我们可以使用Lambda函数优雅地解决这个问题。如下:
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new ui::MainWindow) { setupUi(this); // 使用lambda函数实现slot方法 connect(ui->btn, &QPushButton::clicked, [&](){ doSomething(); }); … }
使用Lambda函数的另一个好处是,可以借助闭包的概念加速开发。比如我曾做过一个运动控制项目,界面上有32个之多的QCheckBox控件,如下:
需要响应每个QCheckBox控件的clicked消息,改变对应的IO口电平输出。我的做法是,对这些控件进行Tab排序,并将第一个控件命名为ui->ioChk,然后编码如下:
typedef QCheckBox *F_QCB; #define F_QNEXT(w) (w->nextInFocusChain())
…
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent), ui(new ui::MainWindow)
{
setupUi(ui);
…
F_QCB cb = ui->ioChk;
for (int i = 0; i < 32; ++i)
{
connect(cb, &QCheckBox::clicked, [=](bool s){
setIo(i, s); // 改变第i个端口的输出电平为s
});
cb = F_QNEXT(cb);
}
…
}
要读懂上面的程序,你需要一些关于C++11标准的Lambda函数的知识,代码中使用值捕获方式捕获i变量值形成闭包函数,这些闭包函数被绑定到不同的QCheckBox的clicked消息中。
线程间通信
很长的时间里,我都以为QT的signal & slot机制只适用于单向异步通信。事实上,它可以设计为带返回的同步通信。
有个案例如下:工作线程在运行时需要同步提取界面上的参数信息(假设要提取界面上的QSpinBox的值),要同时保证界面的响应和工作线程的不间断运转。
一种可能的实现方法是,在工作线程上的某个对象定义一个request消息,绑定到MainWindow的response槽中,然后在MainWindow中定义一个answer消息,绑定到工作线程上的某个对象的onAnswer槽中。通信过程如下:
1. 工作线程需要界面参数值,发送request消息
2. 主线程接收到request消息,开始执行response槽方法
3. 在response函数中,获取界面控件的值,发送answer消息
4. 工作线程收到answer消息,调用onAnswer槽方法,恢复之前的运行流程
这种方法条理清晰,但编码实现过于繁琐。我的实现方法是,在工作线程实现一个形如request(QString req, QVariant& arg)的消息,然后以QT::BlockBlockingQueuedConnection方式连接至MainWindow的response槽方法中。编码如下:
WorkerThread声明:
#ifndef __WORKER_THREAD_H
#define __WORKER_THREAD_H
class WorkerThread : public QThread {
public:
…
// @Override
void run();
private:
// 用于通信
class InThreadObject;
InThreadObject *ito;
…
}
#endif
WorkerThread实现:
class WorkerThread::InThreadObject : public QObject {
Q_OBJECT
signals:
void request(QString req, QVariant& arg);
}
void WorkerThread::run()
{
ito = new InThreadObject;
…
// 注意下面的连接使用了QT::BlockingQueuedConnection选项
connect(ito,
&InThreadObject::request,
pMainWindow,
&MainWindow::response,
QT::BlockingQueuedConnection);
…
// 同步获取界面参数值
QVariant var;
ito->request("param.level", var);
// 这里会同步等待主线程执行response函数完
int param = var.toInt();
……
}
MainWindow相关实现:
void MainWindow::response(QString req, QVariant& ans)
{
if (req == "param.level")
{
// 获取控件值并存放至ans引用变量中
ans.setValue(ui->levelSpin->value());
}
}
本次经验分享完毕,关于一些具体的技术细节,如QT的消息与槽机制、C++11的Lambda函数还请读者另行学习。
相关文章
- QT(3)第一个QT程序
- qt创建多层目录_Qt多工程多目录的编译案例
- QT信号槽: QT实现的观察者机制,可由信号触发槽方法。
- qt中xe运行缺少组件,Qt-c++桌面编程报错:qt.qpa.plugin: Could not find the Qt platform plugin “windows“ in ““,最终解决方案
- Qt 事件处理机制-qt源码解读
- qt+visa实现程控实例
- 【QT】Qt获取前几天/后几天的时间
- 【QT】Qt creator连接MySQL数据库 - 增删改查
- 【QT】对于Qt MSVC 2017无法编译的问题解决
- 【QT】Qt 使用MSVC2017找不到编译器的解决办法
- QT基础——QML及其相关的qt模块
- Qt音视频开发42-网络推流(视频推流/本地摄像头推流/桌面推流/网络摄像头转发推流等)
- Qt编写地图综合应用34-生成区域轮廓图
- Qt编写的项目作品12-简易视频播放器
- Qt编写安防视频监控系统26-硬件加速
- Qt编写数据可视化大屏界面电子看板1-布局方案
- qt widget设置Qt::FramelessWindowHint和Qt::WA_TranslucentBackground, 会出现一个bug: 在最小化后还原时界面停止刷新(Qt5.1解决了这个问题。但实际Qt5.7还有这个问题)
- Qt5.9 提供Qt Remote Objects,OAuth1 & OAuth2,重写了QML的GC
- QT中|Qt::Tool类型窗口自动退出消息循环问题解决(setQuitOnLastWindowClosed必须设置为false,最后一个窗口不显示的时候,程序会退出消息循环)
- Install Qt 5 on Ubuntu(使用qt-opensource-linux-x64-5.7.0.run进行安装,而且是官方的wiki)
- Qt之QPauseAnimation
- Qt之图形(简笔画-绘制漂亮的西瓜)
- VS2010使用静态编译的qt库(Qt 5)
- Qt String 与char* char int之间的转换
- 【Qt程序】基于Qt词典开发系列<十二>呼叫讲述
- qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
- QT源码之Qt信号槽机制与事件机制的联系