Qt 事件機制 學習
Qt程式是事件驅動的, 程式的每個動作都是由幕後某個事件所觸發.。
Qt事件的發生和處理成為程式執行的主線,存在於程式整個生命週期。
Qt事件的型別很多, 常見的qt的事件如下:
鍵盤事件: 按鍵按下和鬆開.
滑鼠事件: 滑鼠移動,滑鼠按鍵的按下和鬆開.
拖放事件: 用滑鼠進行拖放.
滾輪事件: 滑鼠滾輪滾動.
繪屏事件: 重繪螢幕的某些部分.
定時事件: 定時器到時.
焦點事件: 鍵盤焦點移動.
進入和離開事件: 滑鼠移入widget之內,或是移出.
移動事件: widget的位置改變.
大小改變事件: widget的大小改變.
顯示和隱藏事件:widget顯示和隱藏.
視窗事件: 視窗是否為當前視窗.
還有一些非常見的qt事件,比如socket事件,剪貼簿事件,字型改變,佈局改變等等.
交流會圍繞以下三個問題展開:
一、什麼是事件?
二、事件是怎樣被處理的?
三、事件與訊號的區別?
一、什麼是事件?
事件:某個“動作”的完成後,需讓某個物件知道而傳送的訊息。(個人觀點)
解釋:此時的“動作”並非通常意義所指的動作,而是廣義的“動作”,是主動和被動的總和。
例:兩個窗體A和B,當A為最小化狀態時,我們使它最大化,這就會讓A主動產生一個重繪事件;當A和B非最小化狀態,且B位於A窗體之上時,我們讓B最小化,那麼剛才被B遮擋的A窗體就會被動地產生一個重繪事件。
Qt 的事件和Qt中的signal不一樣. 後者通常用來"使用"widget,而前者用來"實現" widget. 比如一個按鈕, 我們使用這個按鈕的時候, 我們只關心他clicked()的signal, 至於這個按鈕如何接收處理滑鼠事件,再發射這個訊號,我們是不用關心的. 但是如果我們要過載一個按鈕的時候,我們就要面對event了. 比如我們可以改變它的行為,在滑鼠按鍵按下的時候(mouse press event) 就觸發clicked()的signal而不是通常在釋放的( mouse release event)時候.
我們按產生來源把事件分為兩類:
(一) 系統產生的;通常是windowsystem把從系統得到的訊息,比如滑鼠按鍵,鍵盤按鍵等, 放入系統的訊息佇列中,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() 函式和特定事件處理函式之間的溝通. 而且只有用在某些類別事件上是有意義的, 這些事件就是上面提到的那些會被轉發的事件, 包括: 滑鼠, 滾輪, 按鍵等事件.
(四)實際運用
根據對Qt事件機制的分析, 我們可以得到5種級別的事件過濾,處理辦法. 以功能從弱到強, 排列如下:
(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物件安裝任意個數的事
三、事件與訊號的區別?
Qt 的事件和Qt中的signal不一樣. 後者通常用來"使用"widget,而前者用來"實現" widget. 比如一個按鈕, 我們使用這個按鈕的時候, 我們只關心他clicked()的signal, 至於這個按鈕如何接收處理滑鼠事件,再發射這個訊號,我們是不用關心的。但是如果我們要過載一個按鈕的時候,我們就要面對event了。 比如我們可以改變它的行為,在滑鼠按鍵按下的時候(mousepress event) 就觸發clicked()的signal而不是通常在釋放的( mouse release event)時候.。
訊號通過事件實現,事件可以過濾,事件更底層,事件是基礎,訊號是擴充套件。
相關文章
- React 原始碼學習(五):事件機制React原始碼事件
- 從EventBus學習擴充套件Weex事件機制套件事件
- Halo 開源專案學習(六):事件監聽機制事件
- 淺談JS事件機制與React事件機制JS事件React
- 一文學會Java事件機制Java事件
- DOM事件機制事件
- redis事件機制Redis事件
- react事件機制React事件
- 從另一個思路來學習安卓事件分發機制安卓事件
- Flutter學習之事件迴圈機制、資料庫、網路請求Flutter事件資料庫
- Redis的事件機制Redis事件
- View事件機制分析View事件
- JS的事件物件與事件機制JS事件物件
- JavaScript執行緒機制與事件機制JavaScript執行緒事件
- Qt學習2QT
- Redis學習之管道機制Redis
- Android事件分發機制Android事件
- JS 事件機制 Event LoopJS事件OOP
- JavaScript 事件迴圈機制JavaScript事件
- Spring事件機制詳解Spring事件
- Redis 事件機制詳解Redis事件
- 【React深入】React事件機制React事件
- JavaScript事件迴圈機制JavaScript事件
- jQuery的事件機制,事件物件介紹,外掛機制,多庫共存,each()jQuery事件物件
- Android10_原理機制系列_事件傳遞機制Android事件
- 學習資料庫索引機制資料庫索引
- MFC學習(四) 訊息機制
- attention注意力機制學習
- 學習筆記(2)IPC機制筆記
- java反射機制的學習心得Java反射
- Qt學習之XMLQTXML
- View事件分發機制分析View事件
- javascript事件迴圈機制EventLoopJavaScript事件OOP
- javascript之事件迴圈機制JavaScript事件
- 深入理解DOM事件機制事件
- Java——事件處理機制概要Java事件
- cocos EventDispatcher事件分發機制事件
- 18.spring系列- 事件機制Spring事件