1. 程式人生 > >qt事件處理流程

qt事件處理流程

前面的章節中我們曾經提到event()函式。事件物件建立完畢後,Qt 將這個事件物件傳遞給QObject的event()函式。event()函式並不直接處理事件,而是將這些事件物件按照它們不同的型別,分發給不同的事件處理器(event handler)。

如上所述,event()函式主要用於事件的分發。所以,如果你希望在事件分發之前做一些操作,就可以重寫這個event()函數了。例如,我們希望在一個QWidget元件中監聽 tab 鍵的按下,那麼就可以繼承QWidget,並重寫它的event()函式,來達到這個目的:

bool CustomWidget::event(QEvent *e)
{
    if
(e->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e); if (keyEvent->key() == Qt::Key_Tab) { qDebug() << "You press tab."; return true; } } return QWidget::event(e); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

CustomWidget是一個普通的QWidget子類。我們重寫了它的event()函式,這個函式有一個QEvent物件作為引數,也就是需要轉發的事件物件。函式返回值是 bool 型別。如果傳入的事件已被識別並且處理,則需要返回 true,否則返回 false。如果返回值是 true,並且,該事件物件設定了accept(),那麼 Qt 會認為這個事件已經處理完畢,不會再將這個事件傳送給其它物件,而是會繼續處理事件佇列中的下一事件。注意,在event()函式中,呼叫事件物件的accept()和ignore()函式是沒有作用的,不會影響到事件的傳播。
我們可以通過使用QEvent::type()函式可以檢查事件的實際型別,其返回值是QEvent::Type型別的列舉。我們處理過自己感興趣的事件之後,可以直接返回 true,表示我們已經對此事件進行了處理;對於其它我們不關心的事件,則需要呼叫父類的event()函式繼續轉發,否則這個元件就只能處理我們定義的事件了。為了測試這一種情況,我們可以嘗試下面的程式碼:

bool CustomTextEdit::event(QEvent *e)
{
    if (e->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
        if (keyEvent->key() == Qt::Key_Tab) {
            qDebug() << "You press tab.";
            return true;
        }
    }
    return false
; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

CustomTextEdit是QTextEdit的一個子類。我們重寫了其event()函式,卻沒有呼叫父類的同名函式。這樣,我們的元件就只能處理 Tab 鍵,再也無法輸入任何文字,也不能響應其它事件,比如滑鼠點選之後也不會有光標出現。這是因為我們只處理的KeyPress型別的事件,並且如果不是KeyPress事件,則直接返回 false,滑鼠事件根本不會被轉發,也就沒有了滑鼠事件。

通過檢視QObject::event()的實現,我們可以理解,event()函式同前面的章節中我們所說的事件處理器有什麼聯絡:

//!!! Qt5
bool QObject::event(QEvent *e)
{
    switch (e->type()) {
    caseQEvent::Timer:
        timerEvent((QTimerEvent*)e);
        break;

    caseQEvent::ChildAdded:
    caseQEvent::ChildPolished:
    caseQEvent::ChildRemoved:
        childEvent((QChildEvent*)e);
        break;
    // ...
    default:
        if (e->type() >= QEvent::User) {
            customEvent(e);
            break;
        }
        return false;
    }
    return true;
}
```這是 Qt 5QObject::event()函式的原始碼(Qt 4 的版本也是類似的)。我們可以看到,同前面我們所說的一樣,Qt 也是使用QEvent::type()判斷事件型別,然後呼叫了特定的事件處理器。比如,如果event->type()返回值是QEvent::Timer,則呼叫timerEvent()函式。可以想象,QWidget::event()中一定會有如下的程式碼:





<div class="se-preview-section-delimiter"></div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

這裡寫程式碼片
“`


switch (event->type()) {
case QEvent::MouseMove:
mouseMoveEvent((QMouseEvent*)event);
break;
// ...
}
事實也的確如此。timerEvent()和mouseMoveEvent()這樣的函式,就是我們前面章節所說的事件處理器 event handler。也就是說,event()函式中實際是通過事件處理器來響應一個具體的事件。這相當於event()函式將具體事件的處理“委託”給具體的事件處理器。而這些事件處理器是 protected virtual 的,因此,我們重寫了某一個事件處理器,即可讓 Qt 呼叫我們自己實現的版本。

由此可以見,event()是一個集中處理不同型別的事件的地方。如果你不想重寫一大堆事件處理器,就可以重寫這個event()函式,通過QEvent::type()判斷不同的事件。鑑於重寫event()函式需要十分小心注意父類的同名函式的呼叫,一不留神就可能出現問題,所以一般還是建議只重寫事件處理器(當然,也必須記得是不是應該呼叫父類的同名處理器)。這其實暗示了event()函式的另外一個作用:遮蔽掉某些不需要的事件處理器。正如我們前面的CustomTextEdit例子看到的那樣,我們建立了一個只能響應 tab 鍵的元件。這種作用是重寫事件處理器所不能實現的。