QObject三大核心功能——事件处理

QObject三大核心功能:信号与槽,内存管理,事件处理

总览

1、谁来产生事件: 最容易想到的是我们的输入设备,比如键盘、鼠标产生的keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent),这些事件来自于底层的操作系统,它们以异步的形式通知Qt事件处理系统,后文会仔细道来。当然Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent. 用户的程序可还以自己定制事件。
2、谁来接受和处理事件:答案是QObject。在Qt的内省机制剖析一文已经介绍QObject 类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责(内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。
3、谁来负责分发事件:对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类Receiver。对于Qt GUI程序,由QApplication来负责。
4、每个线程可以有它的事件循环。
初始线程开始它的事件循环需使用QCoreApplication::exec(),别的线程开始它的事件循环需要用QThread::exec(),像QCoreApplication一样,QThreadr提供了exit(int)函数,一个quit() slot。

事件处理逻辑

自定义事件

自定义事件QEvent

5种级别的事件过滤

1、重载特定事件函数。
比如:mousePressEvent(),keyPressEvent(), paintEvent() 。
2、重载QObject::event()。
我们可以在事件被特定的事件处理函数处理之前(像keyPressEvent())处理它。
这一般用在Qt没有提供该事件的处理函数时。也就是,我们增加新的事件时。
3、安装事件过滤器。
比如用 objA 过滤 objB 的事件,即事件到达 objB 之前,先交由 objA 处理。


安装事件过滤器
    调用objB->installEventFilter(objA)
    重载objA::eventFilter()

4、在QApplication上安装事件过滤器。
一旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过QApplication这个eventFilter()。
5、继承QApplication类,并重载notify()函数。
Qt使用notify()来分发事件。要想在任何事件处理器捕获事件之前捕获事件,唯一的方法就是重新实现QApplication的notify()方法。这是一个最原始的检查事件不响应的终极办法。


事件处理优先级

事件循环模型

事件的产生、处理流程

事件的产生

事件的两种来源:

1. 一种是系统产生的。Spontaneous events

通常是window system把从系统得到的消息,比如鼠标按键,键盘按键等,放入系统的消息队列中,Qt事件循环的时候读取这些事件,转化为QEvent,再依次处理。
系统底层事件是通过抽象事件分发器QAbstractEventDispatcher整合进Qt的事件循环的。QAbstractEventDispatcher接受窗口系统以及其他源中的事件。它对事件的传递提供了一种精细控制的能力如下:

QAbstractEventDispatcher
    *QEventDispatcherUNIX       // 默认的glib不可用时,就用这个
        QEventDispatcherX11
        QEventDispatcherQWS
        QEventDispatcherQPA
    *QEventDispatcherGlib       // 使用glib事件循环,有助于和Gtk的集成
        QGuiEventDispatcherGlib
        QWSEventDispatcherGlib
    *QEventDispatcherWin32      // Qt 创建一个带回调函数的隐藏窗口来处理事件
        QGuiEventDispatcherWin32
    *QEventDispatcherMac
    *...
2. 一种是由Qt应用程序程序自身产生的。

程序产生事件有两种方式:
一种方式是同步的,调用QApplication::sendEvent()bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)函数。 这时候事件不会放入队列,而是直接被派发和处理,QWidget::repaint()函数用的就是这种方式。
发送按键"X"的事件到 mainWin 窗口

QKeyEvent event(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier, "X", 0);
QApplication::sendEvent(mainWin, &event);

bool mainWinQObject::event(QEvent *e)
{
    switch (e->type()) {
    ......
    case QEvent::KeyPress:
        // 处理事件消息
        break;
    }
}

一种是异步的,调用QApplication::postEvent(),事件会进入到事件队列中,等待receiver接收。 例如QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数,new出来一个paintEvent,调用QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理。
发送按键"X"的事件到 mainWin 窗口:

QApplication::postEvent(mainWin, 
    new QKeyEvent(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier, "X", 0));

这会将该事件放入Qt自己的事件队列中,事件循环QEventLoop空闲时会判断该队列是否为空。最终使用 sendEvent() 依次派发事件队列中的这些事件。


