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

QObject三大核心功能:信號與槽涩蜘,內存管理,事件處理

總覽

1熏纯、誰來產生事件: 最容易想到的是我們的輸入設備同诫,比如鍵盤、鼠標產生的keyPressEvent豆巨,keyReleaseEvent剩辟,mousePressEvent掐场,mouseReleaseEvent事件(他們被封裝成QMouseEvent和QKeyEvent)往扔,這些事件來自于底層的操作系統(tǒng),它們以異步的形式通知Qt事件處理系統(tǒng)熊户,后文會仔細道來萍膛。當然Qt自己也會產生很多事件,比如QObject::startTimer()會觸發(fā)QTimerEvent. 用戶的程序可還以自己定制事件嚷堡。
2蝗罗、誰來接受和處理事件:答案是QObject。在Qt的內省機制剖析一文已經介紹QObject 類是整個Qt對象模型的心臟蝌戒,事件處理機制是QObject三大職責(內存管理串塑、內省(intropection)與事件處理制)之一。任何一個想要接受并處理事件的對象均須繼承自QObject北苟,可以選擇重載QObject::event()函數(shù)或事件的處理權轉給父類桩匪。
3、誰來負責分發(fā)事件:對于non-GUI的Qt程序友鼻,是由QCoreApplication負責將QEvent分發(fā)給QObject的子類Receiver傻昙。對于Qt GUI程序,由QApplication來負責彩扔。
4妆档、每個線程可以有它的事件循環(huán)。
初始線程開始它的事件循環(huán)需使用QCoreApplication::exec()虫碉,別的線程開始它的事件循環(huán)需要用QThread::exec()贾惦,像QCoreApplication一樣,QThreadr提供了exit(int)函數(shù),一個quit() slot纤虽。

事件處理邏輯

自定義事件

自定義事件QEvent

5種級別的事件過濾

1乳绕、重載特定事件函數(shù)。
比如:mousePressEvent()逼纸,keyPressEvent()洋措, paintEvent() 。
2杰刽、重載QObject::event()菠发。
我們可以在事件被特定的事件處理函數(shù)處理之前(像keyPressEvent())處理它。
這一般用在Qt沒有提供該事件的處理函數(shù)時贺嫂。也就是滓鸠,我們增加新的事件時。
3第喳、安裝事件過濾器糜俗。
比如用 objA 過濾 objB 的事件,即事件到達 objB 之前曲饱,先交由 objA 處理悠抹。


安裝事件過濾器
    調用objB->installEventFilter(objA)
    重載objA::eventFilter()

4、在QApplication上安裝事件過濾器扩淀。
一旦我們給qApp(每個程序中唯一的QApplication對象)裝上過濾器楔敌,那么所有的事件在發(fā)往任何其他的過濾器時,都要先經過QApplication這個eventFilter()驻谆。
5卵凑、繼承QApplication類,并重載notify()函數(shù)胜臊。
Qt使用notify()來分發(fā)事件勺卢。要想在任何事件處理器捕獲事件之前捕獲事件,唯一的方法就是重新實現(xiàn)QApplication的notify()方法象对。這是一個最原始的檢查事件不響應的終極辦法黑忱。


事件處理優(yōu)先級

事件循環(huán)模型

事件的產生、處理流程

事件的產生

事件的兩種來源:

1. 一種是系統(tǒng)產生的织盼。Spontaneous events

通常是window system把從系統(tǒng)得到的消息杨何,比如鼠標按鍵,鍵盤按鍵等沥邻,放入系統(tǒng)的消息隊列中危虱,Qt事件循環(huán)的時候讀取這些事件,轉化為QEvent唐全,再依次處理埃跷。
系統(tǒng)底層事件是通過抽象事件分發(fā)器QAbstractEventDispatcher整合進Qt的事件循環(huán)的蕊玷。QAbstractEventDispatcher接受窗口系統(tǒng)以及其他源中的事件。它對事件的傳遞提供了一種精細控制的能力如下:

