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

在使用Qt时,我们都知道能通过mousePressEvent,eventFilter等虚函数的重写来处理事件,那么当我们向一个界面发送事件,控件和它的父控件之间的事件传递过程是什么样的呢?
本文将以下图所示界面为例,结合源码介绍Qt事件传递的过程。
父到子的关系依次为:
MyWindow->MyButton->MyEdit。
Qt事件传递及相关的性能问题_第1张图片

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

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

过程

Qt事件传递及相关的性能问题_第2张图片
这里最后接收点击事件的是最上层的控件,也就是本例中的MyEdit。

相关源码

源码按执行顺序来排列。
qwindowsysteminterface.cpp->QWindowSystemInterface::sendWindowSystemEvents
Qt事件传递及相关的性能问题_第3张图片
qapplication->QApplicationPrivate::notify_helper
Qt事件传递及相关的性能问题_第4张图片
qwidgetwindow->QWidgetWindow::handleMouseEvent
Qt事件传递及相关的性能问题_第5张图片
childAt的过程:
1、遍历MyWindow的子控件,找到包含了点击坐标的MyButton。
2、遍历MyButton的子控件,找到包含了点击坐标的MyEdit。
3、遍历MyEdit的子控件,没找到子控件,返回MyEdit。

第二步、控件处理事件

过程

Qt事件传递及相关的性能问题_第6张图片
流程看起来有点复杂,用文字来表示就是以下几点:
1、每个控件处理事件时会先执行qApp安装的事件过滤器,即qApp->installEventFilter安装的。然后执行自己安装的事件过滤器,即this->installEventFilter安装的,最后才是事件处理,如mousePressEvent等。
2、控件在处理事件时可以用setAccepted来设置事件是否被吸收,如果setAccepted(true),那么这个事件不会被传递到父控件中去。
3、事件处理是从子控件开始依次传递到各级父控件的。
4、事件过滤器返回true和e->setAccepted(true)都可以阻断事件往父控件传递,但在事件过滤器中设置e->setAccepted并不能阻止当前控件的事件处理。

相关源码

qapplication->QApplication::notify
Qt事件传递及相关的性能问题_第7张图片
qapplication->QApplicationPrivate::notify_helper
Qt事件传递及相关的性能问题_第8张图片

总结

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中做以下修改。
Qt事件传递及相关的性能问题_第9张图片
放置多个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架构时,应避免出现层次过多的情况。

你可能感兴趣的:(Qt笔记,qt,ui,开发语言,事件传递,性能优化)