Qt事件传递及相关的性能问题

  • Post author:
  • Post category:其他


在使用Qt时,我们都知道能通过mousePressEvent,eventFilter等虚函数的重写来处理事件,那么当我们向一个界面发送事件,控件和它的父控件之间的事件传递过程是什么样的呢?

本文将以下图所示界面为例,结合源码介绍Qt事件传递的过程。

父到子的关系依次为:

MyWindow->MyButton->MyEdit。

在这里插入图片描述

在启动程序后,用鼠标点击一下MyEdit,下面是事件传递的过程。



第一步、QCoreApplication(qApp)处理事件



过程

在这里插入图片描述

这里最后接收点击事件的是最上层的控件,也就是本例中的MyEdit。



相关源码

源码按执行顺序来排列。

qwindowsysteminterface.cpp->QWindowSystemInterface::sendWindowSystemEvents

在这里插入图片描述

qapplication->QApplicationPrivate::notify_helper

在这里插入图片描述

qwidgetwindow->QWidgetWindow::handleMouseEvent

在这里插入图片描述

childAt的过程:

1、遍历MyWindow的子控件,找到包含了点击坐标的MyButton。

2、遍历MyButton的子控件,找到包含了点击坐标的MyEdit。

3、遍历MyEdit的子控件,没找到子控件,返回MyEdit。



第二步、控件处理事件



过程

在这里插入图片描述

流程看起来有点复杂,用文字来表示就是以下几点:

1、每个控件处理事件时会先执行qApp安装的事件过滤器,即qApp->installEventFilter安装的。然后执行自己安装的事件过滤器,即this->installEventFilter安装的,最后才是事件处理,如mousePressEvent等。

2、控件在处理事件时可以用setAccepted来设置事件是否被吸收,如果setAccepted(true),那么这个事件不会被传递到父控件中去。

3、事件处理是从子控件开始依次传递到各级父控件的。

4、事件过滤器返回true和e->setAccepted(true)都可以阻断事件往父控件传递,但在事件过滤器中设置e->setAccepted并不能阻止当前控件的事件处理。



相关源码

qapplication->QApplication::notify

在这里插入图片描述

qapplication->QApplicationPrivate::notify_helper

在这里插入图片描述



总结

Qt的事件传递大致就上述说的两步,因为要介绍里面的一些细节,所以看起没那么直观,这里写一个示意的代码来说明整个流程。

现有appFilter(obj, e)和widgetFilter(obj, e)事件过滤器,分别被qApp和各个控件安装。

void handle(systemEvent)
{
    e = toEvent(systemEvent); //转换为QEvent

    /* qApp的事件过滤 */
    if (appFilter(systemEvent.qApp, e) == true)
        return;

    w = qApp.m_widget.childAt(e.pos());  //获取MyEdit

    while (w)
    {
        /* 事件过滤 */
        if (appFilter(w, e) == true)
            break;
        if (widgetFilter(w, e) == true)
            break;

        w.event(e); //事件处理

        /* 判断事件是否被接收 */
        if (e.isAccept())
            break;

        w = w.parentWidget();
    }
}



相关的性能问题

从上面给出的例子可以看到,我们点击一个控件时,会有两个步骤。

1、Qt会先从窗口找到MyWindow,再从MyWindow找到MyButton,再从MyButton找到MyEdit。

2、找到MyEdit后,会执行MyEdit的事件过滤,事件处理,然后依次对MyButton和MyWindow做同样的操作。

这两个步骤带来了一些性能的问题。

1、找MyEdit的过程是递归的,带来了函数栈的开销。

2、如果MyEdit的父控件确实需要处理点击事件,那么多次调用事件过滤,事件处理是有必要的,但是如果传递的是绘图事件呢?当一个子控件接收到绘图事件,这个事件也会被传递给它的父控件们,而想要提升UI的性能,减少不必要的刷新是一个方向。

我们做一个测试,在MyWindow中做以下修改。

在这里插入图片描述

放置多个QFrame控件嵌套在一起。

创建一个MyButton控件,分别对比它作为frame_14和窗口的子控件时执行update的耗时。

update本质上是发送绘图事件来重回控件。

界面中的按钮绑定槽。

void MyWindow::on_pushButton_clicked()
{
    QElapsedTimer timer;
    timer.start();

    for (int i = 0; i < 1000000; ++i)
        button->update();

    qDebug() << "cost time: " << timer.elapsed() << "ms";
}

测试结果:

作为窗口子控件时:

在这里插入图片描述

作为frame_14的子控件时:

在这里插入图片描述

可以看出差别是很大的。

所以我们在设计UI架构时,应避免出现层次过多的情况。



版权声明:本文为weixin_45001971原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。