码迷,mamicode.com
首页 > 其他好文 > 详细

Qt:事件和事件循环

时间:2016-05-06 15:26:55      阅读:432      评论:0      收藏:0      [点我收藏+]

标签:

      最近想要了解一下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来负责完成的。
再来看一下QDialog::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类 开启一个本地事件循环。  一样的问题:也是可能导致递归

参考文献:

剖析Qt事件机制原理

事件循环

 事件循环嵌套


Qt:事件和事件循环

标签:

原文地址:http://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/51321147

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!