天天看點

Qt 事件機制,底層實作原理

【1】事件

事件是可以被控件識别的操作。如按下确定按鈕、選擇某個單選按鈕或複選框。

每種控件有自己可識别的事件,如窗體的加載、單擊、輕按兩下等事件,編輯框(文本框)的文本改變事件等等。

事件就是使用者對視窗上各種元件的操作。

【2】Qt事件

由視窗系統或Qt自身産生的,用以響應所發生各類事情的操作。具體點,Qt事件是一個QEvent對象,用于描述程式内部或外部發生的動作。

【3】Qt事件産生類型

1、鍵盤或滑鼠事件:使用者按下或松開鍵盤或滑鼠上的按鍵時,就可以産生一個鍵盤或者滑鼠事件。

2、繪制事件:某個視窗第一次顯示的時候,就會産生一個繪制事件,用來告訴視窗需要重新繪制它本身,進而使得該視窗可見。

3、QT事件:Qt自己也會産生很多事件,比如QObject::startTimer()會觸發QTimerEvent。

【4】Qt事件分類

基于事件如何被産生與分發,可以把事件分為三類:

1、Spontaneous 事件

由視窗系統産生,它們被放到系統隊列中,通過事件循環逐個處理。

本類事件通常是Windows System把從系統得到的消息,比如滑鼠按鍵、鍵盤按鍵等, 放入系統的消息隊列中。 Qt事件循環的時候讀取這些事件,轉化為QEvent,再依次逐個處理。

2、Posted 事件

由Qt或應用程式産生,它們被Qt組成隊列,再通過事件循環處理。

調用QApplication::postEvent()來産生一個posted類型事件。例如:QWidget::update()函數,當需要重新繪制螢幕時,程式調用update()函數。

其實作的原理是new出一個paintEvent,調用 QApplication::postEvent(),将其放入Qt的消息隊列中,等待依次被處理。

3、Send事件

由Qt或應用程式産生,但它們被直接發送到目标對象。

調用QApplication::sendEvent()函數來産生一個send類型事件。

send 類型事件不會放入隊列, 而是直接被派發和處理, QWidget::repaint()函數用的就是這種方式。

【5】QObject類

QObject三大職責

1、記憶體管理

2、内省(intropection)

3、事件處理機制

任何一個想要接受并處理事件的對象均須繼承自QObject,可以重寫QObject::event() 來處理事件,也可以由父類處理。

【6】事件處理與過濾

Qt提供了5個級别來處理和過濾事件。

1、我們可以重新實作特定的event handler。

重新實作像mousePressEvent(), keyPressEvent()和paintEvent()這樣的event Handler是目前處理event最普通的方式。

2、我們可以重新實作QObject::event()。

通過重新實作event(),我們可以在事件到達特定的event handler之前對它們作出處理。

這個方法主要是用來覆寫Tab鍵的預設實作,也可以用來處理不同發生的事件類型,對它們,就沒有特定的event handler。

當重新實作event()的時候,我們必須調用基類的event()來處理我們不顯式處理的情況。

3、我們可以安裝一個event filter到一個單獨的QObject。

一旦一個對象用installEventFilter注冊了, 發到目标對象的所有事件都會先發到監測對象的eventFilter()。

如果同一個object安裝了多個event filter, filter會依次被激活, 從最近安裝的回到第一個。

4、我們可以在QApplication對象上安裝event filter。

一旦一個event filter被注冊到qApp(唯一的QApplication對象), 程式裡發到每個對象的每個事件在發到其他event filter之前,都要首先發到eventFilter()。

這個方法對debugging非常有用,也可以用來處理發到disable的widget上的事件, QApplication通常會丢棄它們。

5、我們可以子類化QApplication并重新實作notify()。

Qt調用QApplication::notify()來發出事件,在任何event filter得到之前, 重新實作這個函數是得到所有事件的唯一方法。

event filter通常更有用, 因為可以有任意數目且同時存在的event filter, 但是隻有一個notify()函數。

【7】事件過濾器

