#(22):事件總結
Qt 的事件是整個 Qt 框架的核心機制之一,也比較復雜。說它復雜,更多是因為它涉及到的函數眾多,而處理方法也很多,有時候讓人難以選擇。現在我們簡單總結一下 Qt 中的事件機制。
Qt 中有很多種事件:鼠標事件、鍵盤事件、大小改變的事件、位置移動的事件等等。如何處理這些事件,實際有兩種選擇:
1. 所有事件對應一個事件處理函數,在這個事件處理函數中用一個很大的分支語句進行選擇,其代表作就是 win32 API 的`WndProc()`函數:
~~~
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
~~~
在這個函數中,我們需要使用`switch`語句,選擇`message`參數的類型進行處理,典型代碼是:
~~~
switch(message)
{
case WM_PAINT:
// ...
break;
case WM_DESTROY:
// ...
break;
...
}
~~~
2. 每一種事件對應一個事件處理函數。Qt 就是使用的這么一種機制:
* `mouseEvent()`
* `keyPressEvent()`
* …
Qt 具有這么多種事件處理函數,肯定有一個地方對其進行分發,否則,Qt 怎么知道哪一種事件調用哪一個事件處理函數呢?這個分發的函數,就是`event()`。顯然,當`QMouseEvent`產生之后,`event()`函數將其分發給`mouseEvent()`事件處理器進行處理。
`event()`函數會有兩個問題:
1. `event()`函數是一個 protected 的函數,這意味著我們要想重寫`event()`,必須繼承一個已有的類。試想,我的程序根本不想要鼠標事件,程序中所有組件都不允許處理鼠標事件,是不是我得繼承所有組件,一一重寫其`event()`函數?protected 函數帶來的另外一個問題是,如果我基于第三方庫進行開發,而對方沒有提供源代碼,只有一個鏈接庫,其它都是封裝好的。我怎么去繼承這種庫中的組件呢?
2. `event()`函數的確有一定的控制,不過有時候我的需求更嚴格一些:我希望那些組件根本看不到這種事件。`event()`函數雖然可以攔截,但其實也是接收到了`QMouseEvent`對象。我連讓它收都收不到。這樣做的好處是,模擬一種系統根本沒有那個事件的效果,所以其它組件根本不會收到這個事件,也就無需修改自己的事件處理函數。這種需求怎么辦呢?
這兩個問題是`event()`函數無法處理的。于是,Qt 提供了另外一種解決方案:事件過濾器。事件過濾器給我們一種能力,讓我們能夠完全移除某種事件。事件過濾器可以安裝到任意`QObject`類型上面,并且可以安裝多個。如果要實現全局的事件過濾器,則可以安裝到`QApplication`或者`QCoreApplication`上面。這里需要注意的是,如果使用`installEventFilter()`函數給一個對象安裝事件過濾器,那么該事件過濾器只對該對象有效,只有這個對象的事件需要先傳遞給事件過濾器的`eventFilter()`函數進行過濾,其它對象不受影響。如果給`QApplication`對象安裝事件過濾器,那么該過濾器對程序中的每一個對象都有效,任何對象的事件都是先傳給`eventFilter()`函數。
事件過濾器可以解決剛剛我們提出的`event()`函數的兩點不足:首先,事件過濾器不是 protected 的,因此我們可以向任何`QObject`子類安裝事件過濾器;其次,事件過濾器在目標對象接收到事件之前進行處理,如果我們將事件過濾掉,目標對象根本不會見到這個事件。
事實上,還有一種方法,我們沒有介紹。Qt 事件的調用最終都會追溯到`QCoreApplication::notify()`函數,因此,最大的控制權實際上是重寫`QCoreApplication::notify()`。這個函數的聲明是:
~~~
virtual bool QCoreApplication::notify ( QObject * receiver, QEvent * event );
~~~
該函數會將`event`發送給`receiver`,也就是調用`receiver->event(event)`,其返回值就是來自`receiver`的事件處理器。注意,這個函數為任意線程的任意對象的任意事件調用,因此,它不存在事件過濾器的線程的問題。不過我們并不推薦這么做,因為`notify()`函數只有一個,而事件過濾器要靈活得多。
現在我們可以總結一下 Qt 的事件處理,實際上是有五個層次:
1. 重寫`paintEvent()`、`mousePressEvent()`等事件處理函數。這是最普通、最簡單的形式,同時功能也最簡單。
2. 重寫`event()`函數。`event()`函數是所有對象的事件入口,`QObject`和`QWidget`中的實現,默認是把事件傳遞給特定的事件處理函數。
3. 在特定對象上面安裝事件過濾器。該過濾器僅過濾該對象接收到的事件。
4. 在`QCoreApplication::instance()`上面安裝事件過濾器。該過濾器將過濾所有對象的所有事件,因此和`notify()`函數一樣強大,但是它更靈活,因為可以安裝多個過濾器。全局的事件過濾器可以看到 disabled 組件上面發出的鼠標事件。全局過濾器有一個問題:只能用在主線程。
5. 重寫`QCoreApplication::notify()`函數。這是最強大的,和全局事件過濾器一樣提供完全控制,并且不受線程的限制。但是全局范圍內只能有一個被使用(因為`QCoreApplication`是單例的)。
為了進一步了解這幾個層次的事件處理方式的調用順序,我們可以編寫一個測試代碼:
~~~
class Label : public QWidget
{
public:
Label()
{
installEventFilter(this);
}
bool eventFilter(QObject *watched, QEvent *event)
{
if (watched == this) {
if (event->type() == QEvent::MouseButtonPress) {
qDebug() << "eventFilter";
}
}
return false;
}
protected:
void mousePressEvent(QMouseEvent *)
{
qDebug() << "mousePressEvent";
}
bool event(QEvent *e)
{
if (e->type() == QEvent::MouseButtonPress) {
qDebug() << "event";
}
return QWidget::event(e);
}
};
class EventFilter : public QObject
{
public:
EventFilter(QObject *watched, QObject *parent = 0) :
QObject(parent),
m_watched(watched)
{
}
bool eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_watched) {
if (event->type() == QEvent::MouseButtonPress) {
qDebug() << "QApplication::eventFilter";
}
}
return false;
}
private:
QObject *m_watched;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Label label;
app.installEventFilter(new EventFilter(&label, &label));
label.show();
return app.exec();
}
~~~
我們可以看到,鼠標點擊之后的輸出結果是:
~~~
QApplication::eventFilter
eventFilter
event
mousePressEvent
~~~
因此可以知道,全局事件過濾器被第一個調用,之后是該對象上面的事件過濾器,其次是`event()`函數,最后是特定的事件處理函數。
- (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(續)