qt事件機制

pamxy發表於2013-05-06

轉自:http://www.cnblogs.com/li-hao/archive/2011/11/13/2247662.html

學習了一段時間的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(); 

}


相關文章