QAbstractEventDispatcher
    *QEventDispatcherUNIX       // 默認的glib不可用時弥雹,就用這個
        QEventDispatcherX11
        QEventDispatcherQWS
        QEventDispatcherQPA
    *QEventDispatcherGlib       // 使用glib事件循環(huán)垃帅,有助于和Gtk的集成
        QGuiEventDispatcherGlib
        QWSEventDispatcherGlib
    *QEventDispatcherWin32      // Qt 創(chuàng)建一個帶回調函數(shù)的隱藏窗口來處理事件
        QGuiEventDispatcherWin32
    *QEventDispatcherMac
    *...
2. 一種是由Qt應用程序程序自身產生的。

程序產生事件有兩種方式:
一種方式是同步的剪勿,調用QApplication::sendEvent()贸诚,bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)函數(shù)。 這時候事件不會放入隊列厕吉,而是直接被派發(fā)和處理酱固,QWidget::repaint()函數(shù)用的就是這種方式。
發(fā)送按鍵"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()函數(shù)班眯,當需要重新繪制屏幕時,程序調用update()函數(shù)烁巫,new出來一個paintEvent署隘,調用QApplication::postEvent(),將其放入Qt的消息隊列中程拭,等待依次被處理宝惰。
發(fā)送按鍵"X"的事件到 mainWin 窗口:

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

這會將該事件放入Qt自己的事件隊列中蹦锋,事件循環(huán)QEventLoop空閑時會判斷該隊列是否為空恤左。最終使用 sendEvent() 依次派發(fā)事件隊列中的這些事件计呈。


注意一:postEvent的事件是異步的逢净,需要在堆上申請空間脯燃。
注意二:每一個線程有一個事件隊列劲腿。

事件的調度

兩種調度方式俏险,一種是同步的sendEvent肴楷,一種是異步postEvent水由。
調用QApplication::sendEvent的時候,消息會立即被處理赛蔫,是同步的砂客。 實際上QApplication::sendEvent()是通過調用QApplication::notify(),直接進入了事件的派發(fā)和處理環(huán)節(jié)呵恢。
Qt的事件循環(huán)是異步的鞠值,當調用QApplication::exec()時,就進入了事件循環(huán)渗钉。 該循環(huán)可以簡化的描述為如下的代碼:

while ( !app_exit_loop )
{
        while( !postedEvents ) { processPostedEvents() }  // 先處理Qt事件隊列中的事件
        while( !qwsEvnts ){ qwsProcessEvents();   }       // 處理系統(tǒng)消息隊列中的消息
        while( !postedEvents ) { processPostedEvents() }
}

先處理Qt事件隊列中的事件彤恶,直至為空钞钙。 再處理系統(tǒng)消息隊列中的消息,直至為空声离,在處理系統(tǒng)消息的時候會產生新的Qt事件芒炼,需要對其再次進行處理。

事件的派發(fā)和處理

事件的派發(fā)是從bool QCoreApplication::notify(QObject *receiver, QEvent *event)開始的术徊,因為QAppliction也是繼承自QObject本刽,所以先檢查QAppliation對象,如果有事件過濾器安裝在qApp上赠涮,先調用這些事件過濾器盅安。 接下來QApplication::notify()會過濾或合并一些事件(比如失效widget的鼠標事件會被過濾掉,而同一區(qū)域重復的繪圖事件會被合并)世囊。 之后别瞭,會執(zhí)行receiver->event(event),事件被送到reciver::event() 處理株憾。
當QApplication開始析構時蝙寨,QApplication::notify(),不再派發(fā)消息嗤瞎。
reciver::event()中墙歪,先檢查有無事件過濾器安裝在reciever上。 若有贝奇,則調用之虹菲。 接下來,根據QEvent的類型掉瞳,調用相應的特定事件處理函數(shù)毕源。 一些常見的事件都有特定事件處理函數(shù),比如mousePressEvent()陕习,focusOutEvent()霎褐,resizeEvent(),paintEvent()该镣,resizeEvent()等等冻璃。