Qt建立QEvent事件對象後,會調用QObject的event()函數來分發事件。

但有時,你可能需要在調用event()函數之前做一些自己的操作,比如,對話框上某些元件可能并不需要響應Enter鍵按下的事件,此時,你就需要重新定義元件的event()函數。

如果元件很多,就需要重寫很多次event()函數,這顯然沒有效率。為此,你可以使用一個事件過濾器,來判斷是否需要調用元件的event()函數。

QOjbect有一個eventFilter()函數,用于建立事件過濾器。這個函數的聲明如下:

virtual bool QObject::eventFilter (QObject * watched, QEvent * event)

在建立了過濾器之後,下面要做的是安裝這個過濾器。安裝過濾器需要調用installEventFilter()函數。這個函數的聲明如下:

void QObject::installEventFilter ( QObject * filterObj)

這個函數是QObject的一個函數,是以可以安裝到任何QObject的子類,并不僅僅是UI元件。

這個函數接收一個QObject對象,調用了這個函數安裝事件過濾器的元件會調用filterObj定義的eventFilter()函數。

例如,textField.installEventFilter(obj),則如果有事件發送到textField元件時,會先調用obj->eventFilter()函數,然後才會調用textField.event()。

當然,你也可以把事件過濾器安裝到QApplication上面,這樣就可以過濾所有的事件,已獲得更大的控制權。不過,這樣做的後果就是會降低事件分發的效率。

我們可以把Qt的事件傳遞看成鍊狀:如果子類沒有處理這個事件,就會繼續向其他類傳遞。其實,Qt的事件對象都有一個accept()函數和ignore()函數。

正如它們的名字,前者用來告訴Qt,事件處理函數“接收”了這個事件,不要再傳遞;後者則告訴Qt,事件處理函數“忽略”了這個事件,需要繼續傳遞,尋找另外的接受者。

在事件處理函數中,可以使用isAccepted()來查詢這個事件是不是已經被接收了。

事實上,我們很少使用accept()和ignore()函數,而是像上面的示例一樣,如果希望忽略事件,隻要調用父類的響應函數即可。

記得我們曾經說過,Qt中的事件大部分是protected的,是以,重寫的函數必定存在着其父類中的響應函數,這個方法是可行的。

為什麼要這麼做呢?因為我們無法确認父類中的這個處理函數沒有操作,如果我們在子類中直接忽略事件,Qt不會再去尋找其他的接受者,那麼父類的操作也就不能進行,這可能會有潛在的危險。

不過,事情也不是絕對的。在一個情形下,我們必須使用accept()和ignore()函數,那就是在視窗關閉的時候。

如果你在視窗關閉時需要有個詢問對話框,那麼就需要這麼去寫:

Qt 事件機制,底層實作原理
1 void MainWindow::closeEvent(QCloseEvent * event)
 2 {
 3     if (continueToClose())
 4     {
 5         event->accept();
 6     }
 7     else
 8     {
 9         event->ignore();
10     }
11 }      
Qt 事件機制,底層實作原理

non-GUI的Qt程式,是由QCoreApplication負責将QEvent分發給QObject的子類-Receiver. Qt GUI程式,由QApplication來負責。

【8】事件和信号的差別

Qt的事件很容易和信号槽混淆。signal由具體對象發出,然後會馬上交給由connect函數連接配接的slot進行處理;

而對于事件,Qt使用一個事件隊列對所有發出的事件進行維護,當新的事件産生時,會被追加到事件隊列的尾部,前一個事件完成後,取出後面的事件接着再進行處理。

但是,必要的時候,Qt的事件也是可以不進入事件隊列,而是直接處理的。并且,事件還可以使用“事件過濾器”進行過濾。

比如一個按鈕對象, 我們使用這個按鈕對象的時候, 我們隻關心它被按下的信号, 至于這個按鈕如何接收處理滑鼠事件,再發射這個信号,我們是不用關心的。

但是如果我們要重載一個按鈕的時候,我們就要面對event了。 比如我們可以改變它的行為,在滑鼠按鍵按下的時候(mouse press event) 就觸發clicked()的signal而不是通常在釋放的( mouse release event)時候。

