處理監控系統的時候遇到問題,在MainWidget中創建多個子Widget的時候,原意是想鼠標點擊先讓MainWidget截獲處理后再分派給子Widget去處理,但調試后發現如果子Widget重新實現了事件方法,就直接處理掉事件了,沒有進到MainWidget的處理方法中去,如果子Widget沒有accept或ignore該事件,則該事件就會被傳遞給其父親,在子Widget存在accept或ignore事件的時候,想要經過一下MainWidget的處理方法,就得用到事件處理器,因此網上找了一下,發現QT的事件處理器可以處理。
QT將事件封裝為QEvent實例以后,會呼叫QObject的event()方法,並且將QEvent實例傳送給它,在某些情況下,希望在執行event()之前,先對一些事件進行處理或過濾,然后再決定是否呼叫event()方法,這時候可以使用事件過濾器。
可以重新定義一個繼承自QObject(或其子類)的類的eventFilter()方法,
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab)
{
// 處理Tab鍵
return true;
}
}
return false;
}
eventFilter()的object參數表示事件發生的來源物件,eventFilter()若返回false,則安裝該事件過濾器的對象的event()會繼續執行,若返回true,則安裝事件過濾器的對象后event()方法就不會被執行,由此進行事件的攔截處理。給本對象安裝事件過濾器:
this->installEventFilter(this);
Qt事件的類型很多, 常見的qt的事件如下:
鍵盤事件: 按鍵按下和松開.
鼠標事件: 鼠標移動,鼠標按鍵的按下和松開.
拖放事件: 用鼠標進行拖放.
滾輪事件: 鼠標滾輪滾動.
繪屏事件: 重繪屏幕的某些部分.
定時事件: 定時器到時.
焦點事件: 鍵盤焦點移動.
進入和離開事件: 鼠標移入widget之內,或是移出.
移動事件: widget的位置改變.
大小改變事件: widget的大小改變.
顯示和隱藏事件: widget顯示和隱藏.
窗口事件: 窗口是否為當前窗口.
還有一些非常見的qt事件,比如socket事件,剪貼板事件,字體改變,布局改變等等.
Qt的事件和Qt中的signal不一樣. 后者通常用來"使用"widget, 而前者用來"實現" widget. 比如一個按鈕, 我們使用這個按鈕的時候, 我們只關心他clicked()的signal, 至於這個按鈕如何接收處理鼠標事件,再發射這個信號,我們是不用關心的. 但是如果我們要重載一個按鈕的時候,我們就要面對event了. 比如我們可以改變它的行為,在鼠標按鍵按下的時候(mouse press event) 就觸發clicked()的signal而不是通常在釋放的( mouse release event)時候.
事件的產生
事件的兩種來源:
一種是系統產生的;通常是window system把從系統得到的消息,比如鼠標按鍵,鍵盤按鍵等, 放入系統的消息隊列中. Qt事件循環的時候讀取這些事件,轉化為QEvent,再依次處理.
一種是由Qt應用程序程序自身產生的.程序產生事件有兩種方式, 一種是調用QApplication::postEvent(). 例如QWidget::update()函數,當需要重新繪制屏幕時,程序調用update()函數,new出來一個paintEvent,調用QApplication::postEvent(),將其放入Qt的消息隊列中,等待依次被處理. 另一種方式是調用sendEvent()函數. 這時候事件不會放入隊列, 而是直接被派發和處理, QWidget::repaint()函數用的就是這種方式.
事件的調度
兩種調度方式,一種是同步的, 一種是異步.
Qt的事件循環是異步的,當調用QApplication::exec()時,就進入了事件循環. 該循環可以簡化的描述為如下的代碼:
while ( !app_exit_loop ) {
while( !postedEvents ) { processPostedEvents() }
while( !qwsEvnts ){ qwsProcessEvents(); }
while( !postedEvents ) { processPostedEvents() }
}
先處理Qt事件隊列中的事件, 直至為空. 再處理系統消息隊列中的消息, 直至為空, 在處理系統消息的時候會產生新的Qt事件, 需要對其再次進行處理.
調用QApplication::sendEvent的時候, 消息會立即被處理,是同步的. 實際上QApplication::sendEvent()是通過調用QApplication::notify(), 直接進入了事件的派發和處理環節.
事件的派發和處理
首先說明Qt中事件過濾器的概念. 事件過濾器是Qt中一個獨特的事件處理機制, 功能強大而且使用起來靈活方便. 通過它, 可以讓一個對象偵聽攔截另外一個對象的事件. 事件過濾器是這樣實現的: 在所有Qt對象的基類: QObject中有一個類型為QObjectList的成員變量,名字為eventFilters,當某個QObjec (qobjA)給另一個QObject (qobjB)安裝了事件過濾器之后, qobjB會把qobjA的指針保存在eventFilters中. 在qobjB處理事件之前,會先去檢查eventFilters列表, 如果非空, 就先調用列表中對象的eventFilter()函數. 一個對象可以給多個對象安裝過濾器. 同樣, 一個對象能同時被安裝多個過濾器, 在事件到達之后, 這些過濾器以安裝次序的反序被調用. 事件過濾器函數( eventFilter() ) 返回值是bool型, 如果返回true, 則表示該事件已經被處理完畢, Qt將直接返回, 進行下一事件的處理; 如果返回false, 事件將接着被送往剩下的事件過濾器或是目標對象進行處理.
Qt中,事件的派發是從QApplication::notify() 開始的, 因為QAppliction也是繼承自QObject, 所以先檢查QAppliation對象, 如果有事件過濾器安裝在qApp上, 先調用這些事件過濾器. 接下來QApplication::notify() 會過濾或合並一些事件(比如失效widget的鼠標事件會被過濾掉, 而同一區域重復的繪圖事件會被合並). 之后,事件被送到reciver::event() 處理.
同樣, 在reciver::event()中, 先檢查有無事件過濾器安裝在reciever上. 若有, 則調用之. 接下來,根據QEvent的類型, 調用相應的特定事件處理函數. 一些常見的事件都有特定事件處理函數, 比如:mousePressEvent(), focusOutEvent(), resizeEvent(), paintEvent(), resizeEvent()等等. 在實際應用中, 經常需要重載這些特定事件處理函數在處理事件. 但對於那些不常見的事件, 是沒有相對應的特定事件處理函數的. 如果要處理這些事件, 就需要使用別的辦法, 比如重載event() 函數, 或是安裝事件過濾器.
事件的轉發
對於某些類別的事件, 如果在整個事件的派發過程結束后還沒有被處理, 那么這個事件將會向上轉發給它的父widget, 直到最頂層窗口. 如圖所示, 事件最先發送給QCheckBox, 如果QCheckBox沒有處理, 那么由QGroupBox接着處理, 如果QGroupBox沒有處理, 再送到QDialog, 因為QDialog已經是最頂層widget, 所以如果QDialog不處理, QEvent將停止轉發.
如何判斷一個事件是否被處理了呢? Qt中和事件相關的函數通過兩種方式相互通信. QApplication::notify(), QObject::eventFilter(), QObject::event() 通過返回bool值來表示是否已處理. “真”表示已經處理, “假”表示事件需要繼續傳遞. 另一種是調用QEvent::ignore() 或 QEvent::accept() 對事件進行標識. 這種方式只用於event() 函數和特定事件處理函數之間的溝通. 而且只有用在某些類別事件上是有意義的, 這些事件就是上面提到的那些會被轉發的事件, 包括: 鼠標, 滾輪, 按鍵等事件.
實際應用
1.重載特定事件處理函數
最常見的事件處理辦法就是重載象mousePressEvent(), keyPressEvent(), paintEvent() 這樣的特定事件處理函數. 以按鍵事件為例, 一個典型的處理函數如下:
void imageView::keyPressEvent(QKeyEvent * event)
{
switch (event->key()) {
case Key_Plus:
zoomIn();
break;
case Key_Minus:
zoomOut();
break;
case Key_Left:
// …
default:
QWidget::keyPressEvent(event);
}
}
2.重載event()函數
通過重載event()函數,我們可以在事件被特定的事件處理函數處理之前(象keyPressEvent())處理它. 比如, 當我們想改變tab鍵的默認動作時,一般要重載這個函數. 在處理一些不常見的事件(比如:LayoutDirectionChange)時,evnet()也很有用,因為這些函數沒有相應的特定事件處理函數. 當我們重載event()函數時, 需要調用父類的event()函數來處理我們不需要處理或是不清楚如何處理的事件.
下面這個例子演示了如何重載event()函數, 改變Tab鍵的默認動作: (默認的是鍵盤焦點移動到下一個控件上. )
bool CodeEditor::event(QEvent * event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = (QKeyEvent *) event;
if (keyEvent->key() == Key_Tab) {
insertAtCurrentPosition('\t');
return true;
}
}
return QWidget::event(event);
}
3.在QT對象上安裝事件過濾器
安裝事件過濾器有兩個步驟: (假設要用A來監視過濾B的事件)
首先調用B的installEventFilter( const QOject *obj ), 以A的指針作為參數. 這樣所有發往B的事件都將先由A的eventFilter()處理.
然后, A要重載QObject::eventFilter()函數, 在eventFilter() 中書寫對事件進行處理的代碼.
用這種方法改寫上面的例子: (假設我們將CodeEditor 放在MainWidget中)
MainWidget::MainWidget()
{
// …
CodeEditor * ce = new CodeEditor( this, “code editor”);
ce->installEventFilter( this );
// …
}
bool MainWidget::eventFilter( QOject * target , QEvent * event )
{
if( target == ce ){
if( event->type() == QEvent::KeyPress ) {
QKeyEvent *ke = (QKeyEvent *) event;
if( ke->key() == Key_Tab ){
ce->insertAtCurrentPosition('\t');
return true;
}
}
}
return false;
}
4.給QAppliction對象安裝事件過濾器
一旦我們給qApp(每個程序中唯一的QApplication對象)裝上過濾器,那么所有的事件在發往任何其他的過濾器時,都要先經過當前這個eventFilter(). 在debug的時候,這個辦法就非常有用, 也常常被用來處理失效了的widget的鼠標事件,通常這些事件會被QApplication::notify()丟掉. ( 在QApplication::notify() 中, 是先調用qApp的過濾器, 再對事件進行分析, 以決定是否合並或丟棄)
5.繼承QApplication類,並重載notify()函數
Qt是用QApplication::notify()函數來分發事件的.想要在任何事件過濾器查看任何事件之前先得到這些事件,重載這個函數是唯一的辦法. 通常來說事件過濾器更好用一些, 因為不需要去繼承QApplication類. 而且可以給QApplication對象安裝任意個數的事件過濾器, 相比之下, notify()函數只有一個.
總結:
Qt中,事件的派發是從QApplication::notify() 開始的, 因為QAppliction也是繼承自QObject, 所以先檢查QAppliation對象, 如果有事件過濾器安裝在qApp上, 先調用這些事件過濾器. 接下來QApplication::notify() 會過濾或合並一些事件(比如失效widget的鼠標事件會被過濾掉, 而同一區域重復的繪圖事件會被合並). 之后,事件被送到reciver::event() 處理.
同樣, 在reciver::event()中, 先檢查有無事件過濾器安裝在reciever上. 若有, 則調用之. 接下來,根據QEvent的類型, 調用相應的特定事件處理函數.
在QApplication::notify() 中, 是先調用qApp的過濾器, 再對事件進行分析;Qt是用QApplication::notify()函數來分發事件的.想要在任何事件過濾器查看任何事件之前先得到這些事件,重載這個函數是唯一的辦法.
所以,notify先調用qApp的過濾器, 再對事件進行分析;而reciver::event()中,也會先檢查有無事件過濾器安裝在reciever上.證據如下(網上搜索“Qt中事件分發源代碼剖析“,網址是http://blog.csdn.net/chenlong12580/article/details/25009095):
notify_helper傳遞的順序是:首先傳遞給全局的事件過濾器,再傳遞給目標對象的事件過濾器,最終傳遞給目標對象。
還有一個類似的證據:網上搜索“Qt事件處理機制整個流程--以鼠標在一個窗口中點擊為例“,網址是http://blog.csdn.net/zgrjkflmkyc/article/details/44240729,內容如下:
1.QT_TRY {
2. //哇,終於來到大名鼎鼎的函數QCoreApplication::nofity()了 ==> Section 2-6
3. returnValue = notify(receiver, event);
4. } QT_CATCH (...) {
5. --threadData->loopLevel;
6. QT_RETHROW;
7. }
8.}
9.// Section 2-6: $QTDIR\gui\kernel\qapplication.cpp
10. // QCoreApplication::notify和它的重載函數QApplication::notify在Qt的派發過程中起到核心的作用,Qt的官方文檔時這樣說的:
11. 任何線程的任何對象的所有事件在發送時都會調用notify函數。
12. bool QApplication::notify(QObject *receiver, QEvent *e)
13. {
14. //代碼很長,最主要的是一個大大的Switch,Case
15. ..
16. switch ( e->type())
17. {
18. ...
19. case QEvent::MouseButtonPress:
20. case QEvent::MouseButtonRelease:
21. case QEvent::MouseButtonDblClick:
22. case QEvent::MouseMove:
23. ...
24. //讓自己私有類(d是私有類的句柄)來進一步處理 ==> Section 2-7
25. res = d->notify_helper(w, w == receiver ? mouse : &me);
26. e->spont = false;
27. break;
28. }
29. ...
30. }
31. // Section 2-7: $QTDIR\gui\kernel\qapplication.cpp
32. bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
33. {
34. ...
35. // 向事件過濾器發送該事件,這里介紹一下Event Filters. 事件過濾器是一個接受即將發送給目標對象所有事件的對象。
36. //如代碼所示它開始處理事件在目標對象行動之前。過濾器的QObject::eventFilter()實現被調用,能接受或者丟棄過濾,
37. 允許或者拒絕事件的更進一步的處理。如果所有的事件過濾器允許更進一步的事件處理,事件將被發送到目標對象本身。
38. 如果他們中的一個停止處理,目標和任何后來的事件過濾器不能看到任何事件。
39. if (sendThroughObjectEventFilters(receiver, e))
40. return true;
41. // 遞交事件給receiver => Section 2-8
42. bool consumed = receiver->event(e);
43. e->spont = false;
44. }
45. // Section 2-8 $QTDIR\gui\kernel\qwidget.cpp
46. // QApplication通過notify及其私有類notify_helper,將事件最終派發給了QObject的子類- QWidget.
47. bool QWidget::event(QEvent *event)
48. {
49. ...
50. switch(event->type()) {
51. case QEvent::MouseButtonPress:
52. // Don't reset input context here. Whether reset or not is
53. // a responsibility of input method. reset() will be
54. // called by mouseHandler() of input method if necessary
55. // via mousePressEvent() of text widgets.
56. #if 0
57. resetInputContext();
58. #endif
59. //mousePressEvent是虛函數,QWidget的子類可以通過重載重新定義mousePress事件的行為
60. mousePressEvent((QMouseEvent*)event);
61. break;
}
事件的轉發
對於某些類別的事件, 如果在整個事件的派發過程結束后還沒有被處理, 那么這個事件將會向上轉發給它的父widget, 直到最頂層窗口.
總結:(QT學習之路p55)
事件的調用最終都會調用 QCoreApplication的 notify()函數,因此,最大的控制權實際上是重寫QCoreApplication的 notify()函數。由此可以看出, Qt的事件處理實際上是分層五個層次:重定義事件處理函數,重定義 event()函數,為單個組件安裝事件過濾器(這里應該指的是上面的notify_helper源碼中目標對象的事件過濾器),為 QApplication 安裝事件過濾器(這里應該指的是上面的notify_helper源碼中全局的事件過濾器),重定義QCoreApplication的 notify()函數。這幾個層次的控制權是逐層增大的。
http://blog.csdn.net/u013281495/article/details/51049577