注意一:postEvent的事件是异步的,需要在堆上申请空间。
注意二:每一个线程有一个事件队列。

事件的调度

两种调度方式,一种是同步的sendEvent,一种是异步postEvent
调用QApplication::sendEvent的时候,消息会立即被处理,是同步的。 实际上QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节。
Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环。 该循环可以简化的描述为如下的代码:

while ( !app_exit_loop )
{
        while( !postedEvents ) { processPostedEvents() }  // 先处理Qt事件队列中的事件
        while( !qwsEvnts ){ qwsProcessEvents();   }       // 处理系统消息队列中的消息
        while( !postedEvents ) { processPostedEvents() }
}

先处理Qt事件队列中的事件,直至为空。 再处理系统消息队列中的消息,直至为空,在处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理。

事件的派发和处理

事件的派发是从bool QCoreApplication::notify(QObject *receiver, QEvent *event)开始的,因为QAppliction也是继承自QObject,所以先检查QAppliation对象,如果有事件过滤器安装在qApp上,先调用这些事件过滤器。 接下来QApplication::notify()会过滤或合并一些事件(比如失效widget的鼠标事件会被过滤掉,而同一区域重复的绘图事件会被合并)。 之后,会执行receiver->event(event),事件被送到reciver::event() 处理。
当QApplication开始析构时,QApplication::notify(),不再派发消息。
reciver::event()中,先检查有无事件过滤器安装在reciever上。 若有,则调用之。 接下来,根据QEvent的类型,调用相应的特定事件处理函数。 一些常见的事件都有特定事件处理函数,比如mousePressEvent(),focusOutEvent(),resizeEvent(),paintEvent(),resizeEvent()等等。

事件的转发

子类未处理的事件会一层一层传递给父类进行处理,直到最顶层,若无处理,QEvent将停止转发。
参考文章:QT事件传递与事件过滤器

事件过滤器实现

事件过滤器void QObject::installEventFilter(QObject *obj)是这样实现的:
在所有Qt对象的基类: QObject中有一个类型为QObjectList的成员变量,名字为eventFilters,当某个QObject (qobjA)给另一个QObject (qobjB)安装了事件过滤器(objB->installEventFilter(objA))之后,qobjB会把qobjA的指针保存在eventFilters中。 在qobjB处理事件之前,会先去检查eventFilters列表,如果非空,就先调用列表中对象的eventFilter()函数。
事件过滤器函数eventFilter(),如果返回true,则表示该事件已经被处理完毕,Qt将直接返回,进行下一事件的处理; 如果返回false,事件将接着被送往剩下的事件过滤器或是目标对象进行处理。
注意:事件过滤器限定被监视对象与监视对象生存在同一线程中

多线程事件循环

  1. 线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI类(如,QTimer,QTcpSocket,QProcess)。
  2. 可以把任何线程的signals连接到特定线程的slots,也就是说信号-槽机制是可以跨线程使用的。
  3. 对于在QApplication之前创建的对象,QObject::thread()返回0,这意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。
  4. 可以用QObject::moveToThread(QThread *targetThread)来改变QObject和QObject孩子们的线程亲缘关系,假如QObject对象有父亲,它不能移动这种关系。
  5. 在另一个线程(而不是创建它的那个线程)中delete QObject对象是不安全的。除非你可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。
  6. 假如没有事件循环运行,事件不会分发给对象。举例来说,假如你在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号,对deleteLater()也不会工作。(这同样适用于主线程)。你可以手工使用线程安全的函数QCoreApplication::postEvent()在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。
  7. 事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。类似地,QCoreApplication::sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。
  8. QObject和所有它的子类是非线程安全的。这包括整个的事件投递系统。需要牢记的是,当你正从别的线程中访问对象时,事件循环可以向你的QObject子类投递事件。假如你调用一个不生存在当前线程中的QObject子类的函数时,你必须用mutex来保护QObject子类的内部数据,否则会遭遇灾难或非预期结果。
  9. QThread对象(像其它的对象一样)生存在创建它的那个线程中,即父线程中,而不是当QThread::run()被调用时创建的那个线程。一般来讲,在你的QThread子类中提供slots是不安全的,除非你用mutex保护了你的成员变量。另一方面,你可以安全的从QThread::run()的实现中发射信号,因为信号发射是线程安全的。
    Qt 多线程之逐线程事件循环 下篇

