Qt 的事件是整個 Qt 架構的核心機制之一,也比較複雜。說它複雜,更多是因為它涉及到的函數衆多,而處理方法也很多,有時候讓人難以選擇。現在我們簡單總結一下 Qt 中的事件機制。
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 就是使用的這麼一種機制:
- mouseEvent()
- keyPressEvent()
- …
Qt 具有這麼多種事件處理函數,肯定有一個地方對其進行分發,否則,Qt 怎麼知道哪一種事件調用哪一個事件處理函數呢?這個分發的函數,就是 event()。顯然,當 QMouseEvent 産生之後,event() 函數将其分發給 mouseEvent() 事件處理器進行處理。
【領QT開發教程學習資料,點選下方連結莬費領取↓↓,先碼住不迷路~】
點選→領取「連結」
event() 函數會有兩個問題:
- 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() 函數,最後是特定的事件處理函數。