qt事件機制

學習了一段時間的Qt之後,發現Qt的事件機制和其他語言的機制有些不同。Qt除了能夠通過信號和槽機制來實現一些Action動作之外,還可以用對象所帶的事件,或者用戶自定義的事件來實現對象的一些行爲處理。

現在,我們從頭開始講解。

到底什麼是事件呢? 
事件起源: 基於事件如何被產生與分發,可以把事件分爲以下三類。

Spontaneous 事件——自發事件

由窗口系統產生,它們被放到系統隊列中,通過事件循環逐個處理。

Posted 事件

由Qt或是應用程序產生,它們被Qt組成隊列,再通過事件循環處理。

Sent 事件

由Qt或是應用程序產生,但它們被直接發送到目標對象。

Qt事件循環的過程

當我們在main()函數的末尾調用QApplication::exec()時,程序進入了Qt的事件循環,大概來講,事件循環如下面所示: 
while (!exit_was_called) 

while(!posted_event_queue_is_empty) 

process_next_posted_event(); 

while(!spontaneous_event_queue_is_empty) 

process_next_spontaneous_event(); 

while(!posted_event_queue_is_empty) 

process_next_posted_event(); 

}

首 先,事件循環處理所有的posted事件,直到隊列空。 
然後再處理所有的spontaneous事件,最後它處理所有的因爲處理spontaneous事 件而產生的posted事件。 
send 事件並不在事件循環內處理,它們都直接被髮送到了目標對象。 
現在看一下實踐中的paint 事件是如何工作的。 
當一個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事件。

人工合成的事件

QT應用程序可以產生他們自己的事件,或是預定義類型,或是自定義類型。 這可以通過創建QEvent類或它的子類的實例,並且調用QApplication:postEvent()或QApplication::sendEvent()來實現。 
這兩個函數需要一個 QObject* 與一個QEvent * 作爲參數,假如你調用postEvent(),你必須用 new 操作符來創建事件對象,Qt會它被處理後幫你刪除它。假如你用sendEvent(), 你應該在棧上來創建事件。下面舉兩個例子:

一是posting 事件

QApplication::postEvent(mainWin, new QKeyEvent(QEvent::KeyPress,Key_X,'X',0));

二是sending 事件

QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0); 
QApplication::sendEvent(mainWin, &event); 
Qt應用程序很少直接調用postEvent()或是sendEvnet(),因爲大多數事件會在必要時被Qt或是窗口系統自動產生 
。在大多數的情況下,當你想發送一個事件時,Qt已經爲了準備好了一個更高級的函數來爲你服務。(例如 
update()與repaint())。

定製事件類型

qt允許你創建自己的事件類型,這在多線程的程序中尤其有用。在單線程的程序也相當有用,它可以作爲 
對象間的一種通訊機制。爲什麼你應該用事件而不是其他的標準函數調用,或信號、槽的主要原因是:事件既可用於同步也可用於異步(依賴於你是調用sendEvent()或是postEvents()),函數調用或是槽調用總是同步的。事件的另外一個好處是它可以被過濾。 
演示如何post一個定製事件的代碼片段: 
const QEvent::Type MyEvent = (QEvent::Type)1234; 
... 
QApplication::postEvent(obj, new QCustomEvent(MyEvent)); 
事件必須是QCustomEvent類型(或子類)的。構造函數的參數是事件的類型,1024以下被Qt保留。其他可被程序使用。爲處理定製事件類型,要重新實現customEvent()函數: 
void MyLineEdit::customEvent(QCustomEvent *event) 