事件源码分析

以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理。
1、创建一个QApplication和MyWidget,并注册一个事件过滤器

#include      
#include "widget.h"     
//Section 1     
int main(int argc, char *argv[])     
{     
    QApplication app(argc, argv);     
    MyWidget window;        // MyWidget继承自QWidget   
    OtherWidget other;      // OtherWidget继承自QWidget   
    window.installEventFilter(&other);
    window.show();     
    return app.exec(); // 进入Qpplication事件循环,见section 2     
}    

事件源码流程图解

循环调用QEventLoop::processEvents

事件循环

调用与平台相关的QAbstractEventDispatcher子类的processEvents接口

实现Windows消息循环

不断从Windows系统中PeekMessage,然后分发给Windows系统,再由Windows系统找到对应的目标窗口,将消息分发到目标线程的消息回调中WndProc。


调用与平台相关的QAbstractEventDispatcher子类的processEvents接口

Windows消息分发

执行到QT注册的消息回调QtWndProc

系统消息转换为QEvent

消息层层传递到最后的MyWidget::event

QEvent传递过程

最后总体简图-Windows平台为例

事件循环总体简图

2、QApplication::exec()

int QApplication::exec()     
{     
   //skip codes     
   //简单的交给QCoreApplication来处理事件循环=〉section 3     
   return QCoreApplication::exec();     
}     

3、QCoreApplication::exec()


int QCoreApplication::exec()     
{     
    //得到当前Thread数据     
    QThreadData *threadData = self->d_func()->threadData;     
    if (threadData != QThreadData::current()) {     
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());     
        return -1;     
    }     
    //检查event loop是否已经创建     
    if (!threadData->eventLoops.isEmpty()) {     
        qWarning("QCoreApplication::exec: The event loop is already running");     
        return -1;     
    }     
    ...     
    QEventLoop eventLoop;     
    self->d_func()->in_exec = true;     
    self->d_func()->aboutToQuitEmitted = false;     
    //委任QEventLoop 处理事件队列循环 ==> Section 4     
    int returnCode = eventLoop.exec();     
    ....     
    }     
    return returnCode;     
}  

4、QEventLoop::exec
只要QEventloop未退出,则循环调用processEvents派发事件。

int QEventLoop::exec(ProcessEventsFlags flags)     
{     
   //这里的实现代码不少,最为重要的是以下几行     
   Q_D(QEventLoop); // 访问QEventloop私有类实例d     
   try {     
        //只要没有遇见exit,循环派发事件     
        while (!d->exit)     
            processEvents(flags | WaitForMoreEvents | EventLoopExec);     
    } catch (...) {}     
} 

5、QEventLoop::processEvents
将事件派发给与平台相关的QAbstractEventDispatcher子类

bool QEventLoop::processEvents(ProcessEventsFlags flags)     
{     
    Q_D(QEventLoop);   // 访问QEventloop私有类实例d   
    if (!d->threadData->eventDispatcher)     
        return false;     
    if (flags & DeferredDeletion)     
        QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);     
    // 将事件派发给与平台相关的QAbstractEventDispatcher子类 =>Section 6     
    return d->threadData->eventDispatcher->processEvents(flags);     
}  

