1. 程式人生 > 實用技巧 >Qt__事件處理機制

Qt__事件處理機制

https://www.cnblogs.com/narjaja/p/9143917.html

一、Qt事件##

Qt會將系統訊息(如滑鼠按鍵、鍵盤按鍵等)轉化為Qt事件,Qt事件被封裝為物件且定義該物件的類均繼承自抽象類QEvent。

二、Qt事件的產生##

1.作業系統產生###

  • Spontaneous events(自發事件)
    從系統得到的訊息,比如滑鼠按鍵,鍵盤按鍵等,放入系統訊息佇列中。

2.QT應用程式程式產生###

  • Posted events

      由Qt或應用程式產生,放入Qt訊息佇列。
    
    static void postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority);
  • Sent events

      由Qt或應用程式產生,不放入佇列,直接被派發和處理。
    
    static bool sendEvent(QObject *receiver, QEvent *event);

注:兩個函式都是接受一個 QObject * 和一個 QEvent * 作為引數。
前輩們說 sendEvent 的 event 可分配在 stack或者heep 上; postEvent 的 event 必須分配在 heep 上。
但我試的兩個怎麼都可以

程式碼如下:

    QPointF pos(10,10);
    QMouseEvent* mEvnPress = new QMouseEvent(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    QApplication::postEvent(&label,mEvnPress);

例子###

比如考慮重繪事件處理函式 paintEvent(),3種事件都能使得該函式被呼叫:
當視窗被其他視窗覆蓋後,再次重新顯示時,系統將產生 spontaneous 事件來請求重繪。
當我們呼叫 update() 時,產生的是 Posted 事件。
當我們呼叫 repaint() 時,產生的是 Sent 事件。

三、Qt事件的呼叫##

當呼叫QApplication::exec()時,就進入了事件佇列迴圈,不斷地檢測事件並呼叫事件。

  • 先處理Qt事件佇列中的事件, 直至為空。
  • 再處理系統訊息佇列中的訊息, 直至為空。
  • 在處理系統訊息的時候會產生新的Qt事件, 需要對其再次進行處理。

而在呼叫QApplication::sendEvent的時候, 訊息會立即被處理,是同步的。實際上QApplication::sendEvent()是通過呼叫QApplication::notify(), 直接進入了事件的派發和處理環節。

四、事件的派發與處理##

事實上,Qt 事件的呼叫最終都會追溯到QCoreApplication::notify()函式。這個函式的宣告是:

virtual bool QCoreApplication::notify ( QObject * receiver, QEvent * event );

該函式會將event傳送給receiver,也就是呼叫receiver->event(event),這個函式就實現了事件的派發,根據event的型別將呼叫不同的事件處理器mousePressEvent(), keyPressEvent(), paintEvent()等等。

注意,QCoreApplication::notify()這個函式為任意執行緒的任意物件的任意事件呼叫,因此,它不存在事件過濾器的執行緒的問題。不過我們並不推薦這麼做,因為notify()函式只有一個,而事件過濾器要靈活得多。

五、事件的過濾##

事件在經過notify()呼叫的內部函式notify_helper()的原始碼部分的原始碼如下:

bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
    // send to all application event filters
    if (sendThroughApplicationEventFilters(receiver, event))
        return true;
    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event))
        return true;
    // deliver the event
    return receiver->event(event);
}

事件在傳遞到物件之前(呼叫receiver->event()函式之前),要先能通過 Applicaton 和 receiver 安裝的過濾器,那麼過濾器是怎麼安裝的:

QObject(A)->installEventFilter(QObject(B));

首先QObject(A)中有一個型別為QObjectList的成員變數,名字為eventFilters
當某個QObject(A)安裝了事件過濾器之後, 它會將QObject(B)的指標儲存在eventFilters中,在事件到達QObject::event()函式之前,會先檢視該物件的eventFilters列表, 如果非空, 就先呼叫列表中物件的eventFilter()函式.

過濾器的定義如下:

bool QObject::eventFilter ( QObject * watched, QEvent * event )

事件過濾器函式eventFilter()返回值是bool型
如果返回true, 則表示該事件已經被處理完畢, Qt將直接返回, 進行下一事件的處理
如果返回false, 事件將接著被送往剩下的事件過濾器或是目標物件進行處理

