首先來看一段代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //!!! Qt5 // ---------- custombutton.h ---------- // class CustomButton : public QPushButton { Q_OBJECT public: CustomButton(QWidget *parent = 0); private: void onButtonCliecked(); }; // ---------- custombutton.cpp ---------- // CustomButton::CustomButton(QWidget *parent) : QPushButton(parent) { connect(this, &CustomButton::clicked, this, &CustomButton::onButtonCliecked); } void CustomButton::onButtonCliecked() { qDebug() << "You clicked this!"; } // ---------- main.cpp ---------- // int main(int argc, char *argv[]) { QApplication a(argc, argv); CustomButton btn; btn.setText("This is a Button!"); btn.show(); return a.exec(); } |
這是一段簡單的代碼,經過我們前面一段時間的學習,我們已經能夠知道這段代碼的運行結果:點擊按鈕,會在控制檯打印出“You clicked this!”字符串。這是我們前面介紹過的內容。下面,我們向CustomButton
類添加一個事件函數:
我們重寫了CustomButton
的mousePressEvent()
函數,也就是鼠標按下。在這個函數中,我們判斷如果鼠標按下的是左鍵,則打印出來“left”字符串,否則,調用父類的同名函數。編譯運行這段代碼,當我們點擊按鈕時,“You
clicked this!”字符串不再出現,只有一個“left”。也就是說,我們把父類的實現覆蓋掉了。由此可以看出,父類QPushButton
的mousePressEvent()
函數中肯定發出了clicked()
信號,否則的話,我們的槽函數怎麼會不執行了呢?這暗示我們一個非常重要的細節:當重寫事件回調函數時,時刻注意是否需要通過調用父類的同名函數來確保原有實現仍能進行!比如我們的CustomButton
了,如果像我們這麼覆蓋函數,clicked()
信號永遠不會發生,你連接到這個信號的槽函數也就永遠不會被執行。這個錯誤非常隱蔽,很可能會浪費你很多時間才能找到。因爲這個錯誤不會有任何提示。這一定程度上說,我們的組件“忽略”了父類的事件,但這更多的是一種違心之舉,一種錯誤。
通過調用父類的同名函數,我們可以把 Qt 的事件傳遞看成鏈狀:如果子類沒有處理這個事件,就會繼續向其父類傳遞。Qt 的事件對象有兩個函數:accept()
和ignore()
。正如它們的名字一樣,前者用來告訴
Qt,這個類的事件處理函數想要處理這個事件;後者則告訴 Qt,這個類的事件處理函數不想要處理這個事件。在事件處理函數中,可以使用isAccepted()
來查詢這個事件是不是已經被接收了。具體來說:如果一個事件處理函數調用了一個事件對象的accept()
函數,這個事件就不會被繼續傳播給其父組件;如果它調用了事件的ignore()
函數,Qt
會從其父組件中尋找另外的接受者。
事實上,我們很少會使用accept()
和ignore()
函數,而是像上面的示例一樣,如果希望忽略事件(所謂忽略,是指自己不想要這個事件),只要調用父類的響應函數即可。記得我們曾經說過,Qt
中的事件都是 protected 的,因此,重寫的函數必定存在着其父類中的響應函數,所以,這個方法是可行的。爲什麼要這麼做,而不是自己去手動調用這兩個函數呢?因爲我們無法確認父類中的這個處理函數有沒有額外的操作。如果我們在子類中直接忽略事件,Qt 會去尋找其他的接收者,該子類的父類的操作會被忽略(因爲沒有調用父類的同名函數),這可能會有潛在的危險。爲了避免自己去調用accept()
和ignore()
函數,而是儘量調用父類實現,Qt
做了特殊的設計:事件對象默認是 accept 的,而作爲所有組件的父類QWidget
的默認實現則是調用ignore()
。這麼一來,如果你自己實現事件處理函數,不調用QWidget
的默認實現,你就等於是接受了事件;如果你要忽略事件,只需調用QWidget
的默認實現。這一點我們前面已經說明。下面可以從代碼級別來理解這一點,我們可以查看一下QWidget
的mousePressEvent()
函數的實現:
這段代碼在 Qt4 和 Qt5 中基本一致(區別在於activePopupWidget()
一行,Qt4 的版本是qApp->activePopupWidget()
)。注意函數的第一個語句:event->ignore()
,如果子類都沒有重寫這個函數,Qt 會默認忽略這個事件,繼續尋找下一個事件接收者。如果我們在子類的mousePressEvent()
函數中直接調用了accept()
或者ignore()
,而沒有調用父類的同名函數,QWidget::mousePressEvent()
函數中關於Popup
判斷的那段代碼就不會被執行,因此可能會出現默認其妙的怪異現象。
針對accept()
和ignore()
,我們再來看一個例子:
這段代碼在一個MainWindow
中添加了一個CustomWidget
,裏面有兩個按鈕對象:CustomButton
和CustomButtonEx
。每一個類都重寫了mousePressEvent()
函數。運行程序點擊
CustomButtonEx,結果是
這是因爲我們重寫了鼠標按下的事件,但是並沒有調用父類函數或者顯式設置accept()
或ignore()
。下面我們在CustomButtonEx
的mousePressEvent()
第一行增加一句event->accept()
,重新運行,發現結果不變。正如我們前面所說,QEvent
默認是accept
的,調用這個函數並沒有什麼區別。然後我們將CustomButtonEx
的event->accept()
改成event->ignore()
。這次運行結果是
ignore()
說明我們想讓事件繼續傳播,於是CustomButtonEx
的父組件CustomWidget
也收到了這個事件,所以輸出了自己的結果。同理,CustomWidget
又沒有調用父類函數或者顯式設置accept()
或ignore()
,所以事件傳播就此打住。這裏值得注意的是,CustomButtonEx
的事件傳播給了父組件CustomWidget
,而不是它的父類CustomButton
。事件的傳播是在組件層次上面的,而不是依靠類繼承機制。
接下來我們繼續測試,在CustomWidget
的mousePressEvent()
中增加QWidget::mousePressEvent(event)
。這次的輸出是
如果你把QWidget::mousePressEvent(event)
改成event->ignore()
,結果也是一樣的。這正如我們前面說的,QWidget
的默認是調用event->ignore()
。
不過,事情也不是絕對的。在一個情形下,我們必須使用accept()
和ignore()
函數,那就是窗口關閉的事件。注意,不要試圖用前面瞭解到的有關accept()
和ignore()
的知識來理解這個事件!對於QCloseEvent
事件,調用accept()
意味着 Qt 會停止事件的傳播,調用ignore()
則意味着事件繼續傳播。這與前面所說的正好相反。回到我們前面寫的簡單的文本編輯器。我們在構造函數中添加如下代碼:
setWindowTitle()
函數可以使用 [*] 這種語法來表明,在窗口內容發生改變時(通過setWindowModified(true)
函數通知),Qt
會自動在標題上面的 [*] 位置替換成 * 號。我們使用 Lambda 表達式連接QTextEdit::textChanged()
信號,將windowModified
設置爲
true。然後我們需要重寫closeEvent()
函數。在這個函數中,我們首先判斷是不是有過修改,如果有,則彈出詢問框,問一下是否要退出。如果用戶點擊了“Yes”,則接受關閉事件,這個事件所在的操作就是關閉窗口。因此,一旦接受事件,窗口就會被關閉;否則窗口繼續保留。當然,如果窗口內容沒有被修改,則直接接受事件,關閉窗口。