#(21):事件過濾器
有時候,對象需要查看、甚至要攔截發送到另外對象的事件。例如,對話框可能想要攔截按鍵事件,不讓別的組件接收到;或者要修改回車鍵的默認處理。
通過前面的章節,我們已經知道,Qt 創建了`QEvent`事件對象之后,會調用`QObject`的`event()`函數處理事件的分發。顯然,我們可以在`event()`函數中實現攔截的操作。由于`event()`函數是 protected 的,因此,需要繼承已有類。如果組件很多,就需要重寫很多個`event()`函數。這當然相當麻煩,更不用說重寫`event()`函數還得小心一堆問題。好在 Qt 提供了另外一種機制來達到這一目的:事件過濾器。
`QObject`有一個`eventFilter()`函數,用于建立事件過濾器。這個函數的簽名如下:
~~~
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
~~~
這個函數正如其名字顯示的那樣,是一個“事件過濾器”。所謂事件過濾器,可以理解成一種過濾代碼。想想做化學實驗時用到的過濾器,可以將雜質留到濾紙上,讓過濾后的液體溜走。事件過濾器也是如此:它會檢查接收到的事件。如果這個事件是我們感興趣的類型,就進行我們自己的處理;如果不是,就繼續轉發。這個函數返回一個 bool 類型,如果你想將參數 event 過濾出來,比如,不想讓它繼續轉發,就返回 true,否則返回 false。事件過濾器的調用時間是目標對象(也就是參數里面的`watched`對象)接收到事件對象之前。也就是說,如果你在事件過濾器中停止了某個事件,那么,`watched`對象以及以后所有的事件過濾器根本不會知道這么一個事件。
我們來看一段簡單的代碼:
~~~
class MainWindow : public QMainWindow
{
public:
MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *event);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
~~~
`MainWindow`是我們定義的一個類。我們重寫了它的`eventFilter()`函數。為了過濾特定組件上的事件,首先需要判斷這個對象是不是我們感興趣的組件,然后判斷這個事件的類型。在上面的代碼中,我們不想讓`textEdit`組件處理鍵盤按下的事件。所以,首先我們找到這個組件,如果這個事件是鍵盤事件,則直接返回 true,也就是過濾掉了這個事件,其他事件還是要繼續處理,所以返回 false。對于其它的組件,我們并不保證是不是還有過濾器,于是最保險的辦法是調用父類的函數。
`eventFilter()`函數相當于創建了過濾器,然后我們需要安裝這個過濾器。安裝過濾器需要調用`QObject::installEventFilter()`函數。這個函數的簽名如下:
~~~
void QObject::installEventFilter ( QObject * filterObj )
~~~
這個函數接受一個`QObject *`類型的參數。記得剛剛我們說的,`eventFilter()`函數是`QObject`的一個成員函數,因此,任意`QObject`都可以作為事件過濾器(問題在于,如果你沒有重寫`eventFilter()`函數,這個事件過濾器是沒有任何作用的,因為默認什么都不會過濾)。已經存在的過濾器則可以通過`QObject::removeEventFilter()`函數移除。
我們可以向一個對象上面安裝多個事件處理器,只要調用多次`installEventFilter()`函數。如果一個對象存在多個事件過濾器,那么,最后一個安裝的會第一個執行,也就是后進先執行的順序。
還記得我們前面的那個例子嗎?我們使用`event()`函數處理了 Tab 鍵:
~~~
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);
}
~~~
現在,我們可以給出一個使用事件過濾器的版本:
~~~
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if (object == target && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
} else {
return false;
}
}
return false;
}
~~~
事件過濾器的強大之處在于,我們可以為整個應用程序添加一個事件過濾器。記得,`installEventFilter()`函數是`QObject`的函數,`QApplication`或者`QCoreApplication`對象都是`QObject`的子類,因此,我們可以向`QApplication`或者`QCoreApplication`添加事件過濾器。這種全局的事件過濾器將會在所有其它特性對象的事件過濾器之前調用。盡管很強大,但這種行為會嚴重降低整個應用程序的事件分發效率。因此,除非是不得不使用的情況,否則的話我們不應該這么做。
**注意,如果你在事件過濾器中 delete 了某個接收組件,務必將函數返回值設為 true。否則,Qt 還是會將事件分發給這個接收組件,從而導致程序崩潰。**
事件過濾器和被安裝過濾器的組件必須在同一線程,否則,過濾器將不起作用。另外,如果在安裝過濾器之后,這兩個組件到了不同的線程,那么,只有等到二者重新回到同一線程的時候過濾器才會有效。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)