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纤虽。
自定義事件
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()方法象对。這是一個最原始的檢查事件不響應的終極辦法黑忱。
事件循環(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)
- 線程中的事件循環(huán)白魂,使得線程可以使用那些需要事件循環(huán)的非GUI類(如,QTimer,QTcpSocket,QProcess)上岗。
- 可以把任何線程的signals連接到特定線程的slots福荸,也就是說信號-槽機制是可以跨線程使用的。
- 對于在QApplication之前創(chuàng)建的對象肴掷,QObject::thread()返回0敬锐,這意味著主線程僅為這些對象處理投遞事件,不會為沒有所屬線程的對象處理另外的事件呆瞻。
- 可以用
QObject::moveToThread(QThread *targetThread)
來改變QObject和QObject孩子們的線程親緣關系台夺,假如QObject對象有父親,它不能移動這種關系痴脾。 -
在另一個線程(而不是創(chuàng)建它的那個線程)中delete QObject對象是不安全的颤介。除非你可以保證在同一時刻對象不在處理事件。可以用
QObject::deleteLater()
赞赖,它會投遞一個DeferredDelete事件滚朵,這會被對象線程的事件循環(huán)最終選取到。 - 假如沒有事件循環(huán)運行薯定,事件不會分發(fā)給對象始绍。舉例來說瞳购,假如你在一個線程中創(chuàng)建了一個QTimer對象话侄,但從沒有調用過exec(),那么QTimer就不會發(fā)射它的timeout()信號学赛,對deleteLater()也不會工作年堆。(這同樣適用于主線程)。你可以手工使用線程安全的函數(shù)
QCoreApplication::postEvent()
盏浇,在任何時候变丧,給任何線程中的任何對象投遞一個事件,事件會在那個創(chuàng)建了對象的線程中通過事件循環(huán)派發(fā)绢掰。 -
事件過濾器在所有線程中也被支持痒蓬,不過它限定被監(jiān)視對象與監(jiān)視對象生存在同一線程中童擎。類似地,
QCoreApplication::sendEvent
(不是postEvent())攻晒,僅用于在調用此函數(shù)的線程中向目標對象投遞事件顾复。 - QObject和所有它的子類是非線程安全的。這包括整個的事件投遞系統(tǒng)鲁捏。需要牢記的是芯砸,當你正從別的線程中訪問對象時,事件循環(huán)可以向你的QObject子類投遞事件给梅。假如你調用一個不生存在當前線程中的QObject子類的函數(shù)時假丧,你必須用mutex來保護QObject子類的內部數(shù)據,否則會遭遇災難或非預期結果动羽。
- 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
調用與平臺相關的QAbstractEventDispatcher子類的processEvents接口
實現(xiàn)Windows消息循環(huán)
不斷從Windows系統(tǒng)中PeekMessage逃糟,然后分發(fā)給Windows系統(tǒng)吼鱼,再由Windows系統(tǒng)找到對應的目標窗口,將消息分發(fā)到目標線程的消息回調中WndProc绰咽。
執(zhí)行到QT注冊的消息回調QtWndProc
消息層層傳遞到最后的MyWidget::event
最后總體簡圖-Windows平臺為例
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 事件處理機制 (下篇)