6、具体与平台相关的QAbstractEventDispatcher子类的processEvents

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)     
{     
    // 拿到QEventDispatcherWin32私有类QEventDispatcherWin32Private实例d
    Q_D(QEventDispatcherWin32);
    if (!d->internalHwnd)     
        createInternalHwnd();     
    d->interrupt = false;     
    emit awake();     
    bool canWait;     
    bool retVal = false;     
    bool seenWM_QT_SENDPOSTEDEVENTS = false;     
    bool needWM_QT_SENDPOSTEDEVENTS = false;     
    do {     
        DWORD waitRet = 0;     
        HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];     
        QVarLengthArray processedTimers;     
        // 如下循环实现了Windows的消息循环机制        
        /*
        while(PeekMessage(&Msg, NULL, 0, 0) > 0) 
        { 
            TranslateMessage(&Msg);    //转换
            DispatchMessage(&Msg);     //分发
        }
        */
        while (!d->interrupt) {     
            DWORD nCount = d->winEventNotifierList.count();     
            Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);     
            MSG msg;     
            bool haveMessage;     
            if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {     
                // 处理排队的用户输入事件
                haveMessage = true;     
                // 从处理用户输入队列中取出一条事件     
                msg = d->queuedUserInputEvents.takeFirst();     
            } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {     
                // 从处理socket队列中取出一条事件     
                haveMessage = true;     
                msg = d->queuedSocketEvents.takeFirst();     
            } else {     
                // 从系统消息队列中取消息。设置了PM_REMOVE,消息被取出后从消息队列中删除
                // PeekMessage从消息队列中取不到消息,直接返回,线程不会被阻塞
                // GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程
                haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
                if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)     
                    && ((msg.message >= WM_KEYFIRST     
                         && msg.message <= WM_KEYLAST)     
                        || (msg.message >= WM_MOUSEFIRST     
                            && msg.message <= WM_MOUSELAST)     
                        || msg.message == WM_MOUSEWHEEL     
                        || msg.message == WM_MOUSEHWHEEL     
                        || msg.message == WM_TOUCH     
#ifndef QT_NO_GESTURES     
                        || msg.message == WM_GESTURE     
                        || msg.message == WM_GESTURENOTIFY     
#endif     
                        || msg.message == WM_CLOSE)) {                         
                    haveMessage = false;     
                    // 用户输入事件入队列,待以后处理
                    d->queuedUserInputEvents.append(msg);     
                }
                if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)     
                    && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {     
                    haveMessage = false;     
                    // socket 事件入队列,待以后处理 
                    d->queuedSocketEvents.append(msg);     
                }     
            }     
            ....     
            if (!filterEvent(&msg)) {     
                // 转换:将虚拟键值信息转换为字符信息
                TranslateMessage(&msg);    
                // 分发
                // 将事件打包成message调用Windows API派发出去     
                // 分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数 => section 7 
                // DispatchMessage()函数将消息再给windows系统,由windows系统找到目标窗口并分发给该窗口,
                // 调用消息对应的窗口过程函数,即窗口的WinPro函数,让WinPro函数处理
                // qt的窗口处理程序为QtWndProc
                DispatchMessage(&msg);     
            }
        }     
    } while (canWait);     
      ...     
    return retVal;     
}     

7、窗口过程函数 QtWndProc
Windows窗口回调函数定义在QTDIR\src\gui\kernel\qapplication_win.cpp
关键:通过QApplication::widgetAt(curPos.x, curPos.y),取得鼠标点击的坐标所在的QWidget指针,然后再调用QWidget指针所指对象的translateMouseEvent,将消息分发到对应的QWidget中。

QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)      
{     
   ...     
   // 检查message是否属于Qt可转义的鼠标事件     
   if (qt_is_translatable_mouse_event(message)) {     
        if (QApplication::activePopupWidget() != 0) {                 
            POINT curPos = msg.pt;     
            // 取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例     
            QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);     
            if (w)     
                widget = (QETWidget*)w;     
        }     
        if (!qt_tabletChokeMouse) {     
            // 对,就在这里。Windows的回调函数将鼠标事件分发给了Qt Widget
           // 即我们在main函数里创建的widget实例      
            result = widget->translateMouseEvent(msg);            
     ...     
}  

8、translateMouseEvent
该函数所在与Windows平台相关,主要职责就是把Windows格式打包的鼠标事件解包、翻译成QApplication、QWidget可识别的QMouseEvent事件。

bool QETWidget::translateMouseEvent(const MSG &msg)  
{  
     //.. 这里很长的代码给以忽略    
      // 让我们看一下sendMouseEvent的声明  
     // widget是事件的接受者; e是封装好的QMouseEvent  
     // ==> Section 2-3  
     res = QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down, qt_last_mouse_receiver);  
}  

