天天看點

Qt事件總結

作者:音視訊開發老舅

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() 函數會有兩個問題:

  1. event() 函數是一個 protected 的函數,這意味着我們要想重寫 event(),必須繼承一個已有的類。試想,我的程式根本不想要滑鼠事件,程式中所有元件都不允許處理滑鼠事件,是不是我得繼承所有元件,一一重寫其 event() 函數?protected 函數帶來的另外一個問題是,如果我基于第三方庫進行開發,而對方沒有提供源代碼,隻有一個連結庫,其它都是封裝好的。我怎麼去繼承這種庫中的元件呢?
  2. 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 的事件處理,實際上是有五個層次

  1. 重寫 paintEvent()、mousePressEvent() 等事件處理函數。這是最普通、最簡單的形式,同時功能也最簡單。
  2. 重寫 event() 函數。event() 函數是所有對象的事件入口,QObject 和 QWidget 中的實作,預設是把事件傳遞給特定的事件處理函數。
  3. 在特定對象上面安裝事件過濾器。該過濾器僅過濾該對象接收到的事件。
  4. 在 QCoreApplication::instance() 上面安裝事件過濾器。該過濾器将過濾所有對象的所有事件,是以和 notify() 函數一樣強大,但是它更靈活,因為可以安裝多個過濾器。全局的事件過濾器可以看到 disabled 元件上面發出的滑鼠事件。全局過濾器有一個問題:隻能用在主線程。
  5. 重寫 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() 函數,最後是特定的事件處理函數。