Qt 事件处理机制 (上篇)
本篇來介紹Qt 事件處理機制 。深入了解事件處理系統對于每個學習Qt人來說非常重要,可以說,Qt是以事件驅動的UI工具集。 大家熟知Signals/Slots在多線程的實現也依賴于Qt的事件處理機制。
在Qt中,事件被封裝成一個個對象,所有的事件均繼承自抽象類QEvent.? 接下來依次談談Qt中有誰來產生、分發、接受和處理事件:
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來負責。
接下來,將通過對代碼的解析來看看QT是利用event loop從事件隊列中獲取用戶輸入事件,又是如何將事件轉義成QEvents,并分發給相應的QObject處理。
#include <QApplication> #include "widget.h" //Section 1 int main(int argc, char *argv[]) { QApplication app(argc, argv); Widget window; // Widget 繼承自QWidget window.show(); return app.exec(); // 進入Qpplication事件循環,見section 2 } // Section 2: int QApplication::exec() { //skip codes //簡單的交給QCoreApplication來處理事件循環=〉section 3 return QCoreApplication::exec(); } // Section 3 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; } // Section 4 int QEventLoop::exec(ProcessEventsFlags flags) { //這里的實現代碼不少,最為重要的是以下幾行 Q_D(QEventLoop); // 訪問QEventloop私有類實例d try { //只要沒有遇見exit,循環派發事件 while (!d->exit) processEvents(flags | WaitForMoreEvents | EventLoopExec); } catch (...) {} } // Section 5 bool QEventLoop::processEvents(ProcessEventsFlags flags) { Q_D(QEventLoop); if (!d->threadData->eventDispatcher) return false; if (flags & DeferredDeletion) QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); //將事件派發給與平臺相關的QAbstractEventDispatcher子類 =>Section 6 return d->threadData->eventDispatcher->processEvents(flags); } #include <QApplication> #include "widget.h" //Section 1 int main(int argc, char *argv[]) { QApplication app(argc, argv); Widget window; // Widget 繼承自QWidget window.show(); return app.exec(); // 進入Qpplication事件循環,見section 2 } // Section 2: int QApplication::exec() { //skip codes //簡單的交給QCoreApplication來處理事件循環=〉section 3 return QCoreApplication::exec(); } // Section 3 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; } // Section 4 int QEventLoop::exec(ProcessEventsFlags flags) { //這里的實現代碼不少,最為重要的是以下幾行 Q_D(QEventLoop); // 訪問QEventloop私有類實例d try { //只要沒有遇見exit,循環派發事件 while (!d->exit) processEvents(flags | WaitForMoreEvents | EventLoopExec); } catch (...) {} } // Section 5 bool QEventLoop::processEvents(ProcessEventsFlags flags) { Q_D(QEventLoop); if (!d->threadData->eventDispatcher) return false; if (flags & DeferredDeletion) QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); //將事件派發給與平臺相關的QAbstractEventDispatcher子類 =>Section 6 return d->threadData->eventDispatcher->processEvents(flags); } // Section 6,QTDIR\src\corelib\kernel\qeventdispatcher_win.cpp // 這段代碼是完成與windows平臺相關的windows c++。 以跨平臺著稱的Qt同時也提供了對Symiban,Unix等平臺的消息派發支持 // 其事現分別封裝在QEventDispatcherSymbian和QEventDispatcherUNIX // QEventDispatcherWin32派生自QAbstractEventDispatcher. bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { 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; 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()) { // process queued user input events haveMessage = true; //從處理用戶輸入隊列中取出一條事件 msg = d->queuedUserInputEvents.takeFirst(); } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) { // 從處理socket隊列中取出一條事件 haveMessage = true; msg = d->queuedSocketEvents.takeFirst(); } else { 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)) { // socket 事件入隊列,待以后處理 haveMessage = false; d->queuedSocketEvents.append(msg); } } .... if (!filterEvent(&msg)) { TranslateMessage(&msg); //將事件打包成message調用Windows API派發出去 //分發一個消息給窗口程序。消息被分發到回調函數,將消息傳遞給windows系統,windows處理完畢,會調用回調函數 => section 7 DispatchMessage(&msg); } } } } while (canWait); ... return retVal; } // Section 6,QTDIR\src\corelib\kernel\qeventdispatcher_win.cpp // 這段代碼是完成與windows平臺相關的windows c++。 以跨平臺著稱的Qt同時也提供了對Symiban,Unix等平臺的消息派發支持 // 其事現分別封裝在QEventDispatcherSymbian和QEventDispatcherUNIX // QEventDispatcherWin32派生自QAbstractEventDispatcher. bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { 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; 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()) { // process queued user input events haveMessage = true; //從處理用戶輸入隊列中取出一條事件 msg = d->queuedUserInputEvents.takeFirst(); } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) { // 從處理socket隊列中取出一條事件 haveMessage = true; msg = d->queuedSocketEvents.takeFirst(); } else { 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)) { // socket 事件入隊列,待以后處理 haveMessage = false; d->queuedSocketEvents.append(msg); } } .... if (!filterEvent(&msg)) { TranslateMessage(&msg); //將事件打包成message調用Windows API派發出去 //分發一個消息給窗口程序。消息被分發到回調函數,將消息傳遞給windows系統,windows處理完畢,會調用回調函數 => section 7 DispatchMessage(&msg); } } } } while (canWait); ... return retVal; } // Section 7 windows窗口回調函數 定義在QTDIR\src\gui\kernel\qapplication_win.cpp extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { ... //將消息重新封裝成QEvent的子類QMouseEvent ==> Section 8 result = widget->translateMouseEvent(msg); ... } // Section 7 windows窗口回調函數 定義在QTDIR\src\gui\kernel\qapplication_win.cpp extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { ... //將消息重新封裝成QEvent的子類QMouseEvent ==> Section 8 result = widget->translateMouseEvent(msg); ... }?
從Section 1~Section7,?Qt進入QApplication的event loop,經過層層委任,最終QEventloop的processEvent將通過與平臺相關的QAbstractEventDispatcher的子類QEventDispatcherWin32獲得用戶的用戶輸入事件,并將其打包成message后,通過標準Windows API ,把消息傳遞給了Windows OS,Windows OS得到通知后回調QtWndProc,? 至此事件的分發與處理完成了一半的路程。
小結:Qt 事件處理機制 (上篇)的內容介紹完了,在下文中,我們將進一步討論當我們收到來在Windows的回調后,事件又是怎么一步步打包成QEvent并通過QApplication分發給最終事件的接受和處理者QObject::event.請繼續看Qt 事件處理機制 (下篇)。最后希望本文能幫你解決問題!
總結
以上是生活随笔為你收集整理的Qt 事件处理机制 (上篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python全栈开发之并发编程
- 下一篇: 使用RunWith注解改变JUnit的默