總結的說,Qt的事件和Qt中的signal不一樣。 後者通常用來使用widget, 而前者用來實作widget。 如果我們使用系統預定義的控件,那我們關心的是信号,如果自定義控件我們關心的是事件。

【9】自定義事件

為什麼需要自定義事件?

事件既可用于同步也可用于異步(依賴于你是調用sendEvent()或是postEvents()),函數調用或是槽調用總是同步的。事件的另外一個好處是它可以被過濾。

阻塞型事件:事件發送後需要等待處理完成

[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)

事件生命周期由應用程式自身管理,同時支援棧事件對象和堆事件對象的發送。

非阻塞型發送:事件發送後立刻傳回,事件被發送到事件隊列等待處理

[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)

隻能發送堆事件對象,事件被處理後由Qt平台銷毀

當我們在main()函數的末尾調用QApplication::exec()時,程式進入了Qt的事件循環,大概來講,事件循環如下面所示:

Qt 事件機制,底層實作原理
1 while (!exit_was_called)
 2 {
 3     while (!posted_event_queue_is_empty)
 4     {
 5         process_next_posted_event();
 6     }
 7     while (!spontaneous_event_queue_is_empty)
 8     {
 9         process_next_spontaneous_event();
10     }
11     while (!posted_event_queue_is_empty)
12     {
13         process_next_posted_event();
14     }
15 }      
Qt 事件機制,底層實作原理

 Qt事件循環的過程

首先,事件循環處理所有的posted事件,直到隊列空。然後再處理所有spontaneous事件,最後它處理所有的因為處理spontaneous事件而産生的posted事件。

send 事件并不在事件循環内處理,它們都直接被發送到了目标對象。

當一個widget第一次可見,或被遮擋後再次變為可見,視窗系統産生一個(spontaneous) paint事件,要求程式重繪widget。

事件循環最終會從事件隊列中撿選這個事件并把它分發到那個需要重畫的widget。

并不是所有的paint事件都是由視窗系統産生的。當你調用QWidget::update()去強行重畫widget,這個widget會post 一個paint事件給自己。這個paint事件被放入隊列,最終被事件循環分發之。

如果等不及事件循環去重畫一個widget, 理論上,應該直接調用paintEvent()強制進行立即的重畫。但實際上這不總是可行的,因為paintEvent()函數是protected的(很可能通路不了)。

它也繞開了任何存在的事件過濾器。因為這些原因,Qt提供了一個機制,直接sending事件而不是posting。 QWidget::repaint()就使用了這個機制來強制進行立即重畫。

posting 相對于sending的一個優勢是,它給了Qt一個壓縮(compress)事件的機會。

假如你在一個widget上連續地調用update() 十次,因update()而産生的這十個事件,将會自動地被合并為一個單獨的事件,但是QPaintEvents事件附帶的區域資訊也合并了。

可壓縮的事件類型包括:paint、move、resize、layout hint、language change。

最後要注意,你可以在任何時候調用QApplication::sendPostedEvent(),強制Qt産生一個對象的posted事件。

【10】事件轉發

對于某些類别的事件, 如果在整個事件的派發過程結束後還沒有被處理, 那麼這個事件将會向上轉發給它的父widget,直到最頂層視窗。

比如:事件可能最先發送給QCheckBox, 如果QCheckBox沒有處理, 那麼由QGroupBox接着處理;

如果QGroupBox仍然沒有處理, 再送到QDialog, 因為QDialog已經是最頂層widget, 是以如果QDialog再不處理, QEvent将停止轉發。

如何判斷一個事件是否被處理了呢? Qt中和事件相關的函數通過兩種方式互相通信。

QApplication::notify(), QObject::eventFilter(), QObject::event() 通過傳回bool值來表示是否已處理。“真”表示已經處理, “假”表示事件需要繼續傳遞。

另一種是調用QEvent::ignore() 或 QEvent::accept() 對事件進行辨別。這種方式隻用于event() 函數和特定事件處理函數之間的溝通。

