【Qt筆記】事件總結

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

  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()函數是全部對象的事件入口,QObjectQWidget中的實現,默認是把事件傳遞給特定的事件處理函數。
  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()函數,最後是特定的事件處理函數。

相關文章
相關標籤/搜索