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 <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事件循環,見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<MSG> 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<QWidget> &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 事件處理機制 (下篇)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

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