而且隻有用在某些類别事件上是有意義的, 這些事件就是上面提到的那些會被轉發的事件, 包括: 滑鼠、滾輪、按鍵等事件。

【11】事件的傳播(propogation)

如果事件在目标對象上得不到處理,事件向上一層進行傳播,直到最頂層的widget為止。

如果得到事件的對象,調用了accept(),則事件停止繼續傳播;如果調用了ignore(),事件向上一級繼續傳播。

Qt對自定義事件處理函數的預設傳回值是accept(),但預設的事件處理函數是ingore()。

是以,如果要繼續向上傳播,調用QWidget的預設處理函數即可。到此為止的話,不必顯式調用accept()。

但在event處理函數裡,傳回true表示accept,傳回false表示向上級傳播。

但closeEvent是個特殊情形,accept表示quit,ignore表示取消,是以最好在closeEvent顯式調用accept和ignore。

【12】事件産生

事件産生詳細過程:

Qt 事件機制,底層實作原理
1 // section 1-1
  2 int main(int argc, char *argv[])
  3 {
  4     QApplication a(argc, argv);
  5     MainWindow w;
  6     w.show();
  7 
  8     return a.exec();
  9 }
 10 
 11 // section 1-2
 12 // 源碼路徑:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp)
 13 int QApplication::exec()
 14 {
 15     return QGuiApplication::exec();
 16 }
 17 
 18 // section 1-3
 19 // 源碼路徑:($QTDIR\Src\qtbase\src\gui\kernel\qguiapplication.cpp)
 20 int QGuiApplication::exec()
 21 {
 22 #ifndef QT_NO_ACCESSIBILITY
 23     QAccessible::setRootObject(qApp);
 24 #endif
 25     return QCoreApplication::exec();
 26 }
 27 
 28 // section 1-4
 29 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
 30 int QCoreApplication::exec()
 31 {
 32     if (!QCoreApplicationPrivate::checkInstance("exec"))
 33         return -1;
 34 
 35     QThreadData *threadData = self->d_func()->threadData;
 36     if (threadData != QThreadData::current())
 37     {
 38         qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
 39         return -1;
 40     }
 41     if (!threadData->eventLoops.isEmpty())
 42     {
 43         qWarning("QCoreApplication::exec: The event loop is already running");
 44         return -1;
 45     }
 46 
 47     threadData->quitNow = false;
 48     QEventLoop eventLoop;
 49     self->d_func()->in_exec = true;
 50     self->d_func()->aboutToQuitEmitted = false;
 51     int returnCode = eventLoop.exec();
 52     threadData->quitNow = false;
 53     if (self)
 54     {
 55         self->d_func()->in_exec = false;
 56         if (!self->d_func()->aboutToQuitEmitted)
 57         {
 58             emit self->aboutToQuit(QPrivateSignal());
 59         }
 60         self->d_func()->aboutToQuitEmitted = true;
 61         sendPostedEvents(0, QEvent::DeferredDelete);
 62     }
 63 
 64     return returnCode;
 65 }
 66 
 67 // section 1-5
 68 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp)
 69 // 聲明:int exec(ProcessEventsFlags flags = AllEvents);
 70 int QEventLoop::exec(ProcessEventsFlags flags)
 71 {
 72     Q_D(QEventLoop);
 73     // we need to protect from race condition with QThread::exit
 74     QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);
 75     if (d->threadData->quitNow)
 76     {
 77         return -1;
 78     }
 79 
 80     if (d->inExec)
 81     {
 82         qWarning("QEventLoop::exec: instance %p has already called exec()", this);
 83         return -1;
 84     }
 85 
 86     struct LoopReference
 87     {
 88         QEventLoopPrivate *d;
 89         QMutexLocker &locker;
 90 
 91         bool exceptionCaught;
 92         LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)
 93         {
 94             d->inExec = true;
 95             d->exit.storeRelease(false);
 96             ++d->threadData->loopLevel;
 97             d->threadData->eventLoops.push(d->q_func());
 98             locker.unlock();
 99         }
