天天看點

Qt__事件處理機制

作者:清風九宸

一、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()函數.

點選領取Qt學習資料+視訊教程~「連結」

過濾器的定義如下:

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();
}
           

運作結果:

Qt__事件處理機制

是以可以知道,全局事件過濾器被第一個調用,之後是該對象上面的事件過濾器,其次是event()函數,最後是特定的事件處理函數。

繼續閱讀