事件的轉發(fā)

子類未處理的事件會一層一層傳遞給父類進行處理,直到最頂層损合,若無處理省艳,QEvent將停止轉發(fā)。
參考文章:QT事件傳遞與事件過濾器

事件過濾器實現(xiàn)

事件過濾器void QObject::installEventFilter(QObject *obj)是這樣實現(xiàn)的:
在所有Qt對象的基類: QObject中有一個類型為QObjectList的成員變量嫁审,名字為eventFilters跋炕,當某個QObject (qobjA)給另一個QObject (qobjB)安裝了事件過濾器(objB->installEventFilter(objA))之后,qobjB會把qobjA的指針保存在eventFilters中土居。 在qobjB處理事件之前枣购,會先去檢查eventFilters列表嬉探,如果非空,就先調用列表中對象的eventFilter()函數(shù)棉圈。
事件過濾器函數(shù)eventFilter()涩堤,如果返回true,則表示該事件已經被處理完畢分瘾,Qt將直接返回胎围,進行下一事件的處理; 如果返回false,事件將接著被送往剩下的事件過濾器或是目標對象進行處理德召。
注意:事件過濾器限定被監(jiān)視對象與監(jiān)視對象生存在同一線程中

多線程事件循環(huán)

  1. 線程中的事件循環(huán)白魂,使得線程可以使用那些需要事件循環(huán)的非GUI類(如,QTimer,QTcpSocket,QProcess)上岗。
  2. 可以把任何線程的signals連接到特定線程的slots福荸,也就是說信號-槽機制是可以跨線程使用的。
  3. 對于在QApplication之前創(chuàng)建的對象肴掷,QObject::thread()返回0敬锐,這意味著主線程僅為這些對象處理投遞事件,不會為沒有所屬線程的對象處理另外的事件呆瞻。
  4. 可以用QObject::moveToThread(QThread *targetThread)來改變QObject和QObject孩子們的線程親緣關系台夺,假如QObject對象有父親,它不能移動這種關系痴脾。
  5. 在另一個線程(而不是創(chuàng)建它的那個線程)中delete QObject對象是不安全的颤介。除非你可以保證在同一時刻對象不在處理事件。可以用QObject::deleteLater()赞赖,它會投遞一個DeferredDelete事件滚朵,這會被對象線程的事件循環(huán)最終選取到。
  6. 假如沒有事件循環(huán)運行薯定,事件不會分發(fā)給對象始绍。舉例來說瞳购,假如你在一個線程中創(chuàng)建了一個QTimer對象话侄,但從沒有調用過exec(),那么QTimer就不會發(fā)射它的timeout()信號学赛,對deleteLater()也不會工作年堆。(這同樣適用于主線程)。你可以手工使用線程安全的函數(shù)QCoreApplication::postEvent()盏浇,在任何時候变丧,給任何線程中的任何對象投遞一個事件,事件會在那個創(chuàng)建了對象的線程中通過事件循環(huán)派發(fā)绢掰。
  7. 事件過濾器在所有線程中也被支持痒蓬,不過它限定被監(jiān)視對象與監(jiān)視對象生存在同一線程中童擎。類似地,QCoreApplication::sendEvent(不是postEvent())攻晒,僅用于在調用此函數(shù)的線程中向目標對象投遞事件顾复。
  8. QObject和所有它的子類是非線程安全的。這包括整個的事件投遞系統(tǒng)鲁捏。需要牢記的是芯砸,當你正從別的線程中訪問對象時,事件循環(huán)可以向你的QObject子類投遞事件给梅。假如你調用一個不生存在當前線程中的QObject子類的函數(shù)時假丧,你必須用mutex來保護QObject子類的內部數(shù)據,否則會遭遇災難或非預期結果动羽。
  9. QThread對象(像其它的對象一樣)生存在創(chuàng)建它的那個線程中包帚,即父線程中,而不是當QThread::run()被調用時創(chuàng)建的那個線程运吓。一般來講婴噩,在你的QThread子類中提供slots是不安全的,除非你用mutex保護了你的成員變量羽德。另一方面几莽,你可以安全的從QThread::run()的實現(xiàn)中發(fā)射信號,因為信號發(fā)射是線程安全的宅静。
    Qt 多線程之逐線程事件循環(huán) 下篇