100 
101         ~LoopReference()
102         {
103             if (exceptionCaught)
104             {
105                 qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
106                          "exceptions from an event handler is not supported in Qt. You must\n"
107                          "reimplement QApplication::notify() and catch all exceptions there.\n");
108             }
109             locker.relock();
110             QEventLoop *eventLoop = d->threadData->eventLoops.pop();
111             Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
112             Q_UNUSED(eventLoop); // --release warning
113             d->inExec = false;
114             --d->threadData->loopLevel;
115         }
116     };
117     LoopReference ref(d, locker);
118 
119     // remove posted quit events when entering a new event loop
120     QCoreApplication *app = QCoreApplication::instance();
121     if (app && app->thread() == thread())
122     {
123         QCoreApplication::removePostedEvents(app, QEvent::Quit);
124     }
125 
126     while (!d->exit.loadAcquire())
127     {
128         processEvents(flags | WaitForMoreEvents | EventLoopExec);
129     }
130 
131     ref.exceptionCaught = false;
132     return d->returnCode.load();
133 }
134 
135 // section 1-6
136 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp)
137 bool QEventLoop::processEvents(ProcessEventsFlags flags)
138 {
139     Q_D(QEventLoop);
140     if (!d->threadData->eventDispatcher.load())
141     {
142         return false;
143     }
144     return d->threadData->eventDispatcher.load()->processEvents(flags);
145 }
146 
147 // section 1-7
148 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp)
149 // 這段代碼是完成與windows平台相關的windows c++。
150 // 以跨平台著稱的Qt同時也提供了對Symiban、Unix等平台的消息派發支援,
151 // 分别封裝在QEventDispatcherSymbian和QEventDIspatcherUNIX。
152 // QEventDispatcherWin32繼承QAbstractEventDispatcher。
153 bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
154 {
155     Q_D(QEventDispatcherWin32);
156 
157     if (!d->internalHwnd)
158     {
159         createInternalHwnd();
160         wakeUp(); // trigger a call to sendPostedEvents()
161     }
162 
163     d->interrupt = false;
164     emit awake();
165 
166     bool canWait;
167     bool retVal = false;
168     bool seenWM_QT_SENDPOSTEDEVENTS = false;
169     bool needWM_QT_SENDPOSTEDEVENTS = false;
170     do
171     {
172         DWORD waitRet = 0;
173         HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
174         QVarLengthArray<MSG> processedTimers;
175         while (!d->interrupt)
176         {
177             DWORD nCount = d->winEventNotifierList.count();
178             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
179 
180             MSG msg;
181             bool haveMessage;
182 
183             if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty())
184             {
185                 // process queued user input events
186                 haveMessage = true;
187                 msg = d->queuedUserInputEvents.takeFirst(); // 逐個處理使用者輸入隊列中的事件
188             }
189             else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty())
190             {
191                 // process queued socket events
192                 haveMessage = true;
193                 msg = d->queuedSocketEvents.takeFirst(); // 逐個處理socket隊列中的事件
194             }
195             else
196             {
197                 haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
198                 if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)
199                     && ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
200                         || (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
201                         || msg.message == WM_MOUSEWHEEL
202                         || msg.message == WM_MOUSEHWHEEL
203                         || msg.message == WM_TOUCH
204 #ifndef QT_NO_GESTURES
205                         || msg.message == WM_GESTURE
206                         || msg.message == WM_GESTURENOTIFY
207 #endif
208                         || msg.message == WM_CLOSE))
209                 {
210                     // queue user input events for later processing
211                     haveMessage = false;
212                     d->queuedUserInputEvents.append(msg); // 使用者輸入事件入隊列,待以後處理
213                 }
214                 if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)
215                     && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd))
216                 {
217                     // queue socket events for later processing
218                     haveMessage = false;
219                     d->queuedSocketEvents.append(msg); // socket 事件入隊列,待以後處理
220                 }
221             }
222             if (!haveMessage)
223             {
224                 // no message - check for signalled objects
225                 for (int i = 0; i < (int)nCount; i++)
226                 {
227                     pHandles[i] = d->winEventNotifierList.at(i)->handle();
228                 }
229                 waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
230                 if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount)))
231                 {
232                     // a new message has arrived, process it
233                     continue;
234                 }
235             }
236             if (haveMessage)
237             {
238                 // WinCE doesn't support hooks at all, so we have to call this by hand :(
239                 if (!d->getMessageHook)
240                 {
241                     (void) qt_GetMessageHook(0, PM_REMOVE, (LPARAM) &msg);
242                 }
243 
244                 if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS)
245                 {
246                     if (seenWM_QT_SENDPOSTEDEVENTS)
247                     {
248                         // when calling processEvents() "manually", we only want to send posted
249                         // events once
250                         needWM_QT_SENDPOSTEDEVENTS = true;
251                         continue;
252                     }
253                     seenWM_QT_SENDPOSTEDEVENTS = true;
254                 }
255                 else if (msg.message == WM_TIMER)
256                 {
257                     // avoid live-lock by keeping track of the timers we've already sent
258                     bool found = false;
259                     for (int i = 0; !found && i < processedTimers.count(); ++i)
260                     {
261                         const MSG processed = processedTimers.constData()[i];
262                         found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
263                     }
264                     if (found)
265                     {
266                         continue;
267                     }
268                     processedTimers.append(msg);
269                 }
270                 else if (msg.message == WM_QUIT)
271                 {
272                     if (QCoreApplication::instance())
273                     {
274                         QCoreApplication::instance()->quit();
275                     }
276                     return false;
277                 }
278 
279                 if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0))
280                 {
281                     // 将事件打包成message調用Windows API派發出去
282                     TranslateMessage(&msg);
283                     // 分發一個消息給視窗程式。消息被分發到回調函數,将消息傳遞給windows系統,windows處理完畢,會調用回調函數。
284                     DispatchMessage(&msg);
285                 }
286             }
287             else if (waitRet - WAIT_OBJECT_0 < nCount)
288             {
289                 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
290             }
291             else
292             {
293                 // nothing todo so break
294                 break;
295             }
296             retVal = true;
297         }
298 
299         // still nothing - wait for message or signalled objects
300         canWait = (!retVal
301                    && !d->interrupt
302                    && (flags & QEventLoop::WaitForMoreEvents));
303         if (canWait)
304         {
305             DWORD nCount = d->winEventNotifierList.count();
306             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
307             for (int i = 0; i < (int)nCount; i++)
308             {
309                 pHandles[i] = d->winEventNotifierList.at(i)->handle();
310             }
311 
312             emit aboutToBlock();
313             waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
314             emit awake();
315             if (waitRet - WAIT_OBJECT_0 < nCount)
316             {
317                 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
318                 retVal = true;
319             }
320         }
321     } while (canWait);
322 
323     if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0)
324     {
325         // when called "manually", always send posted events
326         sendPostedEvents();
327     }
328 
329     if (needWM_QT_SENDPOSTEDEVENTS)
330     {
331         PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
332     }
333 
334     return retVal;
335 }
336 
337 // section1~7的過程:Qt進入QApplication的event loop,經過層層委任,
338 // 最終QEventLoop的processEvent将通過與平台相關的AbstractEventDispatcher的子類QEventDispatcherWin32
339 // 獲得使用者的輸入事件,并将其打包成message後,通過标準的Windows API傳遞給Windows OS。
340 // Windows OS得到通知後回調QtWndProc,至此事件的分發與處理完成了一半的路程。
341 // 事件的産生、分發、接受和處理,并以視窗系統滑鼠點選QWidget為例,對代碼進行了剖析,向大家分析了Qt架構如何通過Event
342 // Loop處理進入處理消息隊列循環,如何一步一步委派給平台相關的函數擷取、打包使用者輸入事件交給視窗系統處理,函數調用棧如下:
343 // 1.main(int, char **)
344 // 2.QApplication::exec()
345 // 3.QCoreApplication::exec()
346 // 4.QEventLoop::exec(ProcessEventsFlags )
347 // 5.QEventLoop::processEvents(ProcessEventsFlags )
348 // 6.QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)      
Qt 事件機制,底層實作原理

