前面的章節中我們曾經提到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);
}
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;
}
CustomTextEdit
是QTextEdit
的一個子類。我們重寫了其event()
函數,卻沒有調用父類的同名函數。這樣,我們的組件就只能處理 Tab 鍵,再也無法輸入任何文本,也不能響應其它事件,比如鼠標點擊之後也不會有光標出現。這是因爲我們只處理的KeyPress
類型的事件,並且如果不是KeyPress
事件,則直接返回 false,鼠標事件根本不會被轉發,也就沒有了鼠標事件。
通過查看QObject::event()
的實現,我們可以理解,event()
函數同前面的章節中我們所說的事件處理器有什麼聯繫:
//!!! Qt5
bool QObject::event(QEvent *e)
{
switch (e->type()) {
case QEvent::Timer:
timerEvent((QTimerEvent*)e);
break;
case QEvent::ChildAdded:
case QEvent::ChildPolished:
case QEvent::ChildRemoved:
childEvent((QChildEvent*)e);
break;
// ...
default:
if (e->type() >= QEvent::User) {
customEvent(e);
break;
}
return false;
}
return true;
}
這是 Qt 5 中QObject::event()
函數的源代碼(Qt 4 的版本也是類似的)。我們可以看到,同前面我們所說的一樣,Qt 也是使用QEvent::type()
判斷事件類型,然後調用了特定的事件處理器。比如,如果event->type()
返回值是QEvent::Timer
,則調用timerEvent()
函數。可以想象,QWidget::event()
中一定會有如下的代碼:
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 鍵的組件。這種作用是重寫事件處理器所不能實現的。