标签:
最近想要了解一下Qt线程,但在对相关资料师都是从线程和事件循环开始将。对于事件循环是个相对很抽象的概念,研究了很久也很难在脑子里建立起一个具体的模型。今天在这里对这几天研究内容做总结,为以后做参考。
一、事件
网上很多资料都将的很清楚。在这里重点是明确一下一个事件发出后对于该事件的一个调用顺序。在这里我们在QApplication安装一个过滤器,在一个一个Qwidget安装一个过滤器 代码如下:
class Label : public QWidget { public: Label() { installEventFilter(this); } bool eventFilter(QObject *watched, QEvent *event) { if (watched == this) { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "eventFilter"; } } return false; } protected: void mousePressEvent(QMouseEvent *) { qDebug() << "mousePressEvent"; } bool event(QEvent *e) { if (e->type() == QEvent::MouseButtonPress) { qDebug() << "event"; } return QWidget::event(e); } };
class EventFilter : public QObject { public: EventFilter(QObject *watched, QObject *parent = 0) : QObject(parent), m_watched(watched) { } bool eventFilter(QObject *watched, QEvent *event) { if (watched == m_watched) { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "QApplication::eventFilter"; } } return false; } private: QObject *m_watched; };
int main(int argc, char *argv[]) { QApplication app(argc, argv); Label label; app.installEventFilter(new EventFilter(&label, &label)); label.show(); return app.exec(); }实验结果:
QApplication::eventFilter eventFilter event mousePressEvent看到事件首先被App上的过滤器调用,然后再被label上的过滤器调用,然后才会传递给label的事件分发,最后才调用其事件处理函数。其中,如果事件分发中没有对应的事件 会向上传递给他的父对象,父对象也没有就继续向上传递,直到最顶层才结束。
二、事件循环
研究事件循环就不得不提到Exec() 函数。我们最能想到的该函数调用便是QApplocation::Exec();在刚入手Qt书本就告诉我们主函数调用该函数可以开启一个程序的事件循环。其实处了该类还有很多类都有该函数。例如:
QDialog::exec() QThread::exec() QDrag::exec() QMenu::exec() ...那么这个函数在不同类中是否一样呐。本文对事件循环的认识就是通过剖析该函数来了解。
先看一下QApplication::Exec() 函数的实现:
int QCoreApplication::exec() { ... QEventLoop eventLoop; int returnCode = eventLoop.exec(); ... return returnCode; }可以看到该函数实例化一个事件循环类 并调用该类的exec()函数。可见事件循环主要是由QEventloop来负责完成的。
int QDialog::exec() { Q_D(QDialog); ... setAttribute(Qt::WA_ShowModal, true); ... show(); ... QEventLoop eventLoop; (void) eventLoop.exec(QEventLoop::DialogExec); ... }该函数内容有两大部分:1、设置对话框为模态 show对话框 2、开启一个本地事件循环
题外话: 可以看到对话框的模态性和事件循环没有必然联系,实现模态对话框 我们大可以使用dig.setAttribute(Qt::WA_ShowModal,true), dig.show() 这两句来实现。
以此类推 QThread::Exec() 、QMenu::Exec() 一定都有是实现一个QEventloop类来开启事件循环。
下面我们关心一下QEventloop类是如何实现事件循环的:
int QEventLoop::exec(ProcessEventsFlags flags) { Q_D(QEventLoop); ... while (!d->exit) processEvents(flags | WaitForMoreEvents | EventLoopExec); ... return d->returnCode; }这是一个无线循环函数(除非触发了exit),循环体内是一个processEvents函数。查Qt文档看一下该函数相关说明:
Processes pending events that match flags until there are no more events to process. Returns true if pending events were handled; otherwise returns false. This function is especially useful if you have a long running operation and want to show its progress without allowing user input; i.e. by using the ExcludeUserInputEvents flag.可以看到该函数会派发未处理的事件,如果所以待处理事件都处理了返回true 并进入事件等待状态。否则返回false.
继续向下分析:
bool QEventLoop::processEvents(ProcessEventsFlags flags) { Q_D(QEventLoop); if (!d->threadData->eventDispatcher) return false; if (flags & DeferredDeletion) QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); return d->threadData->eventDispatcher->processEvents(flags);//调用不同平台下的事件分派器来处理事件。 }由该函数也可以看到processEvents返回值的条件 最后会调用Qt中和平台相关的函数:
bool QGuiEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { if (!QEventDispatcherWin32::processEvents(flags)) return false; if (configRequests) // any pending configs? qWinProcessConfigRequests(); return true; }
由此可知程序在执行到Exec函数时将会进入一个无限循环等待或分发事件的状态 而不会继续向下执行。在参考文献1中对这个平台相关函数有更深入分析。这里就不继续分析更深的地方了。
而派发的事件是如何传递被最后处理的呢?我在Qt文档的QEvent类中看到了这样的描述:
Qt's main event loop (QCoreApplication::exec())fetches native window system events from the event queue,translates them into QEvents, and sends the translated events to QObjects.看到主循环负责从事件队列(待处理事件)中取出本地窗口事件,并将它翻译成QEvent事件,在派发到具体的QObject中(QObject->event());
从前面分析我们可以认为这一系列任务肯定是有QEventloop::Exec()中的peocessEvents完成。由该函数从队列获取事件并向外分发。
事件到了QObject->event()函数中后,由该函数分发给具体的事件处理函数,如果event()函数中没有该事件 则向上路由到父对象的event函数 直到该事件被处理或者传递到顶层对象结束。
现在如果一个事件(比如:QEvent::MouseButtonPress)的事件处理函数执行的是一个长时间任务(例如:批量读取文件)。那么在处理完成之前 这个事件就会一直处于待处理状态。此时while循环就不会继续循环下去 我们称之阻塞事件循环。 循环阻塞的就是processEvent不在分发事件。
解决该问题方法有三种(参考文献2):这里我们直说和事件循环有关的两种就解决方法;
1、如果长时间任务是一个回调函数或循环体 就可以在函数内部使用qApp->processEvents; 强制分发事件。该方法问题:可能导致递归。
解决:qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
2、自己实例化一个QEventloop类 开启一个本地事件循环。 一样的问题:也是可能导致递归
参考文献:
标签:
原文地址:http://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/51321147