【13】事件分發

事件分發詳細過程:

Qt 事件機制,底層實作原理
1 // 1.QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg)
  2 // 2.bool QApplicationPrivate::sendMouseEvent(...)
  3 // 3.inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
  4 // 4.bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
  5 // 5.bool QApplication::notify(QObject *receiver, QEvent *e)
  6 // 6.bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
  7 // 7.bool QWidget::event(QEvent *event)
  8 // 下面介紹Qt app在視窗系統回調後,事件又是怎麼一步步通過QApplication分發給最終事件的接受和處理者QWidget::event,
  9 // QWidget繼承自Object,重載其虛函數event。
 10 // section 2-1
 11 // windows視窗回調函數
 12 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 13 {
 14     // ...
 15     // 檢查message是否屬于Qt可轉義的滑鼠事件
 16     if (qt_is_translatable_mouse_event(message))
 17     {
 18         if (QApplication::activePopupWidget() != 0)
 19         { // in popup mode
 20             POINT curPos = msg.pt;
 21             // 取得滑鼠點選坐标所在的QWidget指針,它指向我們在main建立的widget執行個體
 22             QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);
 23             if (w)
 24             {
 25                 widget = (QETWidget*)w;
 26             }
 27         }
 28 
 29         if (!qt_tabletChokeMouse)
 30         {
 31             // 對,就在這裡。Windows的回調函數将滑鼠事件分發回給了Qt Widget
 32             // => Section 2-2
 33             result = widget->translateMouseEvent(msg);  // mouse event
 34         }
 35     }
 36 
 37     // ...
 38 }
 39 
 40 // section 2-2 ($QTDIR\src\gui\kernel\qapplication_win.cpp)
 41 // 該函數所在與Windows平台相關,主要職責就是把已用windows格式打包的滑鼠事件解包、翻譯成QApplication可識别的QMouseEvent。
 42 bool QETWidget::translateMouseEvent(const MSG &msg)
 43 {
 44     // ...這裡有很長的一段代碼可以忽略
 45     // 讓我們看一下sendMouseEvent的聲明
 46     // widget是事件的接受者;e是封裝好的QMouseEvent
 47     // ==> Section 2-3
 48     res = QApplicationPrivate::sendMouseEvent(target,
 49                                               &e, alienWidget, this, &qt_button_down,
 50                                               qt_last_mouse_receiver);
 51 }
 52 
 53 // section 2-3
 54 // 源碼路徑:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp)
 55 bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,
 56                                          QWidget *alienWidget, QWidget *nativeWidget,
 57                                          QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,
 58                                          bool spontaneous)
 59 {
 60     // ...
 61     // 至此與平台相關代碼處理完畢
 62     // MouseEvent預設的發送方式是spontaneous, 是以将執行sendSpontaneousEvent。
 63     // sendSpontaneousEvent() 與 sendEvent的代碼實作幾乎相同
 64     // 除了将QEvent的屬性spontaneous标記不同。 這裡是解釋什麼是spontaneous事件:事件由應用程式之外産生的,比如一個系統事件。
 65     // 顯然MousePress事件是由視窗系統産生的一個的事件(詳見上文Section 1~ Section 7),是以它是spontaneous事件
 66     if (spontaneous)
 67     {
 68         result = QApplication::sendSpontaneousEvent(receiver, event);
 69     }
 70     else
 71     {
 72         result = QApplication::sendEvent(receiver, event);//TODO
 73     }
 74 
 75     ...
 76 
 77     return result;
 78 }
 79 
 80 // section 2-4
 81 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
 82 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
 83 {
 84     // 将event标記為自發事件
 85     // 進一步調用 2-5 QCoreApplication::notifyInternal
 86     if (event)
 87     {
 88         event->spont = true;
 89     }
 90     return self ? self->notifyInternal(receiver, event) : false;
 91 }
 92 
 93 // section 2-5:
 94 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
 95 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
 96 {
 97     // 幾行代碼對于Qt Jambi (QT Java綁定版本) 和QSA (QT Script for Application)的支援
 98     // ...
 99     // 以下代碼主要意圖為Qt強制事件隻能夠發送給目前線程裡的對象,
100     // 也就是說receiver->d_func()->threadData應該等于QThreadData::current()。
101     // 注意,跨線程的事件需要借助Event Loop來派發
102     QObjectPrivate *d = receiver->d_func();
103     QThreadData *threadData = d->threadData;
104     ++threadData->loopLevel;
105 
106     // 哇,終于來到大名鼎鼎的函數QCoreApplication::nofity()了 ==> Section 2-6
107     QT_TRY
108     {
109         returnValue = notify(receiver, event);
110     }
111     QT_CATCH (...)
112     {
113         --threadData->loopLevel;
114         QT_RETHROW;
115     }
116 
117     ...
118 
119     return returnValue;
120 }
121 
122 // section 2-6:
123 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
124 // QCoreApplication::notify和它的重載函數QApplication::notify在Qt的派發過程中起到核心的作用,Qt的官方文檔時這樣說的:
125 // 任何線程的任何對象的所有事件在發送時都會調用notify函數。
126 bool QCoreApplication::notify(QObject *receiver, QEvent *event)
127 {
128     Q_D(QCoreApplication);
129     // no events are delivered after ~QCoreApplication() has started
130     if (QCoreApplicationPrivate::is_app_closing)
131     {
132         return true;
133     }
134 
135     if (receiver == 0)
136     {                        // serious error
137         qWarning("QCoreApplication::notify: Unexpected null receiver");
138         return true;
139     }
140 
141 #ifndef QT_NO_DEBUG
142     d->checkReceiverThread(receiver);
143 #endif
144 
145     return receiver->isWidgetType() ? false : d->notify_helper(receiver, event);
146 }
147 
148 // section 2-7:
149 // 源碼路徑:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
150 // notify 調用 notify_helper()
151 bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
152 {
153     // send to all application event filters
154     if (sendThroughApplicationEventFilters(receiver, event))
155     {
156         return true;
157     }
158     // 向事件過濾器發送該事件,這裡介紹一下Event Filters. 事件過濾器是一個接受即将發送給目标對象所有事件的對象。
159     //如代碼所示它開始處理事件在目标對象行動之前。過濾器的QObject::eventFilter()實作被調用,能接受或者丢棄過濾
160     //允許或者拒絕事件的更進一步的處理。如果所有的事件過濾器允許更進一步的事件處理,事件将被發送到目标對象本身。
161     //如果他們中的一個停止處理,目标和任何後來的事件過濾器不能看到任何事件。
162     if (sendThroughObjectEventFilters(receiver, event))
163     {
164         return true;
165     }
166     // deliver the event
167     // 遞交事件給receiver  => Section 2-8
168     return receiver->event(event);
169 }
170 
171 // section 2-8
172 // 源碼路徑:($QTDIR\Src\qtbase\src\widgets\kernel\qwidget.cpp)
173 // QApplication通過notify及其私有類notify_helper,将事件最終派發給了QObject的子類- QWidget.
174 bool QWidget::event(QEvent *event)
175 {
176     // ...
177     switch (event->type())
178     {
179     case QEvent::MouseMove:
180         mouseMoveEvent((QMouseEvent*)event);
181         break;
182 
183     case QEvent::MouseButtonPress:
184         // Don't reset input context here. Whether reset or not is
185         // a responsibility of input method. reset() will be
186         // called by mouseHandler() of input method if necessary
187         // via mousePressEvent() of text widgets.
188 #if 0
189         resetInputContext();
190 #endif
191         mousePressEvent((QMouseEvent*)event);
192         break;
193     }
194     // ...
195 }      
Qt 事件機制,底層實作原理

【14】Qt5.3.2版本事件機制源碼調試

事件産生于分發調試堆棧圖如下:

Qt 事件機制,底層實作原理

繼續閱讀