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会尽量会将事件向父对象传递。

好啦,就说到这里了,有不对的地方,还希望大家不吝赐教。

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