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

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

  • 相关阅读:
    再不迁移到Material Design Components 就out啦
    Material Design Compoents 1.1.0
    Android低功耗蓝牙总结
    JAVA8 对象排序
    记录UIButton 自适应的一个坑
    MySQL 索引大全
    MySQL 与 MongoDB 的区别
    Flutter https://flutter.cn/docs学习之添加资源和图片
    Flutter https://flutter.cn/docs学习之加入交互体验
    Flutter https://flutter.cn/docs学习之向 Android 应用中添加闪屏页和启动页
  • 原文地址:https://www.cnblogs.com/luoxiang/p/15896549.html
Copyright © 2020-2023  润新知