if (event->type() == MyEvent) { 
myEvent(); 
} else { 
QLineEdit::customEvent(event); 


QcustomEvent類有一個void *的成員,可用於特定的目的。你也可以子類化QCustomEvent,加上別的成員,但是你也需要在customEvent()中轉換QCustomeEvent到你特有的類型。

事件處理與過濾

Qt中的事件可以在五個不同的層次上被處理

1.重新實現一個特定的事件handler

QObject與QWidget提供了許多特定的事件handlers,分別對應於不同的事件類型。(如paintEvent()對應paint事件)

2.重新實現QObject::event()

event()函數是所有對象事件的入口,QObject和QWidget中缺省的實現是簡單地把事件推入特定的事件handlers。

3.在QObject安裝上事件過濾器

事件過濾器是一個對象,它接收別的對象的事件,在這些事件到達指定目標之間。

4.在aApp上安裝一個事件過濾器

它會監視程序中發送到所有對象的所有事件。

5.重新實現QApplication:notify()

Qt的事件循環與sendEvent()調用這個函數來分發事件,通過重寫它,你可以在別人之前看到事件。

特定對象的事件處理

一些事件類型可以被傳遞。這意味着假如目標對象不處理一個事件,Qt會試着尋找另外的事件接收者。用新的目標來調用QApplication::notify()。舉例來講,key事件是傳遞的,假如擁有焦點的Widget不處理特定鍵,Qt會分發相同的事件給父widget,然後是父親的父親,直到最頂層widget。 
那麼何時接收該事件,何時忽略呢?

通過accept( )函數和ignore( )函數。

可被傳遞的事件有一個accept()函數和一個ignore()函數,你可以用它們來告訴Qt,你“接收”或是 “忽略”這個事件。假如事件handler調用accept(),這個事件將不會再被傳遞。假如事件handler調用 ignore(),Qt會試着查找另外的事件接收者。 像大多數的開發者一樣,你可能不會被調用accept()或是ignore()所煩惱。缺省情況下是“接收”,在 QWidget中的缺省實現是調用ignore(),假如你希望接收事件,你需要做的是重新實現事件handler,避免 調用QWidget的實現。假如你想“忽略”事件,只需簡單地傳遞它到QWidget的實現。下面的代碼演示了這一點: 
void MyFancyWidget::keyPressEvent(QKeyEvent *event) 

if (event->key() == Key_Escape) { 
doEscape(); 
} else { 
QWidget::keyPressEvent(event); 


在上面的例子裏,假如用戶按了"ESC"鍵,我們會調用doEscape()並且事件被“接收”了(這是缺省的情況), 事件不會被傳遞到父widget,假如用戶按了別的鍵,則調用QWidget的缺省實現。 
void QWidget::keyPressEvent(QKeyEvent *event) 

event->ignore(); 

應該感謝ignore(),事件會被傳遞到父widget中去。 
討論到目前爲至,我們都假設基類是QWidget,然而,同樣的規則也可以應用到別的層次中,只要用QWidget 代替基類即可。舉例來說: 
void MyFancyLineEdit::keyPressEvent(QKeyEvent *event) 

if (event->key() == Key_SysReq) { 
doSystemRequest(); 
} else { 
QLineEdit::keyPressEvent(event); 


由於某些原因,你會在event()中處理事件,而不是在特定的handler中,如keyPressEvent(),這個過程會有些不同。event() 會返回一個布爾值,來告訴調用者是否事件被accept或ignore,(true表示accept),從event()中調用accept()或是 ignore()是沒有意義的。“Accept”標記是event()與特定事件handler之間的一種通訊機制。而從event()返回的布爾值卻是 用來與QApplication:notify()通訊的。在QWidgetk中缺省的event()實現是轉換“Accept”標記爲一個布爾值,如下 所示: 
bool QWidget::event(QEvent *event) 

switch (e->type()) { 
case QEvent::KeyPress: 
keyPressEvent((QKeyEvent *)event); 
if (!((QKeyEvent *)event)->isAccepted()) 
return false; 
break; 
case QEvent::KeyRelease: 
keyReleaseEvent((QKeyEvent *)event); 
if (!((QKeyEvent *)event)->isAccepted()) 
return false; 
break; 
... 

return true; 

到現在爲至,我們所說的內容不僅僅適用於key事件,也適用於mouse,wheel,tablet,context menu等事件。 
Close事件有點不同,調用QCloseEvent:ignore()取消了關閉操作,而accept()告訴Qt繼續執行正常的關閉操作。爲了避免混亂,最好是在closeEvent()的新實現中明確地進行accept()與ignore()的調用: 
void MainWindow::closeEvent(QCloseEvent *event) 

if (userReallyWantsToQuit()) { 
event->accept(); 
} else { 
event->ignore(); 

}

發佈了2 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章