#(18):事件
事件(event)是由系統或者 Qt 本身在不同的時刻發出的。當用戶按下鼠標、敲下鍵盤,或者是窗口需要重新繪制的時候,都會發出一個相應的事件。一些事件在對用戶操作做出響應時發出,如鍵盤事件等;另一些事件則是由系統自動發出,如計時器事件。
事件也就是我們通常說的“事件驅動(event drive)”程序設計的基礎概念。事件的出現,使得程序代碼不會按照原始的線性順序執行。想想看,從最初的 C 語言開始,我們的程序就是以一種線性的順序執行代碼:這一條語句執行之后,開始執行下一條語句;這一個函數執行過后,開始執行下一個函數。這種類似“批處理”的程序設計風格顯然不適合于處理復雜的用戶交互。我們來想象一下用戶交互的情景:我們設計了一堆功能放在界面上,用戶點擊了“打開文件”,于是開始執行打開文件的操作;用戶點擊了“保存文件”,于是開始執行保存文件的操作。我們不知道用戶究竟想進行什么操作,因此也就不能預測接下來將會調用哪一個函數。如果我們設計了一個“文件另存為”的操作,如果用戶不點擊,這個操作將永遠不會被調用。這就是所謂的“事件驅動”,我們的程序的執行順序不再是線性的,而是由一個個事件驅動著程序繼續執行。沒有事件,程序將阻塞在那里,不執行任何代碼。
在 Qt 中,事件的概念似乎同信號槽類似。的確如此,一般來說,使用 Qt 組件時,我們并不會把主要精力放在事件上。因為在 Qt 中,我們關心的更多的是事件關聯的一個信號。比如,對于`QPushButton`的鼠標點擊,我們不需要關心這個鼠標點擊事件,而是關心它的`clicked()`信號的發出。這與其他的一些 GUI 框架不同:在 Swing 中,你所要關心的是`JButton`的`ActionListener`這個點擊事件。由此看出,相比于其他 GUI 框架,Qt 給了我們額外的選擇:信號槽。
但是,Qt 中的事件和信號槽卻并不是可以相互替代的。信號由具體的對象發出,然后會馬上交給由`connect()`函數連接的槽進行處理;而對于事件,Qt 使用一個事件隊列對所有發出的事件進行維護,當新的事件產生時,會被追加到事件隊列的尾部。前一個事件完成后,取出后面的事件進行處理。但是,必要的時候,Qt 的事件也可以不進入事件隊列,而是直接處理。信號一旦發出,對應的槽函數一定會被執行。但是,事件則可以使用“事件過濾器”進行過濾,對于有些事件進行額外的處理,另外的事件則不關心。總的來說,如果我們**使用**組件,我們關心的是信號槽;如果我們**自定義**組件,我們關心的是事件。因為我們可以通過事件來改變組件的默認操作。比如,如果我們要自定義一個能夠響應鼠標事件的`EventLabel`,我們就需要重寫`QLabel`的鼠標事件,做出我們希望的操作,有可能還得在恰當的時候發出一個類似按鈕的`clicked()`信號(如果我們希望讓這個`EventLabel`能夠被其它組件使用)或者其它的信號。
在前面我們也曾經簡單提到,Qt 程序需要在`main()`函數創建一個`QCoreApplication`對象,然后調用它的`exec()`函數。這個函數就是開始 Qt 的事件循環。在執行`exec()`函數之后,程序將進入事件循環來監聽應用程序的事件。當事件發生時,Qt 將創建一個事件對象。Qt 中所有事件類都繼承于`QEvent`。在事件對象創建完畢后,Qt 將這個事件對象傳遞給`QObject`的`event()`函數。`event()`函數并不直接處理事件,而是按照事件對象的類型分派給特定的事件處理函數(event handler)。關于這一點,我們會在以后的章節中詳細說明。
在所有組件的父類`QWidget`中,定義了很多事件處理的回調函數,如`keyPressEvent()`、`keyReleaseEvent()`、`mouseDoubleClickEvent()`、`mouseMoveEvent()`、`mousePressEvent()`、`mouseReleaseEvent()`等。這些函數都是 protected virtual 的,也就是說,我們可以在子類中重新實現這些函數。下面來看一個例子:
~~~
class EventLabel : public QLabel
{
protected:
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
};
void EventLabel::mouseMoveEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Move: (%1, %2)</h1></center>")
.arg(QString::number(event->x()), QString::number(event->y())));
}
void EventLabel::mousePressEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Press: (%1, %2)</h1></center>")
.arg(QString::number(event->x()), QString::number(event->y())));
}
void EventLabel::mouseReleaseEvent(QMouseEvent *event)
{
QString msg;
msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>",
event->x(), event->y());
this->setText(msg);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
EventLabel *label = new EventLabel;
label->setWindowTitle("MouseEvent Demo");
label->resize(300, 200);
label->show();
return a.exec();
}
~~~
我們編譯運行上面的代碼,就可以理解到有關事件的使用方法。
`EventLabel`繼承了`QLabel`,覆蓋了`mousePressEvent()`、`mouseMoveEvent()`和`MouseReleaseEvent()`三個函數。我們并沒有添加什么功能,只是在鼠標按下(press)、鼠標移動(move)和鼠標釋放(release)的時候,把當前鼠標的坐標值顯示在這個`Label`上面。由于`QLabel`是支持 HTML 代碼的,因此我們直接使用了 HTML 代碼來格式化文字。
`QString`的`arg()`函數可以自動替換掉`QString`中出現的占位符。其占位符以 % 開始,后面是占位符的位置,例如 %1,%2 這種。
~~~
QString("[%1, %2]").arg(x, y);
~~~
語句將會使用 x 替換 %1,y 替換 %2,因此,這個語句生成的`QString`為 [x, y]。
在`mouseReleaseEvent()`函數中,我們使用了另外一種`QString`的構造方法。我們使用類似 C 風格的格式化函數`sprintf()`來構造`QString`。
運行上面的代碼,當我們點擊了一下鼠標之后,label 上將顯示鼠標當前坐標值。
[](http://files.devbean.net/images/2012/09/eventlabel-demo.png)
為什么要點擊鼠標之后才能在`mouseMoveEvent()`函數中顯示鼠標坐標值?這是因為`QWidget`中有一個`mouseTracking`屬性,該屬性用于設置是否追蹤鼠標。只有鼠標被追蹤時,`mouseMoveEvent()`才會發出。如果`mouseTracking`是 false(默認即是),組件在至少一次鼠標點擊之后,才能夠被追蹤,也就是能夠發出`mouseMoveEvent()`事件。如果`mouseTracking`為 true,則`mouseMoveEvent()`直接可以被發出。知道了這一點,我們就可以在`main()`函數中直接設置下:
~~~
EventLabel *label = new EventLabel;
label->setWindowTitle("MouseEvent Demo");
label->resize(300, 200);
label->setMouseTracking(true);
label->show();
~~~
這樣子就沒有這個問題了。
- (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(續)