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()
來進行事件派發。前面的問題也都是由它派發的事件引發的。