【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()函數之前做一些自己的操作,比如,對話框上某些組件可能並不需要響應回車鍵按下的事件,此時,你就需要重新定義組件的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()函數,那就是在窗口關閉的時候。
如果你在窗口關閉時需要有個詢問對話框,那么就需要這么去寫:
1 void MainWindow::closeEvent(QCloseEvent * event) 2 { 3 if (continueToClose()) 4 { 5 event->accept(); 6 } 7 else 8 { 9 event->ignore(); 10 } 11 }
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的事件循環,大概來講,事件循環如下面所示:
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事件循環的過程
首先,事件循環處理所有的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】事件產生
事件產生詳細過程:
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)
【13】事件分發
事件分發詳細過程:
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 }
【14】Qt5.3.2版本事件機制源碼調試
事件產生於分發調試堆棧圖如下:
【15】總結
到此為止。
Good Good Study, Day Day Up.
順序 選擇 循環 總結