Qt 事件处理机制-qt源码解读
在Qt中,事件被封裝成一個個對象,所有的事件均繼承自抽象類QEvent. 接下來依次談?wù)凲t中有誰來產(chǎn)生、分發(fā)、接受和處理事件。
本篇來介紹Qt 事件處理機(jī)制。深入了解事件處理系統(tǒng)對于每個學(xué)習(xí)Qt人來說非常重要,可以說,Qt是以事件驅(qū)動的UI工具集。 大家熟知Signals/Slots在多線程的實(shí)現(xiàn)也依賴于Qt的事件處理機(jī)制
在Qt中,事件被封裝成一個個對象,所有的事件均繼承自抽象類QEvent.? 接下來依次談?wù)?strong>Qt中有誰來產(chǎn)生、分發(fā)、接受和處理事件:
1、誰來產(chǎn)生事件: 最容易想到的是我們的輸入設(shè)備,比如鍵盤、鼠標(biāo)產(chǎn)生的
keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他們被封裝成QMouseEvent和QKeyEvent),這些事件來自于底層的操作系統(tǒng),它們以異步的形式通知Qt事件處理系統(tǒng),后文會仔細(xì)道來。當(dāng)然Qt自己也會產(chǎn)生很多事件,比如QObject::startTimer()會觸發(fā)QTimerEvent.
用戶的程序可還以自己定制事件
2、誰來接受和處理事件:答案是QObject。在Qt的內(nèi)省機(jī)制剖析一文已經(jīng)介紹QObject
類是整個Qt對象模型的心臟,事件處理機(jī)制是QObject三大職責(zé)(內(nèi)存管理、內(nèi)省(intropection)與事件處理制)之一。任何一個想要接受并處理事件的對象均須繼承自QObject,可以選擇重載QObject::event()函數(shù)或事件的處理權(quán)轉(zhuǎn)給父類。
3、誰來負(fù)責(zé)分發(fā)事件:對于non-GUI的Qt程序,是由QCoreApplication負(fù)責(zé)將QEvent分發(fā)給QObject的子類-Receiver.
對于Qt GUI程序,由QApplication來負(fù)責(zé)
接下來,將通過對代碼的解析來看看QT是利用event loop從事件隊(duì)列中獲取用戶輸入事件,又是如何將事件轉(zhuǎn)義成QEvents,并分發(fā)給相應(yīng)的QObject處理。
section1
1 #include <QApplication> 2 #include "widget.h" 3 int main(int argc, char *argv[]) 4 { 5 QApplication app(argc, argv); 6 Widget window; // Widget 繼承自QWidget 7 window.show(); 8 return app.exec(); // 進(jìn)入Qpplication事件循環(huán),見section 2 9 }section2
1 int QApplication::exec() 2 { 3 #ifndef QT_NO_ACCESSIBILITY 4 QAccessible::setRootObject(qApp); 5 #endif //簡單的交給QCoreApplication來處理事件循環(huán)=〉section?3 6 return QCoreApplication::exec(); 7 }section3
1 int QCoreApplication::exec()2 {3 if (!QCoreApplicationPrivate::checkInstance("exec"))4 return -1;5 //得到當(dāng)前Thread數(shù)據(jù)??6 QThreadData *threadData = self->d_func()->threadData;7 if (threadData != QThreadData::current()) {8 qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());9 return -1; 10 }//檢查event?loop是否已經(jīng)創(chuàng)建? 11 if (!threadData->eventLoops.isEmpty()) { 12 qWarning("QCoreApplication::exec: The event loop is already running"); 13 return -1; 14 } 15 16 threadData->quitNow = false; 17 QEventLoop eventLoop; 18 self->d_func()->in_exec = true; 19 self->d_func()->aboutToQuitEmitted = false;//委任QEventLoop?處理事件隊(duì)列循環(huán)?==>?Section?4 20 int returnCode = eventLoop.exec(); 21 threadData->quitNow = false; 22 if (self) { 23 self->d_func()->in_exec = false; 24 if (!self->d_func()->aboutToQuitEmitted) 25 emit self->aboutToQuit(); 26 self->d_func()->aboutToQuitEmitted = true; 27 sendPostedEvents(0, QEvent::DeferredDelete); 28 } 29 30 return returnCode; 31 }
section4
section5
1 bool QEventLoop::processEvents(ProcessEventsFlags flags) 2 { 3 Q_D(QEventLoop); 4 if (!d->threadData->eventDispatcher) 5 return false; 6 if (flags & DeferredDeletion) 7 QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); 8 return d->threadData->eventDispatcher->processEvents(flags); //將事件派發(fā)給與平臺相關(guān)的QAbstractEventDispatcher子類?=>Section?6 9 }- //?Section?6,QTDIR\src\corelib\kernel\qeventdispatcher_win.cpp??? ?
- //?這段代碼是完成與windows平臺相關(guān)的windows?c++。?以跨平臺著稱的Qt同時也提供了對Symiban,Unix等平臺的消息派發(fā)支持??? ?
- //?其事現(xiàn)分別封裝在QEventDispatcherSymbian和QEventDispatcherUNIX??? ?
- //?QEventDispatcherWin32派生自QAbstractEventDispatcher
//?Section?7?windows窗口回調(diào)函數(shù)?定義在QTDIR\src\gui\kernel\qapplication_win.cpp?
1 extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 2 { 3 ... 4 //將消息重新封裝成QEvent的子類QMouseEvent ==> Section 8 5 result = widget->translateMouseEvent(msg); 6 ... 7 }從Section 1~Section7,?Qt進(jìn)入QApplication的event loop,經(jīng)過層層委任,最終QEventloop的processEvent將通過與平臺相關(guān)的QAbstractEventDispatcher的子類QEventDispatcherWin32獲得用戶的用戶輸入事件,并將其打包成message后,通過標(biāo)準(zhǔn)Windows API ,把消息傳遞給了Windows OS,Windows OS得到通知后回調(diào)QtWndProc,? 至此事件的分發(fā)與處理完成了一半的路程。
在下文中,我們將進(jìn)一步討論當(dāng)我們收到來在Windows的回調(diào)后,事件又是怎么一步步打包成QEvent并通過QApplication分發(fā)給最終事件的接受和處理者QObject::event
事件的產(chǎn)生、分發(fā)、接受和處理,并以視窗系統(tǒng)鼠標(biāo)點(diǎn)擊QWidget為例,對代碼進(jìn)行了剖析,向大家分析了Qt框架如何通過Event
Loop處理進(jìn)入處理消息隊(duì)列循環(huán),如何一步一步委派給平臺相關(guān)的函數(shù)獲取、打包用戶輸入事件交給視窗系統(tǒng)處理,函數(shù)調(diào)用棧如下:
本文將介紹Qt app在視窗系統(tǒng)回調(diào)后,事件又是怎么一步步通過QApplication分發(fā)給最終事件的接受和處理者QWidget::event, (QWidget繼承Object,重載其虛函數(shù)event),以下所有的討論都將嵌入在源碼之中。
1 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 2 bool QETWidget::translateMouseEvent(const MSG &msg) 3 bool QApplicationPrivate::sendMouseEvent(...) 4 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) 5 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event) 6 bool QApplication::notify(QObject *receiver, QEvent *e) 7 bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) 8 bool QWidget::event(QEvent *event)section7 == section2-1
1 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 2 {3 ...4 //檢查message是否屬于Qt可轉(zhuǎn)義的鼠標(biāo)事件5 if (qt_is_translatable_mouse_event(message)) {6 if (QApplication::activePopupWidget() != 0) { // in popup mode7 POINT curPos = msg.pt;8 //取得鼠標(biāo)點(diǎn)擊坐標(biāo)所在的QWidget指針,它指向我們在main創(chuàng)建的widget實(shí)例9 QWidget* w = QApplication::widgetAt(curPos.x, curPos.y); 10 if (w) 11 widget = (QETWidget*)w; 12 } 13 14 if (!qt_tabletChokeMouse) { 15 //對,就在這里。Windows的回調(diào)函數(shù)將鼠標(biāo)事件分發(fā)回給了Qt Widget 16 // => Section 2-2 17 result = widget->translateMouseEvent(msg); // mouse event 18 ... 19 }- //?Section?2-2??$QTDIR\src\gui\kernel\qapplication_win.cpp??? ?
- //該函數(shù)所在與Windows平臺相關(guān),主要職責(zé)就是把已windows格式打包的鼠標(biāo)事件解包、翻譯成QApplication可識別的QMouseEvent,QWidget.
//?Section?2-3?$QTDIR\src\gui\kernel\qapplication.cpp??
1 bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,2 QWidget *alienWidget, QWidget *nativeWidget,3 QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,4 bool spontaneous)5 {6 ...7 //至此與平臺相關(guān)代碼處理完畢8 //MouseEvent默認(rèn)的發(fā)送方式是spontaneous, 所以將執(zhí)行9 //sendSpontaneousEvent。 sendSpontaneousEvent() 與 sendEvent的代碼實(shí)現(xiàn)幾乎相同 10 //除了將QEvent的屬性spontaneous標(biāo)記不同。 這里是解釋什么spontaneous事件:如果事件由應(yīng)用程序之外產(chǎn)生的,比如一個系統(tǒng)事件。 11 //顯然MousePress事件是由視窗系統(tǒng)產(chǎn)生的一個的事件(詳見上文Section 1~ Section 7),因此它是 spontaneous事件 12 if (spontaneous) 13 result = QApplication::sendSpontaneousEvent(receiver, event); 14 else 15 result = QApplication::sendEvent(receiver, event); 16 17 ... 18 19 return result; 20 }//?Section?2-4?C:\Qt\4.7.1-Vs\src\corelib\kernel\qcoreapplication.h
1 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) 2 { 3 //將event標(biāo)記為自發(fā)事件 4 //進(jìn)一步調(diào)用 2-5 QCoreApplication::notifyInternal 5 if (event) 6 event->spont = true; 7 return self ? self->notifyInternal(receiver, event) : false; 8 }//?Section?2-5:??$QTDIR\gui\kernel\qapplication.cpp?????
1 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)2 {3 // 幾行代碼對于Qt Jambi (QT Java綁定版本) 和QSA (QT Script for Application)的支持4 5 ...6 7 // 以下代碼主要意圖為Qt強(qiáng)制事件只能夠發(fā)送給當(dāng)前線程里的對象,也就是說receiver->d_func()->threadData應(yīng)該等于QThreadData::current()。8 //注意,跨線程的事件需要借助Event Loop來派發(fā)9 QObjectPrivate *d = receiver->d_func(); 10 QThreadData *threadData = d->threadData; 11 ++threadData->loopLevel; 12 13 //哇,終于來到大名鼎鼎的函數(shù)QCoreApplication::nofity()了 ==> Section 2-6 14 QT_TRY { 15 returnValue = notify(receiver, event); 16 } QT_CATCH (...) { 17 --threadData->loopLevel; 18 QT_RETHROW; 19 } 20 21 ... 22 23 return returnValue; 24 }- //?Section?2-6:??$QTDIR\gui\kernel\qapplication.cpp??? ?
- //?QCoreApplication::notify和它的重載函數(shù)QApplication::notify在Qt的派發(fā)過程中起到核心的作用,Qt的官方文檔時這樣說的:
- //任何線程的任何對象的所有事件在發(fā)送時都會調(diào)用notify函數(shù)。
notify 調(diào)用 notify_helper()
//?Section?2-7:??$QTDIR\gui\kernel\qapplication.cpp?????
//?Section?2-8??$QTDIR\gui\kernel\qwidget.cpp??
//?QApplication通過notify及其私有類notify_helper,將事件最終派發(fā)給了QObject的子類-?QWidget.
1 bool QWidget::event(QEvent *event)2 {3 ...4 5 switch (event->type()) {6 case QEvent::MouseMove:7 mouseMoveEvent((QMouseEvent*)event);8 break;9 10 case QEvent::MouseButtonPress: 11 // Don't reset input context here. Whether reset or not is 12 // a responsibility of input method. reset() will be 13 // called by mouseHandler() of input method if necessary 14 // via mousePressEvent() of text widgets. 15 #if 0 16 resetInputContext(); 17 #endif 18 mousePressEvent((QMouseEvent*)event); 19 break; 20 21 ... 22 23 }總結(jié)
以上是生活随笔為你收集整理的Qt 事件处理机制-qt源码解读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt-Threads和QObjects详
- 下一篇: apache 官方 Dubbo 文档