Qt 的事件是整個 Qt 框架的核心機制之一,也比較複雜。說它複雜,更可能是由於它涉及到的函數衆多,而處理方法也不少,有時候讓人難以選擇。如今咱們簡單總結一下 Qt 中的事件機制。app
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 就是使用的這麼一種機制:this
mouseEvent()
keyPressEvent()
Qt 具備這麼多種事件處理函數,確定有一個地方對其進行分發,不然,Qt 怎麼知道哪種事件調用哪個事件處理函數呢?這個分發的函數,就是event()
。顯然,當QMouseEvent
產生以後,event()
函數將其分發給mouseEvent()
事件處理器進行處理。線程
event()
函數會有兩個問題:code
event()
函數是一個 protected 的函數,這意味着咱們要想重寫event()
,必須繼承一個已有的類。試想,個人程序根本不想要鼠標事件,程序中全部組件都不容許處理鼠標事件,是否是我得繼承全部組件,一一重寫其event()
函數?protected 函數帶來的另一個問題是,若是我基於第三方庫進行開發,而對方沒有提供源代碼,只有一個連接庫,其它都是封裝好的。我怎麼去繼承這種庫中的組件呢?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 的事件處理,其實是有五個層次
paintEvent()
、mousePressEvent()
等事件處理函數。這是最普通、最簡單的形式,同時功能也最簡單。event()
函數。event()
函數是全部對象的事件入口,QObject
和QWidget
中的實現,默認是把事件傳遞給特定的事件處理函數。QCoreApplication::instance()
上面安裝事件過濾器。該過濾器將過濾全部對象的全部事件,所以和notify()
函數同樣強大,可是它更靈活,由於能夠安裝多個過濾器。全局的事件過濾器能夠看到 disabled 組件上面發出的鼠標事件。全局過濾器有一個問題:只能用在主線程。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()
函數,最後是特定的事件處理函數。