在使用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架构时,应避免出现层次过多的情况。