如果使用installEventFilter()函式給一個物件安裝事件過濾器,那麼該事件過濾器只對該物件有效,只有這個物件的事件需要先傳遞給事件過濾器的eventFilter()函式進行過濾,其它物件不受影響。
給 QCoreApplication(由於也是QObject 派生類,安裝過濾器方式與前述相同)安裝的過濾器屬於全域性的事件過濾器對程式中的每一個物件都有效,任何物件的事件都是先傳給eventFilter()函式。

事件過濾器的好處在於事件過濾器在目標物件接收到事件之前進行處理,如果我們將事件過濾掉,目標物件根本不會見到這個事件。

六、事件的轉發##

對於某些類別的事件, 如果在整個事件的派發過程結束後還沒有被處理, 那麼這個事件將會向上轉發給它的父widget, 直到最頂層視窗.

如何判斷一個事件是否被處理了呢? (有兩個層次)

  • QApplication::notify(), QObject::eventFilter(), QObject::event() 通過返回bool值來表示是否已處理. “真”表示已經處理, “假”表示事件需要繼續傳遞。
  • 另一種是呼叫QEvent::ignore() 或 QEvent::accept() 對事件進行標識,accept表示事件被處理。

為清楚起見,貼一點Qt的原始碼(來自 QApplication::notify()):

    case QEvent::ToolTip:
    case QEvent::WhatsThis:
    case QEvent::QueryWhatsThis:
        {
            QWidget* w = static_cast<QWidget *>(receiver);
            QHelpEvent *help = static_cast<QHelpEvent*>(e);
            QPoint relpos = help->pos();
            bool eventAccepted = help->isAccepted();
            while (w) {
                QHelpEvent he(help->type(), relpos, help->globalPos());
                he.spont = e->spontaneous();
                res = d->notify_helper(w, w == receiver ? help : &he);
                e->spont = false;
                eventAccepted = (w == receiver ? help : &he)->isAccepted();
                if ((res && eventAccepted) || w->isWindow())
                    break;

                relpos += w->pos();
                w = w->parentWidget();
            }
            help->setAccepted(eventAccepted);
        }
        break;

這兒顯示了對 WhatsThis 事件的處理:先派發給 w,如果事件被accepted 或已經是頂級視窗,則停止;否則獲取w的父物件,繼續派發。

七、總結##

現在我們可以總結一下 Qt 的事件處理,實際上是有五個層次:

  • 1.重寫paintEvent()、mousePressEvent()等事件處理函式。這是最普通、最簡單的形式,同時功能也最簡單。
  • 2.重寫event()函式。event()函式是所有物件的事件入口,QObject和QWidget中的實現,預設是把事件傳遞給特定的事件處理函式。
  • 3.在特定物件上面安裝事件過濾器。該過濾器僅過濾該物件接收到的事件。
  • 4.在QCoreApplication::instance()上面安裝事件過濾器。該過濾器將過濾所有物件的所有事件,因此和notify()函式一樣強大,但是它更靈活,因為可以安裝多個過濾器。全域性的事件過濾器可以看到 disabled 元件上面發出的滑鼠事件。全域性過濾器有一個問題:只能用在主執行緒。
  • 5.重寫QCoreApplication::notify()函式。這是最強大的,和全域性事件過濾器一樣提供完全控制,並且不受執行緒的限制。但是全域性範圍內只能有一個被使用(因為QCoreApplication是單例的)。

為了進一步瞭解這幾個層次的事件處理方式的呼叫順序,我們可以編寫一個測試程式碼:

#include <qapplication.h>
#include <QMainWindow>
#include <QPushButton>
#include <custombutton.h>
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));
    QPointF pos(10,10);
    QMouseEvent* mEvnPress = new QMouseEvent(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    QApplication::postEvent(&label,mEvnPress);
    label.show();
    return app.exec();
}

執行結果:

因此可以知道,全域性事件過濾器被第一個呼叫,之後是該物件上面的事件過濾器,其次是event()函式,最後是特定的事件處理函式。

參考##

abluemooon的部落格
豆子空間