9、sendMouseEvent
至此与平台相关代码处理完毕。 根据事件类型调用sendEvent或者sendSpontaneousEvent将事件同步直接发送给接受者。

bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,  
                                         QWidget *alienWidget, QWidget *nativeWidget,  
                                         QWidget **buttonDown, QPointer &lastMouseReceiver,  
                                         bool spontaneous)  
{   
    // MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。 
    // sendSpontaneousEvent()与sendEvent的代码实现几乎相同,除了将QEvent的属性标记为spontaneous不同。 
    // spontaneous事件是由应用程序之外产生的事件,比如一个系统事件。 
    // 显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是spontaneous事件  
    if (spontaneous)  
        result = QApplication::sendSpontaneousEvent(receiver, event);  ==〉Section 2-4  
    else  
        result = QApplication::sendEvent(receiver, event);  
} 

10、sendSpontaneousEvent

// Section 2-4 C:\Qt\4.7.1-Vs\src\corelib\kernel\qcoreapplication.h  
inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)     
{      
    //将event标记为自发事件     
    //进一步调用 2-5 QCoreApplication::notifyInternal     
    if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false;      
} 

11、notifyInternal

bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)     
{              
    // 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持     
    ...     
    // 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,
    // 也就是说receiver->d_func()->threadData应该等于QThreadData::current()。
    // 注意,跨线程的事件需要借助Event Loop来派发     
    QObjectPrivate *d = receiver->d_func();     
    QThreadData *threadData = d->threadData;     
    ++threadData->loopLevel;     
    bool returnValue;     
    QT_TRY {     
        // 哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6     
        returnValue = notify(receiver, event);     
    } QT_CATCH (...) {     
        --threadData->loopLevel;     
        QT_RETHROW;     
    }     
} 

12、QApplication::notify
QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:任何线程的任何对象的所有事件在发送时都会调用notify函数。

bool QApplication::notify(QObject *receiver, QEvent *e)     
{     
   //代码很长,最主要的是一个大大的Switch,Case     
   ..     
   switch ( e->type())     
   {     
    ...     
    case QEvent::MouseButtonPress:     
    case QEvent::MouseButtonRelease:     
    case QEvent::MouseButtonDblClick:     
    case QEvent::MouseMove:     
     ...     
        // 让自己私有类(d是私有类的句柄)来进一步处理 ==> Section 2-7     
        res = d->notify_helper(w, w == receiver ? mouse : &me);     
        e->spont = false;     
        break;     
    }     
    ...     
}     

13、notify_helper
先经过事件过滤器处理,优先让OtherWidget处理感兴趣的消息,再将事件传递给接收对象MyWidget的event函数。

bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)     
{     
    ...     
    // 向事件过滤器即前面的OtherWidget发送该事件,优先让OtherWidget处理感兴趣的消息
    // 这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。     
    // 如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤,
    // 允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。
    // 如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。     
    
    if (sendThroughObjectEventFilters(receiver, e))     
        // 事件过滤器返回true后,事件将不会传递给目标对象receiver
        return true;     
    // 递交事件给目标对象receiver  => Section 2-8     
    bool consumed = receiver->event(e);     
    e->spont = false;     
}  

14、QWidget::event或者children's_QWidget::event
QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类

bool QWidget::event(QEvent *event)     
{     
    ...     
    switch(event->type()) {     
    case QEvent::MouseButtonPress:     
        // Don't reset input context here. Whether reset or not is     
        // a responsibility of input method. reset() will be     
        // called by mouseHandler() of input method if necessary     
        // via mousePressEvent() of text widgets.     
#if 0     
        resetInputContext();     
#endif     
        // mousePressEvent是虚函数,QWidget的子类可以通过重载重新定义mousePress事件的行为     
        mousePressEvent((QMouseEvent*)event);     
        break;        
}  

参考1:Qt 事件处理机制 (上篇)
参考2:Qt 事件处理机制 (下篇)

你可能感兴趣的:(QObject三大核心功能——事件处理)