1. 程式人生 > >QT中訊息處理機制

QT中訊息處理機制

Qt 事件系統 Qt是事件驅動的, 程式每個動作都是由某個事件所觸發。 Qt事件的型別很多,我們可以通過檢視Qt的 manual中的Event System 和 QEvent 來獲得各個事件的詳細資訊。

為了完整起見,一份Qt4.6的事件列表附在本文後面。

事件來源
Spontaneous events(自發事件) 
從系統得到的訊息,比如滑鼠按鍵,鍵盤按鍵等。Qt事件迴圈的時候讀取這些事件,轉化為QEvent後依次處理
Posted events 
有Qt或應用程式產生,放入訊息佇列
QCoreApplication::postEvent()
Sent events 
由Qt或應用程式產生,不放入佇列,直接被派發和處理

QCoreApplication::sendEvent()
比如考慮重繪事件處理函式 paintEvent(),3種事件都能使得該函式被呼叫:

當視窗被其他視窗覆蓋後,再次重新顯示時,系統將產生 spontaneous 事件來請求重繪
當我們呼叫 update() 時,產生的是 Posted 事件
當我們呼叫 repaint() 時,產生的是 Sent 事件
事件派發事件迴圈 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();
}
}
先處理Qt事件佇列中的事件,直至為空
再處理系統訊息佇列中的訊息,直至為空
在處理系統訊息的時候會產生新的Qt事件,需要對其再次進行處理
不通過事件迴圈
sendEvent的事件派發不通過事件迴圈。QApplication::sendEvent()是通過呼叫QApplication::notify(),直接進入了事件的派發和處理環節,是同步的。

sendEvent與postEvent的使用
兩個函式都是接受一個 QObject * 和一個 QEvent * 作為引數。

postEvent 的 event 必須分配在 heep 上。用完後會被Qt自動刪除
sendEvent 的 event 必須分配在 stack 上。
例子(傳送X按鍵事件到mainWin):

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

QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0);
QApplication::sendEvent(mainWin, &event);

notify
所有的事件都最終通過 notify 派發到相應的物件中。

bool QCoreApplication::notify ( QObject * receiver, QEvent * event )事件過濾
看看notify()呼叫的內部函式notify_helper()的原始碼部分:

先通過 Applicaton 安裝的過濾器
如果未被過濾,再通過 receiver 安裝的過濾器
如果仍未被過濾,才呼叫 receiver->event() 函式進行派發

/*!\internal

Helper function called by notify()
*/
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
// send to all application event filters
if (sendThroughApplicationEventFilters(receiver, event))
return true;
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, event))
return true;
// deliver the event
return receiver->event(event);
}
事件在傳遞到物件之前(呼叫obj->event()函式之前),要先能通過 Applicaton 和 obj 安裝的過濾器,那麼過濾器是怎麼安裝的:

首先QObject中有一個型別為QObjectList的成員變數,名字為eventFilters
當某個QObject安裝了事件過濾器之後, 它會將filterObj的指標儲存在eventFilters中

monitoredObj->installEventFilter(filterObj);
在事件到達QObject::event()函式之前,會先檢視該物件的eventFilters列表, 如果非空, 就先呼叫列表中物件的eventFilter()函式.

bool QObject::eventFilter ( QObject * watched, QEvent * event )
事件過濾器函式eventFilter()返回值是bool型 
如果返回true, 則表示該事件已經被處理完畢, Qt將直接返回, 進行下一事件的處理
如果返回false, 事件將接著被送往剩下的事件過濾器或是目標物件進行處理
對於 QCoreApplication ,由於也是QObject 派生類,安裝過濾器方式與前述相同。

事件轉發
對於某些類別的事件, 如果在整個事件的派發過程結束後還沒有被處理, 那麼這個事件將會向上轉發給它的父widget, 直到最頂層視窗.

如何判斷一個事件是否被處理了呢? (有兩個層次)

QApplication::notify(), QObject::eventFilter(), QObject::event() 通過返回bool值來表示是否已處理. “真”表示已經處理, “假”表示事件需要繼續傳遞
另一種是呼叫QEvent::ignore() 或 QEvent::accept() 對事件進行標識,accept表示事件被處理
為清楚起見,貼一點Qt的原始碼(來自 QApplication::notify()):

case QEvent::ToolTip:
case QEvent::WhatsThis:
case QEvent::QueryWhatsThis:
{
QWidget* w = static_cast<QWidget *>(receiver);
QHelpEvent *help = static_cast<QHelpEvent*>(e);
QPoint relpos = help->pos();
bool eventAccepted = help->isAccepted();
while (w) {
QHelpEvent he(help->type(), relpos, help->globalPos());
he.spont = e->spontaneous();
res = d->notify_helper(w, w == receiver ? help : &he);
e->spont = false;
eventAccepted = (w == receiver ? help : &he)->isAccepted();
if ((res && eventAccepted) || w->isWindow())
break;

relpos += w->pos();
w = w->parentWidget();
}
help->setAccepted(eventAccepted);
}
break;
這兒顯示了對 WhatsThis 事件的處理:先派發給 w,如果事件被accepted 或已經是頂級視窗,則停止;否則獲取w的父物件,繼續派發。

事件處理
重新實現一個特定的事件handler
QObject與QWidget提供了許多特定的事件handlers,分別對應於不同的事件型別。(如paintEvent()對應paint事件)

重新實現QObject::event()
event()函式是所有物件事件的入口,QObject和QWidget中預設的實現是簡單地把事件推入特定的事件handlers。

在QObject安裝上事件過濾器
事件過濾器是一個物件,它接收別的物件的事件,在這些事件到達指定目標之間。

在aApp上安裝一個事件過濾器,它會監視程式中傳送到所有物件的所有事件
重新實現QApplication:notify(),Qt的事件迴圈與sendEvent()呼叫這個函式來分發事件,通過重寫它,你可以在別人之前看到事件。
事件列表
Qt4.6的事件列表:

QAccessibleEvent
QActionEvent
QChildEvent
QCloseEvent
QCustomEvent
QDragLeaveEvent
QDropEvent 
QDragMoveEvent 
QDragEnterEvent
QDynamicPropertyChangeEvent
QFileOpenEvent
QFocusEvent
QGestureEvent
QGraphicsSceneEvent 
QGraphicsSceneContextMenuEvent
QGraphicsSceneDragDropEvent
QGraphicsSceneHelpEvent
QGraphicsSceneHoverEvent
QGraphicsSceneMouseEvent
QGraphicsSceneMoveEvent
QGraphicsSceneResizeEvent
QGraphicsSceneWheelEvent.
QHelpEvent
QHideEvent
QHoverEvent
QIconDragEvent
QInputEvent 
QContextMenuEvent
QKeyEvent
QMouseEvent
QTabletEvent
QTouchEvent
QWheelEvent
QInputMethodEvent
QMoveEvent
QPaintEvent
QResizeEvent
QShortcutEvent
QShowEvent
QStatusTipEvent
QTimerEvent
QWhatsThisClickedEvent
QWindowStateChangeEvent