#(23):自定義事件
盡管 Qt 已經提供了很多事件,但對于更加千變萬化的需求來說,有限的事件都是不夠的。例如,我要支持一種新的設備,這個設備提供一種嶄新的交互方式,那么,這種事件如何處理呢?所以,允許創建自己的事件 類型也就勢在必行。即便是不說那種非常極端的例子,在多線程的程序中,自定義事件也是尤其有用。當然,事件也并不是局限在多線程中,它可以用在單線程的程序中,作為一種對象間通訊的機制。那么,為什么我需要使用事件,而不是信號槽呢?主要原因是,事件的分發既可以是同步的,又可以是異步的,而函數的調用或者說是槽的回調總是同步的。事件的另外一個好處是,它可以使用過濾器。
Qt 自定義事件很簡單,同其它類庫的使用很相似,都是要繼承一個類進行擴展。在 Qt 中,你需要繼承的類是QEvent。
繼承QEvent類,最重要的是提供一個QEvent::Type類型的參數,作為自定義事件的類型值。回憶一下,這個 type 是我們在處理事件時用于識別事件類型的代號。比如在event()函數中,我們使用QEvent::type()獲得這個事件類型,然后與我們定義的實際類型對比。
QEvent::Type是QEvent定義的一個枚舉。因此,我們可以傳遞一個 int 值。但是需要注意的是,我們的自定義事件類型不能和已經存在的 type 值重復,否則會有不可預料的錯誤發生。因為系統會將你新增加的事件當做系統事件進行派發和調用。在 Qt 中,系統保留 0 – 999 的值,也就是說,你的事件 type 要大于 999。這種數值當然非常難記,所以 Qt 定義了兩個邊界值:QEvent::User和QEvent::MaxUser。我們的自定義事件的 type 應該在這兩個值的范圍之間。其中,QEvent::User的值是 1000,QEvent::MaxUser的值是 65535。從這里知道,我們最多可以定義 64536 個事件。通過這兩個枚舉值,我們可以保證我們自己的事件類型不會覆蓋系統定義的事件類型。但是,這樣并不能保證自定義事件相互之間不會被覆蓋。為了解決這個問題,Qt 提供了一個函數:registerEventType(),用于自定義事件的注冊。該函數簽名如下:
~~~
static int QEvent::registerEventType ( int hint = -1 );
~~~
這個函數是 static 的,因此可以使用QEvent類直接調用。函數接受一個 int 值,其默認值是 -1;函數返回值是向系統注冊的新的 Type 類型的值。如果 hint 是合法的,也就是說這個 hint 不會發生任何覆蓋(系統的以及其它自定義事件的),則會直接返回這個值;否則,系統會自動分配一個合法值并返回。因此,使用這個函數即可完成 type 值的指定。這個函數是線程安全的,不必另外添加同步。
我們可以在QEvent子類中添加自己的事件所需要的數據,然后進行事件的發送。Qt 中提供了兩種事件發送方式:
~~~
static bool QCoreApplication::sendEvent(QObject *receiver,
QEvent *event);
~~~
直接將event事件發送給receiver接受者,使用的是QCoreApplication::notify()函數。函數返回值就是事件處理函數的返回值。在事件被發送的時候,event對象并不會被銷毀。通常我們會在棧上創建event對象,例如:
~~~
QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
QApplication::sendEvent(mainWindow, &event);
~~~
~~~
static void QCoreApplication::postEvent(QObject *receiver,
QEvent *event);
~~~
將event事件及其接受者receiver一同追加到事件隊列中,函數立即返回。
因為 post 事件隊列會持有事件對象,并且在其 post 的時候將其 delete 掉,因此,我們必須在堆上創建event對象。當對象被發送之后,再試圖訪問event對象就會出現問題(因為 post 之后,event對象就會被 delete)。
當控制權返回到主線程循環是,保存在事件隊列中的所有事件都通過notify()函數發送出去。
事件會根據 post 的順序進行處理。如果你想要改變事件的處理順序,可以考慮為其指定一個優先級。默認的優先級是Qt::NormalEventPriority。
這個函數是線程安全的。
Qt 還提供了一個函數:
~~~
static void QCoreApplication::sendPostedEvents(QObject *receiver,
int event_type);
~~~
這個函數的作用是,將事件隊列中的接受者為receiver,事件類似為 event_type 的所有事件立即發送給 receiver 進行處理。需要注意的是,來自窗口系統的事件并不由這個函數進行處理,而是processEvent()。詳細信息請參考 Qt API 手冊。
現在,我們已經能夠自定義事件對象,已經能夠將事件發送出去,還剩下最后一步:處理自定義事件。處理自定義事件,同前面我們講解的那些處理方法沒有什么區別。我們可以重寫QObject::customEvent()函數,該函數接收一個QEvent對象作為參數:
~~~
void QObject::customEvent(QEvent *event);
~~~
我們可以通過轉換 event 對象類型來判斷不同的事件:
~~~
void CustomWidget::customEvent(QEvent *event) {
CustomEvent *customEvent = static_cast<CustomEvent *>(event);
// ...
}
~~~
當然,我們也可以在event()函數中直接處理:
~~~
bool CustomWidget::event(QEvent *event) {
if (event->type() == MyCustomEventType) {
CustomEvent *myEvent = static_cast<CustomEvent *>(event);
// processing...
return true;
}
return QWidget::event(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(續)