事件源碼分析

以視窗系統(tǒng)鼠標點擊QWidget為例章蚣,對代碼進行了剖析,向大家分析了Qt框架如何通過Event Loop處理進入處理消息隊列循環(huán)姨夹,如何一步一步委派給平臺相關的函數(shù)獲取纤垂、打包用戶輸入事件交給視窗系統(tǒng)處理。
1磷账、創(chuàng)建一個QApplication和MyWidget峭沦,并注冊一個事件過濾器

#include <QApplication>     
#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事件循環(huán),見section 2     
}    

事件源碼流程圖解

循環(huán)調用QEventLoop::processEvents

事件循環(huán)

調用與平臺相關的QAbstractEventDispatcher子類的processEvents接口

實現(xiàn)Windows消息循環(huán)

不斷從Windows系統(tǒng)中PeekMessage逃糟,然后分發(fā)給Windows系統(tǒng)吼鱼,再由Windows系統(tǒng)找到對應的目標窗口,將消息分發(fā)到目標線程的消息回調中WndProc绰咽。


調用與平臺相關的QAbstractEventDispatcher子類的processEvents接口

Windows消息分發(fā)

執(zhí)行到QT注冊的消息回調QtWndProc

系統(tǒng)消息轉換為QEvent

消息層層傳遞到最后的MyWidget::event

QEvent傳遞過程

最后總體簡圖-Windows平臺為例

事件循環(huán)總體簡圖

2菇肃、QApplication::exec()

int QApplication::exec()     
{     
   //skip codes     
   //簡單的交給QCoreApplication來處理事件循環(huán)=〉section 3     
   return QCoreApplication::exec();     
}     

3、QCoreApplication::exec()


int QCoreApplication::exec()     
{     
    //得到當前Thread數(shù)據     
    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是否已經創(chuàng)建     
    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 處理事件隊列循環(huán) ==> Section 4     
    int returnCode = eventLoop.exec();     
    ....     
    }     
    return returnCode;     
}  

4取募、QEventLoop::exec
只要QEventloop未退出琐谤,則循環(huán)調用processEvents派發(fā)事件。

int QEventLoop::exec(ProcessEventsFlags flags)     
{     
   //這里的實現(xiàn)代碼不少玩敏,最為重要的是以下幾行     
   Q_D(QEventLoop); // 訪問QEventloop私有類實例d     
   try {     
        //只要沒有遇見exit斗忌,循環(huán)派發(fā)事件     
        while (!d->exit)     
            processEvents(flags | WaitForMoreEvents | EventLoopExec);     
    } catch (...) {}     
} 

