Qt 是事件驱动的,基本上,每个Qt程序咱们都会经过QCoreApplication或其派生类的exec()函数来开启事件循环(QEventLoop):node
int main(int argc, char**argv) { QApplication a(argc, argv); return a.exec(); }
可是在同一个线程内,咱们能够开启多个事件循环,好比经过:app
这些东西都很经常使用,不是么?它们每个里面都在执行这样的语句:异步
QEventLoop loop; //事件循环 loop.exec();
既然是同一线程内,这些显然是没法并行运行的,那么只能是嵌套运行。ide
如何用最小的例子来直观说明这个问题呢?函数
利用定时器来演示应该是最方便的。因而,很容易写出来这样的代码:oop
#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(); }
而后咱们能够期待看到:post
Enter: 1 Enter: 2 Enter: 3 ...
可是,很让人失望,这并不会工做。由于Qt对Timer事件派发时进行了处理:ui
咱们对这个例子进行一点改进:this
另外,为了友好一点,使用了 QPlainTextEdit 来显示结果:spa
#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() 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()有详细的介绍
看个例子:咱们经过颜色对话框选择一个颜色,
void Widget::onXXXClicked() { QColor c = QColorDialog::getColor(); }
void Widget::onXXXClicked() { QColorDialog dlg(this); dlg.exec(); QColor c = dlg.currentColor(); }
void Widget::onXXXClicked() { QColorDialog *dialog = new QColorDialog; dialog->open(this, SLOT(dialogClosed(QColor))); } void Widget::dialogClosed(const QColor &color) { QColor = color; }
好处嘛(就摘录Andreas Aardal Hanssen的话吧):
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 ()
来使得程序保持相应。这和前面提到的哪些exec()开启局部事件循环的效果实际上是彻底同样的。
不管是这个,仍是QEventLoop::exec()最终都是调用:
QAbstractEventDispatcher::processEvents()
来进行事件派发。前面的问题也都是由它派发的事件引发的。