Qt:事件過濾器詳解
Qt的事件模型一個強大的功能是:一個QObject物件能夠監視傳送其他QObject物件的事件,在事件到達之前對其進行處理。
假設我們有一個CustomerInfoDialog控制元件,由一些QLineEdit控制元件組成。我們希望使用Space鍵得到下一個QLineEdit的輸入焦點。
一個最直接的方法是繼承QLineEdit重寫keyPressEvent()函式,當點選了Space鍵時,呼叫focusNextChild():
void MyLineEdit::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Space) { focusNextChild(); } else { QLineEdit::keyPressEvent(event); } }
這個方法有一個最大的缺點:如果我們在窗體中使用了很多不同型別的控制元件(QComboBox,QSpinBox等等),
我們也要繼承這些控制元件,重寫它們的keyPressEvent()。一個更好的解決方法是讓CustomerInfoDialog監視其子控制元件的鍵盤事件,
在監視程式碼處實現以上功能。這就是事件過濾的方法。實現一個事件過濾包括兩個步驟:
1. 在目標物件上呼叫installEventFilter(),註冊監視物件。
2. 在監視物件的eventFilter()函式中處理目標物件的事件。
註冊監視物件的位置是在CustomerInfoDialog的建構函式中:
CustomerInfoDialog::CustomerInfoDialog(QWidget *parent) : QDialog(parent)
{
...
firstNameEdit->installEventFilter(this);
lastNameEdit->installEventFilter(this);
cityEdit->installEventFilter(this);
phoneNumberEdit->installEventFilter(this);
}
事件過濾器註冊後,傳送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit控制元件的事件首先到達CustomerInfoDialog::eventFilter()函式,
下面是eventFilter()函式的程式碼:
bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
{
if (target == firstNameEdit || target == lastNameEdit
|| target == cityEdit || target == phoneNumberEdit)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space)
{
focusNextChild();
return true;
}
}
}
return QDialog::eventFilter(target, event);
}
首先,我們看是目標控制元件是否為QLineEdit,如果事件為鍵盤事件,把QEvent轉換為QKeyEvent,確定被敲擊的鍵。如果為Space鍵,呼叫focusNextChild(),把焦點交給下一個控制元件,返回true通知Qt已經處理了這個事件,如果返回false,Qt將會把事件傳遞給目標控制元件,把一個空格字元插入到QLineEdit中。
如果目標控制元件不是QLineEdit,或者事件不是Space敲擊事件,把控制權交給基類QDialog的eventFilter()。目標控制元件也可以是基類QDialog正在監視的控制元件。(在Qt4.1中,QDialog沒有監視的控制元件,但是Qt的其他控制元件類,如QScrollArea,監視一些它們的子控制元件)
Qt的事件處理有5中級別:
1. 重寫控制元件的事件處理函式:如重寫keyPressEvent(),mousePressEvent()和paintEvent(),這是最常用的事件處理方法,我們已經看到過很多這樣的例子了。
2. 重寫QObject::event(),在事件到達事件處理函式時處理它。在需要改變Tab鍵的慣用法時這樣做。也可以處理那些沒有特定事件處理函式的比較少見的事件型別(例如,QEvent::HoverEnter)。我們重寫event()時,必須要呼叫基類的event(),由基類處理我們不需要處理的那些情況。
3. 給QObject物件安裝事件過濾器:物件用installEventFilter()後,所有達到目標控制元件的事件都首先到達監視物件的eventFilter()函式。如果一個物件有多個事件過濾器,過濾器按順序啟用,先到達最近安裝的監視物件,最後到達最先安裝的監視物件。
4. 給QApplication安裝事件過濾器,如果qApp(唯一的QApplication物件)安裝了事件過濾器,程式中所有物件的事件都要送到eventFilter()函式中。這個方法在除錯的時候非常有用,在處理非活動狀態控制元件的滑鼠事件時這個方法也很常用。
5. 繼承QApplication,重寫notify()。Qt呼叫QApplication::nofity()來發送事件。重寫這個函式是在其他事件過濾器處理事件前得到所有事件的唯一方法。通常事件過濾器是最有用的,因為在同一時間,可以有任意數量的事件過濾器,但是notify()函式只有一個。
許多事件型別,包括滑鼠,鍵盤事件,是能夠傳播的。如果事件在到達目標物件的途中或者由目標物件處理掉,事件處理的過程會重新開始,不同的是這時的目標物件是原目標物件的父控制元件。這樣從父控制元件再到父控制元件,知道有控制元件處理這個事件或者到達了最頂級的那個控制元件。
下圖顯示了一個鍵盤事件在一個對話方塊中從子控制元件到父控制元件的傳播過程。當用戶敲擊一個鍵盤,時間首先發送到有焦點的控制元件上(這個例子中是QCheckBox)。如果QCheckBox沒有處理這個事件,Qt把事件傳送到QGroupBox中,如果仍然沒有處理,則最後傳送到QDialog中。