Qt雜談4.淺談事件傳遞的那些事

1 爲啥聊這個?

Qt中的事件是具有一套完整的傳遞機制的,記得剛學Qt的時候,很長一段時間內對事件過濾器及返回值、event函數的返回值、事件的accept、ignore等函數的作用沒能有一個清晰的認知,也沒能把他們串聯起來,導致開發過程中概念模糊,寫起代碼來很是喫力。
現在將一些感悟分享出來,一個是希望能幫助到一些人,一個是記錄下來備忘,好啦,話不多說,下面我們一起來研究下Qt事件的傳遞過程。

2 從代碼出發

2.1 一個簡單場景

主窗口MainWindow包含一個子窗口MyWidget,MyWidget包含一個子按鈕MyPushButton,結構夠簡單清晰了吧。

說明一下,這裏的MainWindow繼承QMainWindow,這裏的MyWidget繼承QWidget,這裏的MyPushButton繼承QPushButton。
以鼠標點擊事件爲例,咋們來看看不同情況下事件是如何傳遞的?

2.2 啥也不幹,點擊按鈕

這裏直接通過打印的方式查看事件是如何傳遞的,代碼如下:

void MyPushButton::mousePressEvent(QMouseEvent *event)
{
    qDebug() << __FUNCTION__ << event->isAccepted();

    QPushButton::mousePressEvent(event);
}

bool MyPushButton::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << __FUNCTION__ << event->isAccepted();
    }

    return QPushButton::event(event);
}

打印結果如下:

嗯,事件是先傳遞到event函數,最終是由QWidget::event調用mousePressEvent函數,源碼如下:

到這裏,對於稍有Qt開發經驗的人來說,沒啥理解上的問題。下面開始加料

2.3 在MyPushButton::mousePressEvent中忽略事件

void MyPushButton::mousePressEvent(QMouseEvent *event)
{
    qDebug() << __FUNCTION__ << event->isAccepted();

    QPushButton::mousePressEvent(event);

    event->ignore();
}

打印結果如下:

事件被忽略後,首先傳遞到MyWidget中,再傳遞到MainWindow中了,嗯,是時候拋出Qt真香定律了:

事件被忽略後,Qt會自動將事件轉發到其父對象,注意是父對象,以此類推。

疑問來了,事件被忽略後,傳遞到MyWidget中可以理解,爲啥還傳遞到了MainWindow中?首先我們看看MyWidget的代碼:

void MyWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << __FUNCTION__ << event->isAccepted();

    QWidget::mousePressEvent(event);
}

bool MyWidget::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << __FUNCTION__ << event->isAccepted();
    }

    return QWidget::event(event);
}

好像啥也沒幹,並且還打印了'MyWidget::mousePressEvent true'說明事件被接受了,沒有被忽略,應該不會再傳遞到父對象纔對。
其實,答案在QWidget::mousePressEvent(event)中,我們來裏面是咋實現的:

看到了吧,Qt默認是忽略事件的,所以會被再次轉發到父對象中,也就是MainWindow中。
還有一個疑問,爲啥ignore後的事件傳遞到父對象後,默認變成了accepted了呢?
我的理解,這裏是Qt自動處理了,一個事件傳遞到父對象後狀態會被自動重置爲accepted,所以纔會有這個現象。
到這裏,好像一切都合情合理了,下面繼續加料

2.4 在MyPushButton::event中忽略事件

event函數有返回值,幫助文檔也說的比較簡單,大概是說如果事件被識別並處理,則返回true,好像沒有啥信息含量。
好吧,說說我的理解,一般來說,返回true,說明這個事件被處理了不需要再傳遞到父對象了,返回false反之。
按照這個設想,我們修改MyPushButton代碼如下:

void MyPushButton::mousePressEvent(QMouseEvent *event)
{
    qDebug() << __FUNCTION__ << event->isAccepted();

    QPushButton::mousePressEvent(event);
}

bool MyPushButton::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << __FUNCTION__ << event->isAccepted();
        QPushButton::event(event);
        return false;
    }

    return QPushButton::event(event);
}

打印結果如下:

如我們所想,event函數返回值確有這個作用,這裏返回false後,事件又被傳遞到父對象MyWidget中了,如果返回true則不會繼續傳遞。但是這樣就結束了嘛,非也,如果在返回true之前忽略事件,會有啥效果?好奇心驅使我忍不住這樣幹了。
先來看看直接返回true會有啥效果:

void MyPushButton::mousePressEvent(QMouseEvent *event)
{
    qDebug() << __FUNCTION__ << event->isAccepted();

    QPushButton::mousePressEvent(event);
}

bool MyPushButton::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << __FUNCTION__ << event->isAccepted();
        QPushButton::event(event);
        return true;
    }

    return QPushButton::event(event);
}

結果打印如下:

結果完全在我們的意料之中,這裏沒啥可說的。
再修改下代碼:

bool MyPushButton::event(QEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << __FUNCTION__ << event->isAccepted();
        QPushButton::event(event);
        event->ignore();
        return true;
    }

    return QPushButton::event(event);
}

打印結果如下:

像這種event函數在返回之前忽略事件是非一般情況,不能用固有的邏輯去解釋,這裏我做了個簡單的總結:

只要滿足續傳條件之一,Qt會盡量讓事件傳遞到父對象。

不知道大家能不能理解,簡單來說就是event返回true不一定代表事件不向父對象傳遞了,還要結合是否調用了事件的ignore函數;調用事件的accept()接口,不代表一定就不向父對象傳遞了,還要結合event函數的返回值。
這裏提一下,所謂的ignore和accept其實就是設置一個標記而已,用來標識事件是否被接受了,可以看看源碼:

並且,這個標記默認是設置爲true的:

之前有些博文說在event函數中調用事件的ignore、accept函數沒用,這種錯誤的描述不攻自破,我們以事實說話。
我的理解是,不建議在event函數中調用事件的ignore或accept接口,而是應該在具體的事件函數中調用,不然會有些混亂,並不代表event函數中不能調用或者調了沒用,務必要理解這點。

2.5 爲MyPushButton安裝事件過濾器

首先需要說明的是,事件過濾器的作用是在事件還沒有傳遞到目標對象之前,捕獲該事件並處理,返回ture則說明事件被處理好了,不需要在傳遞到目標對象了,反之亦然。
爲MyPushButton安裝事件過濾器:

MyWidget::MyWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MyWidget)
{
    ui->setupUi(this);

    MyPushButton *button = new MyPushButton(this);
    button->setText("MyPushButton");
    button->move(100, 100);

    button->installEventFilter(this);
}

bool MyWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (QString(watched->metaObject()->className()) == "MyPushButton" && event->type() == QEvent::MouseButtonPress) {
        qDebug() << __FUNCTION__ << event->isAccepted();
    }

    return QWidget::eventFilter(watched, event);
}

打印結果如下:

安裝事件過濾器後,事件首先傳遞到了MyWidget中,由eventFilter函數處理,由於QWidget::eventFilter默認返回了false:

所以,事件會繼續傳遞到目標對象,合情合理。
按照我們最初的設想,如果MyWidget::eventFilter返回了true,那麼事件是不是就不會再繼續傳遞到目標對象了?修改代碼:

bool MyWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (QString(watched->metaObject()->className()) == "MyPushButton" && event->type() == QEvent::MouseButtonPress) {
        qDebug() << __FUNCTION__ << event->isAccepted();
        return true;
    }

    return QWidget::eventFilter(watched, event);
}

結果打印如下:

如我們所料,一切盡在掌控中。如果返回false呢,再在返回前調用ignore接口呢?我們試試:

bool MyWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (QString(watched->metaObject()->className()) == "MyPushButton" && event->type() == QEvent::MouseButtonPress) {
        qDebug() << __FUNCTION__ << event->isAccepted();
        event->ignore();
        return false;
    }

    return QWidget::eventFilter(watched, event);
}

結果打印如下:

看到這裏,應該還有沒懵吧,MyPushButton::mousePressEvent中接受狀態打印爲false了爲啥沒有繼續傳遞到父對象MyWidget中?先來看看MyPushButton的實現:

void MyPushButton::mousePressEvent(QMouseEvent *event)
{
    qDebug() << __FUNCTION__ << event->isAccepted();

    QPushButton::mousePressEvent(event);
}

看到沒有,原因在於我們調用了QPushButton::mousePressEvent的默認實現:

現在明白了吧,之所以事件沒有續傳了,就是因爲默認實現調用了事件的accept函數。

3 總結

一般情況下:

  1. Qt事件傳會首先遞到目標對象event函數中,event函數有個返回值,在於告訴Qt要不要繼續傳遞給父對象,返回true則說明事件已處理,不需要及傳遞給父對象了,反之亦然;
  2. event函數默認會調用對應的事件處理函數,在事件處理函數中可以調用ignore、accept接口用於標記事件是否被接受了,如果被接受了就說明不需要再繼續向父對象傳遞了,反之亦然;
  3. 安裝事件過濾器後,事件首先會被傳遞到事件過濾函數中,過濾函數有個返回值,返回true則說明該事件已被過濾,說明無需繼續傳遞給目標對象了,反之亦然;
  4. 事件由一個對象傳遞到父對象時事件狀態默認會被重置爲accepted。

非一般情況下:

  1. 如果在event函數中既調用了事件的ignore或accept,又用到了event的返回值,這時只要滿足續傳條件之一的,Qt會盡量會將事件向父對象傳遞。

好啦,就說到這裏了,有不對的地方,還希望大家不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章