- 本文旨在说明:QDialog::exec()、QMenu::exec()等开启的局部事件循环,易用的背后,还有很多的陷阱...
引子
Qt 是事件驱动的,基本上,每一个Qt程序我们都会通过QCoreApplication或其派生类的exec()函数来开启事件循环(QEventLoop):
int main(int argc, char**argv)
{
QApplication a(argc, argv);
return a.exec();
}
但是在同一个线程内,我们可以开启多个事件循环,比如通过:
- QDialog::exec()
- QDrag::exec()
- QMenu::exec()
- ...
这些东西都很常用,不是么?它们每一个里面都在执行这样的语句:
QEventLoop loop; //事件循环
loop.exec();
既然是同一线程内,这些显然是无法并行运行的,那么只能是嵌套运行。
如何演示?
如何用最小的例子来直观说明这个问题呢?
利用定时器来演示应该是最方便的。于是,很容易写出来这样的代码:
#include <QtCore>
class Object : public QObject
{
public:
Object() {startTimer(200); }
protected:
void timerEvent(QTimerEvent *) {
static int level = 0;
qDebug()<<"Enter: <<"++level;
QEventLoop loop; //事件循环
loop.exec();
qDebug()<<"Leave: "<<level;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object w;
return a.exec();
}
然后我们可以期待看到:
Enter: 1
Enter: 2
Enter: 3
...
但是,很让人失望,这并不会工作。因为Qt对Timer事件派发时进行了处理:
- 如果当前在处理Timer事件,新的Timer将不会被派发。
演示
我们对这个例子进行一点改进:
- 收到Timer事件后,我们立即post一个自定义事件(然后我们在对自定义事件的相应中开启局部的事件循环)。这样Timer事件的相应可以立即返回,新的Timer事件可以持续被派发。
另外,为了友好一点,使用了 QPlainTextEdit 来显示结果:
#include <QtGui>
#include <QtCore>
class Widget : public QPlainTextEdit
{
public:
Widget() {startTimer(200); }
protected:
bool event(QEvent * evt)
{
if (evt->type() == QEvent::Timer) {
qApp->postEvent(this, new QEvent(QEvent::User));
} else if (evt->type() == QEvent::User) {
static int level = 0;
level++;
this->appendPlainText(QString("Enter : %1").arg(++level));
QEventLoop loop;
loop.exec();
this->appendPlainText(QString("Leave: %1").arg(level));
}
return QPlainTextEdit::event(evt);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
有什么用?
这个例子确实没有什么,因为似乎没人会写这样的代码。
但是,当你调用
- QDialog::exec()
- QMenu::exec()
- QDrag::exec()
- ...
等函数时,实际上就启动了嵌套的事件循环,而如果不小心的话,还有遇到各种怪异的问题!
== QDialog::exec() vs QDialog::open()==在QDialog
模态对话框与事件循环以及漫谈QWidget及其派生类(四)我们解释过QDialog::exec()。它最终就是调用QEventLoop::exec()。
QDialog::exec()这个东西是这么常用,以至于我们很少考虑这个东西的不利因素。QDialog::open()尽管被官方所推荐,但是似乎很少有人用,很多人可能还不知道它的存在。
但是Qt官方blog中:
一文介绍了 exec() 可能造成的危害,并鼓励大家使用 QDialog::open()
在Qt官方的Qt Quarterly中:*QtQuarterly30
之 New Ways of Using Dialogs对QDialog::open()有详细的介绍
QDialog::open()劣势与优势
看个例子:我们通过颜色对话框选择一个颜色,
- 使用静态函数,写法很简介(内部调用exec()启动事件循环)
void Widget::onXXXClicked()
{
QColor c = QColorDialog::getColor();
}
- 对话框的常见用法,使用exec()启动事件循环,很直观
void Widget::onXXXClicked()
{
QColorDialog dlg(this);
dlg.exec();
QColor c = dlg.currentColor();
}
- 使用open()函数,比较不直观(因为是异步,需要链接一个槽)
void Widget::onXXXClicked()
{
QColorDialog *dialog = new QColorDialog;
dialog->open(this, SLOT(dialogClosed(QColor)));
}
void Widget::dialogClosed(const QColor &color)
{
QColor = color;
}
好处嘛(就摘录Andreas Aardal Hanssen的话吧):
- By using open() instead of exec(), you need to write a few more lines of code (implementing the target slot). But what you gain is very significant: complete control over execution. Now, the event loop is no longer nested/reentered,
you’re not blocking inside a magic exec() function
局部事件循环导致崩溃
Kde开发者官方blog中描述这个问题:
在某个槽函数中,我们通过QDialog::exec() 弹出一个对话框。
void ParentWidget::slotDoSomething()
{
SomeDialog dlg( this ); //分配在栈上的对话框
if (dlg.exec() == QDialog::Accepted ) {
const QString str = dlg.someUserInput();
//do something with with str
}
}
如果这时ParentWidget或者通过其他方式(比如dbus)得到通知,需要被关闭。会怎么样?
程序将崩溃:ParentWidget析构时,将会delete这个对话框,而这个对话框却在栈上。
简单模拟一下(在上面代码中加一句即可):
void ParentWidget::slotDoSomething()
{
QTimer::singleShot(1000, this, SLOT(deleteLater()));
...
这篇blog最终给出的结论是:将对话框分配到堆上,并使用QPointer来保存对话框指针。
上面的代码,大概要写成这样:
void ParentWidget::slotDoSomething()
{
QWeakPointer<SomeDialog> dlg = new SomeDialog(this);
if (dlg.data()->exec() == QDialog::Accepted ) {
const QString str = dlg.data()->someUserInput();
//do something with with str
} else if(!dlg) {
//....
}
if (!dlg) {
delete dlg.data();
}
}
感兴趣的可以去看看原文。比较起来 QDialog::open() 应该更值得考虑。
QCoreApplication::sendPostedEvents()
当程序做繁重的操作时,而又不愿意开启一个新线程时,我们都会选择调用
QCoreApplication::sendPostedEvents ()
来使得程序保持相应。这和前面提到的哪些exec()开启局部事件循环的效果其实是完全一样的。
无论是这个,还是QEventLoop::exec()最终都是调用:
QAbstractEventDispatcher::processEvents()
来进行事件派发。前面的问题也都是由它派发的事件引起的。
参考
分享到:
相关推荐
实际上a.exec()便是Qt程序进入事件消息循环, 9.1.2 图形界面应用程序的消息处理模型 回调、os的魔抓windows、linux,从用户层到 内核层,如何管理进程、线程、 Os如何处理、底层机制 特点: 基于操作系统...
Qt事件机制浅析
qt_eventdispatcher_libevent 是基于 Libevent 的 Qt 事件调度器 特点 非常快速 ... 支持Qt4和Qt5 ... 不需要Qt的私有头文件 ... 通过Qt4 和 Qt5 的事件调度,事件循环,定时器和socket通知测试
QT事件系统
这是一个Qt事件处理系统,目前只实现了鼠标事件的处理,后续将添加键盘事件,绘图事件,定时事件等等的处理过程。程序比较简单,适合初学者。程序开发环境:win7+qt5.7.0
QT实现的可移动放大缩小的大小嵌套窗体,里面是完整的测试项目,真正实现的是PatchWindow类
Qt自定义事件,Qt线程应用。
基于QT的系统
QT分辨率修改事件.7zQT分辨率修改事件.7zQT分辨率修改事件.7z
在嵌入式qt项目中,有时并不...在qt项目中,可以通过重写事件过滤器来实现屏幕操作的检测,加上定时器的时间控制,可以实现指定时间内没有屏幕操作,给应用程序发送一个信号;通过这个方式,也可以用于屏保应用的实现
qt_eventdispatcher_libev 是基于 libev 的 Qt 事件调度器。 特点 非常快速 ... 支持 Qt4 和 Qt5 ... 不需要 Qt 的私有头文件 ... 通过了 Qt4 和 Qt5 的事件调度,事件循环,定时器和 socket 通知测试
该实例程序使用Qt进行,windows的键盘后台监听,即使Qt桌面程序失去焦点在后台运行,也会捕捉到按键事件 这个程序使用windows的钩子(hook)实现的,Qt官方没有相关的实现 关于编译,我用的是Qt5.9.7 MinGW32位,我...
Qt输入法事件(QInputMethodEvent)演示小程序。 程序功能:在失去QTextEdit控件失去焦点的时候放弃正在输入的内容。 解决搜狗(或QQ)输入法等输入法失去焦点时会将字幕输入到输入框问题。 运用了Qt的事件监听...
一个用qt实现的图片自动循环播放的小程序。 对刚刚学qt的新生有用!
自己看的一本QT学习资料,感觉内容比较好,有利于大家学习,内容主要分析QT事件问题。内容从浅到深,值得一看。
Qt 信号在多层次对象间传递 多层嵌套类对象之间信号传递,可能是五层,或多层,子对象要发信号给第一层 ; QT信号量传递 QT信号量多层传递,QT信号量任意层传递,Qt信号量多层次对象间传递 博文:...
自己写 的qt在label上循环显示一组图片,语句提供多种分辨率的图片
这是Qt for Android的测试工程,主要测试QT的触摸手势事件
关于Qt事件处理和定时器的详细内容,参考博客:http://blog.csdn.net/rl529014/article/details/53440211
通过事件过滤器,让一个对象对监听另一个对象的事件,进行后续响应,这里实现监听鼠标移入移出时产出的事件