5质礼、QEventLoop::processEvents
將事件派發(fā)給與平臺相關的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);     
    // 將事件派發(fā)給與平臺相關的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<MSG> processedTimers;     
        // 如下循環(huán)實現(xiàn)了Windows的消息循環(huán)機制        
        /*
        while(PeekMessage(&Msg, NULL, 0, 0) > 0) 
        { 
            TranslateMessage(&Msg);    //轉換
            DispatchMessage(&Msg);     //分發(fā)
        }
        */
        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 {     
                // 從系統(tǒng)消息隊列中取消息织阳。設置了PM_REMOVE几苍,消息被取出后從消息隊列中刪除
                // PeekMessage從消息隊列中取不到消息,直接返回陈哑,線程不會被阻塞
                // GetMessage從消息隊列中取不到消息妻坝,則線程就會被操作系統(tǒng)掛起,等到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);    
                // 分發(fā)
                // 將事件打包成message調用Windows API派發(fā)出去     
                // 分發(fā)一個消息給窗口程序。消息被分發(fā)到回調函數(shù)界酒,將消息傳遞給windows系統(tǒng)圣拄,windows處理完畢,會調用回調函數(shù) => section 7 
                // DispatchMessage()函數(shù)將消息再給windows系統(tǒng)毁欣,由windows系統(tǒng)找到目標窗口并分發(fā)給該窗口庇谆,
                // 調用消息對應的窗口過程函數(shù),即窗口的WinPro函數(shù)凭疮,讓WinPro函數(shù)處理
                // qt的窗口處理程序為QtWndProc
                DispatchMessage(&msg);     
            }
        }     
    } while (canWait);     
      ...     
    return retVal;     
}     

7饭耳、窗口過程函數(shù) QtWndProc
Windows窗口回調函數(shù)定義在QTDIR\src\gui\kernel\qapplication_win.cpp
關鍵:通過QApplication::widgetAt(curPos.x, curPos.y)执解,取得鼠標點擊的坐標所在的QWidget指針寞肖,然后再調用QWidget指針所指對象的translateMouseEvent,將消息分發(fā)到對應的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創(chuàng)建的widget實例     
            QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);     
            if (w)     
                widget = (QETWidget*)w;     
        }     
        if (!qt_tabletChokeMouse) {     
            // 對,就在這里右蕊。Windows的回調函數(shù)將鼠標事件分發(fā)給了Qt Widget
           // 即我們在main函數(shù)里創(chuàng)建的widget實例      
            result = widget->translateMouseEvent(msg);            
     ...     
}  

8琼稻、translateMouseEvent
該函數(shù)所在與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將事件同步直接發(fā)送給接受者闹丐。

bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,  
                                         QWidget *alienWidget, QWidget *nativeWidget,  
                                         QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,  
                                         bool spontaneous)  
{   
    // MouseEvent默認的發(fā)送方式是spontaneous, 所以將執(zhí)行sendSpontaneousEvent。 
    // sendSpontaneousEvent()與sendEvent的代碼實現(xiàn)幾乎相同被因,除了將QEvent的屬性標記為spontaneous不同卿拴。 
    // spontaneous事件是由應用程序之外產生的事件衫仑,比如一個系統(tǒng)事件。 
    // 顯然MousePress事件是由視窗系統(tǒng)產生的一個的事件(詳見上文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標記為自發(fā)事件     
    //進一步調用 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強制事件只能夠發(fā)送給當前線程里的對象,
    // 也就是說receiver->d_func()->threadData應該等于QThreadData::current()缘挽。
    // 注意瞄崇,跨線程的事件需要借助Event Loop來派發(fā)     
    QObjectPrivate *d = receiver->d_func();     
    QThreadData *threadData = d->threadData;     
    ++threadData->loopLevel;     
    bool returnValue;     
    QT_TRY {     
        // 哇,終于來到大名鼎鼎的函數(shù)QCoreApplication::nofity()了 ==> Section 2-6     
        returnValue = notify(receiver, event);     
    } QT_CATCH (...) {     
        --threadData->loopLevel;     
        QT_RETHROW;     
    }     
} 

12壕曼、QApplication::notify
QCoreApplication::notify和它的重載函數(shù)QApplication::notify在Qt的派發(fā)過程中起到核心的作用苏研,Qt的官方文檔時這樣說的:任何線程的任何對象的所有事件在發(fā)送時都會調用notify函數(shù)。

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
先經過事件過濾器處理,優(yōu)先讓OtherWidget處理感興趣的消息轧飞,再將事件傳遞給接收對象MyWidget的event函數(shù)衅鹿。

bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)     
{     
    ...     
    // 向事件過濾器即前面的OtherWidget發(fā)送該事件,優(yōu)先讓OtherWidget處理感興趣的消息
    // 這里介紹一下Event Filters. 事件過濾器是一個接受即將發(fā)送給目標對象所有事件的對象过咬。     
    // 如代碼所示它開始處理事件在目標對象行動之前大渤。過濾器的QObject::eventFilter()實現(xiàn)被調用,能接受或者丟棄過濾掸绞,
    // 允許或者拒絕事件的更進一步的處理兼犯。如果所有的事件過濾器允許更進一步的事件處理,事件將被發(fā)送到目標對象本身集漾。
    // 如果他們中的一個停止處理切黔,目標和任何后來的事件過濾器不能看到任何事件。     
    
    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,將事件最終派發(fā)給了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是虛函數(shù)驱显,QWidget的子類可以通過重載重新定義mousePress事件的行為     
        mousePressEvent((QMouseEvent*)event);     
        break;        
}  

參考1:Qt 事件處理機制 (上篇)
參考2:Qt 事件處理機制 (下篇)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末诗芜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子埃疫,更是在濱河造成了極大的恐慌伏恐,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栓霜,死亡現(xiàn)場離奇詭異翠桦,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門销凑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丛晌,“玉大人,你說我怎么就攤上這事斗幼∨熘耄” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵蜕窿,是天一觀的道長谋逻。 經常有香客問我,道長桐经,這世上最難降的妖魔是什么毁兆? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮次询,結果婚禮上荧恍,老公的妹妹穿的比我還像新娘。我一直安慰自己屯吊,他們只是感情好送巡,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盒卸,像睡著了一般骗爆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔽介,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天摘投,我揣著相機與錄音,去河邊找鬼虹蓄。 笑死犀呼,一個胖子當著我的面吹牛,可吹牛的內容都是我干的薇组。 我是一名探鬼主播外臂,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼律胀!你這毒婦竟也來了宋光?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤炭菌,失蹤者是張志新(化名)和其女友劉穎罪佳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黑低,經...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡赘艳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片第练。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡阔馋,死狀恐怖玛荞,靈堂內的尸體忽然破棺而出娇掏,到底是詐尸還是另有隱情,我是刑警寧澤勋眯,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布婴梧,位于F島的核電站,受9級特大地震影響客蹋,放射性物質發(fā)生泄漏塞蹭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一讶坯、第九天 我趴在偏房一處隱蔽的房頂上張望番电。 院中可真熱鬧,春花似錦辆琅、人聲如沸漱办。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娩井。三九已至,卻和暖如春似袁,著一層夾襖步出監(jiān)牢的瞬間洞辣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工昙衅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扬霜,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓而涉,卻偏偏與公主長得像著瓶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子婴谱,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內容

  • 簡介 這里簡單介紹Qt的一些核心機制蟹但,具體參見Qt文檔。 主要包含內容: Qt的信號和槽谭羔,以及事件機制 Qt Ob...
    QuietHeart閱讀 1,488評論 0 3
  • 事件 事件(event)是由系統(tǒng)或者 Qt 本身在不同的時刻發(fā)出的华糖。 一些事件在對用戶操作做出響應時發(fā)出,如鍵盤事...
    人不知QAQ閱讀 266評論 0 0
  • 筆者用Qt算是用了挺長時間了瘟裸,當初入C++的坑就是因為需要用Qt設計上位機軟件】筒妫現(xiàn)在打算總結一下一些當初覺得有點深...
    飲茶先啦靚仔閱讀 50,532評論 2 26
  • 參考:Events and signals in PyQt5 所有的應用都是事件驅動的。事件大部分都是由用戶的行為...
    水之心閱讀 1,819評論 1 1
  • 為什么在頭文件中有的是使用前置聲明,而有的是包含頭文件兼搏? 如下代碼: 前置聲明(forward declarati...
    Joe_HUST閱讀 1,266評論 0 6