事件(event)是由系統或者 Qt 自己在不一樣的時刻發出的。當用戶按下鼠標、敲下鍵盤,或者是窗口須要從新繪製的時候,都會發出一個相應的事件。一些事件在對用戶操做作出響應時發出,如鍵盤事件等;另外一些事件則是由系統自動發出,如計時器事件。框架
事件也就是咱們一般說的「事件驅動(event drive)」程序設計的基礎概念。事件的出現,使得程序代碼不會按照原始的線性順序執行。想一想看,從最初的 C 語言開始,咱們的程序就是以一種線性的順序執行代碼:這一條語句執行以後,開始執行下一條語句;這一個函數執行事後,開始執行下一個函數。這種相似「批處理」的程序設計風格顯然不適合於處理複雜的用戶交互。咱們來想象一下用戶交互的情景:咱們設計了一堆功能放在界面上,用戶點擊了「打開文件」,因而開始執行打開文件的操做;用戶點擊了「保存文件」,因而開始執行保存文件的操做。咱們不知道用戶究竟想進行什麼操做,所以也就不能預測接下來將會調用哪個函數。若是咱們設計了一個「文件另存爲」的操做,若是用戶不點擊,這個操做將永遠不會被調用。這就是所謂的「事件驅動」,咱們的程序的執行順序再也不是線性的,而是由一個個事件驅動着程序繼續執行。沒有事件,程序將阻塞在那裏,不執行任何代碼。函數
在 Qt 中,事件的概念彷佛同信號槽相似。的確如此,通常來講,使用 Qt 組件時,咱們並不會把主要精力放在事件上。由於在 Qt 中,咱們關心的更多的是事件關聯的一個信號。好比,對於QPushButton
的鼠標點擊,咱們不須要關心這個鼠標點擊事件,而是關心它的clicked()
信號的發出。這與其餘的一些 GUI 框架不一樣:在 Swing 中,你所要關心的是JButton
的ActionListener
這個點擊事件。由此看出,相比於其餘 GUI 框架,Qt 給了咱們額外的選擇:信號槽。this
可是,Qt 中的事件和信號槽卻並非能夠相互替代的。信號由具體的對象發出,而後會立刻交給由connect()
函數鏈接的槽進行處理;而對於事件,Qt 使用一個事件隊列對全部發出的事件進行維護,當新的事件產生時,會被追加到事件隊列的尾部。前一個事件完成後,取出後面的事件進行處理。可是,必要的時候,Qt 的事件也能夠不進入事件隊列,而是直接處理。信號一旦發出,對應的槽函數必定會被執行。可是,事件則可使用「事件過濾器」進行過濾,對於有些事件進行額外的處理,另外的事件則不關心。總的來講,若是咱們使用組件,咱們關心的是信號槽;若是咱們自定義組件,咱們關心的是事件。由於咱們能夠經過事件來改變組件的默認操做。好比,若是咱們要自定義一個可以響應鼠標事件的EventLabel
,咱們就須要重寫QLabel
的鼠標事件,作出咱們但願的操做,有可能還得在恰當的時候發出一個相似按鈕的clicked()
信號(若是咱們但願讓這個EventLabel
可以被其它組件使用)或者其它的信號。設計
在前面咱們也曾經簡單提到,Qt 程序須要在main()
函數建立一個QCoreApplication
對象,而後調用它的exec()
函數。這個函數就是開始 Qt 的事件循環。在執行exec()
函數以後,程序將進入事件循環來監聽應用程序的事件。當事件發生時,Qt 將建立一個事件對象。Qt 中全部事件類都繼承於QEvent
。在事件對象建立完畢後,Qt 將這個事件對象傳遞給QObject
的event()
函數。event()
函數並不直接處理事件,而是按照事件對象的類型分派給特定的事件處理函數(event handler)。關於這一點,咱們會在之後的章節中詳細說明。code
在全部組件的父類QWidget
中,定義了不少事件處理的回調函數,如keyPressEvent()
、keyReleaseEvent()
、mouseDoubleClickEvent()
、mouseMoveEvent()
、mousePressEvent()
、mouseReleaseEvent()
等。這些函數都是 protected virtual 的,也就是說,咱們能夠在子類中從新實現這些函數。下面來看一個例子:對象
#include <QApplication> #include <QMouseEvent> #include <QLabel> 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]。get
在mouseReleaseEvent()
函數中,咱們使用了另一種QString
的構造方法。咱們使用相似 C 風格的格式化函數sprintf()
來構造QString
。
運行上面的代碼,當咱們點擊了一下鼠標以後,label 上將顯示鼠標當前座標值。
爲何要點擊鼠標以後才能在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();
這樣子就沒有這個問題了。