这篇文章会从 Qt 的事件处理流程来简单探讨下 Qt 的事件机制。
事件产生
首先要解决的问题是事件如何产生的,这部分暂时只简单的说明一下由来(且不考虑 Qt 自定义事件),不做深入。
以常见的鼠标事件和键盘事件为例,用户在点击之后,操作系统会收到对应的指令,然后 Qt 框架会通过操作系统接口来获取这些不同的事件,然后转换为 Qt 中的事件类型,再转发给指定的接收者(这里姑且这么称呼吧)。
那么,此时又会产生其他的问题,比如这两个非细节性问题:
- 操作系统是如何收到的?
- 操作系统如何发送给 Qt 的?
针对第一个问题,了解底层的同学会很容易想到,有些事件是通过硬件的中断产生的。对应的驱动程序会收到这些中断,然后封装成操作系统能识别的信号后发给操作系统。
第二个问题以 windows 为例,windows 是使用消息队列系统来处理事件,上一个问题中的驱动程序将事件传递到 windows 内核后,内核再生成对应事件的消息类型,并把这些消息发到对应的消息队列中。最终,Qt 收到这个消息后,又会转化为 Qt 内部的事件类型,并转发给对应的接收者。
就简单的解释一下吧,如果深入的话,能思考的东西又太多了。😂
Qt 的处理流程
在这部分中,为了方便讨论,我们忽略掉前面操作系统的细节,并简单地把 Qt 作为一切事件产生的源头。
此时,事件的具体处理过程就是:
事件派发 -> 事件过滤 -> 事件分发 -> 事件处理
另外,还有一些基础概念需要明确:
- Qt 的事件处理流程是一个基于事件循环(Event Loop)的核心机制,负责协调和管理应用程序中的所有事件。
- 不同类型的事件有不同的事件队列进行处理。
- Qt 会对不同的事件队列进行优化(这些事件队列都是线程安全的),比如绘图事件队列。
事件产生
现在,我们面对的第一个问题依然是事件的产生。
在 Qt 中,事件的类型可以分为(这里只是简单的按照是否是用户自定义来划分):
- 系统事件,包括鼠标事件、键盘事件、窗口重绘事件等等
- 用户自定义事件
系统事件的产生,比较容易理解,比如按一下键盘、鼠标,最小化、最大化窗口等等;而用户自定义事件需要调用QApplication::postEvent()
或QApplication::sendEvent()
函数来完成,前者会将自定义事件加入事件队列,逐步处理,后者会直接发送自定义事件,Qt 会立即处理这个事件。
另外,每一个 Qt 应用程序都对应唯一一个 QApplication 应用程序对象,然后调用这个对象的exec()
函数,开始事件循环。
事件过滤
事件产生后,Qt 就会按照事件队列派发给各个接收者,那么首先要经过的地方,就是事件过滤器。
当然,事件过滤生效的前提是需要先给指定的对象安装事件过滤器。而事件过滤器又分为两种:
- 全局事件过滤器
- 对象事件过滤器
二者本质没有区别,只看是为谁安装。顾名思义,全局事件过滤器就是为整个应用程序安装事件过滤器,对象事件过滤器就是为指定对象安装事件过滤器。
在二者都被安装的情况下,全局事件过滤器优先执行,对象事件过滤器的执行顺序遵循 Qt 的对象树系统,父对象优先执行,子对象后续执行。但如果是为一个对象安装多个事件过滤器,那执行顺序就是安装顺序。
此时,我们可以通过重写事件过滤器的eventFilter()
函数来完成对某些事件的优先处理或屏蔽,比如:1
2
3
4
5
6
7
8
9bool GlobalFilter::eventFilter(QObject *watched, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
// 如果是键盘事件,do something...
return true; // 拦截事件
}
// 默认 false 不拦截
// 对象事件过滤器也可以直接调用父对象的时间过滤函数,应该也是直接返回 false
return false;
}
上述功能,也可以通过直接重写父窗口的eventFilter()
函数,并为子对象安装父对象作为事件过滤器。这也是 Qt 中的常规做法。
事件分发
事件分发是由目标对象来完成的,目标对象通过事件分发将对应的事件分发给特定的事件处理器。如果需要提前处理,那么就需要重写目标对象的event()
函数,比如:1
2
3
4
5
6
7
8
9
10// MyWidget 是继承自 Widget 的子类
bool MyWidget::event(QEvent *e) {
if (e->type() == QEvent::MouseButtonPress) {
// 如果是鼠标按键按下事件,do something...
return true; // 事件已处理
}
// 这里一定要调用父类的事件分发函数
// 不然其他类型的事件,可能无法被转发给指定的事件处理器
return QWidget::event(e);
}
事件处理
事件处理是由目标对象的事件处理器来完成的,如果用户需要在发生这些事件之后有一些对应的操作,那么重写对应事件处理器函数就行了,比如:1
2
3
4void MyWidget::mousePressEvent(QMouseEvent *e) {
// 具体事件处理,do something...
QWidget::mousePressEvent(e); // 调用父类实现(可选)
}
上述代码中,重写了鼠标按下事件,当用户按下鼠标按钮后,程序会执行我们设置的代码。
总结
Qt 事件的处理流程:
事件派发 -> 事件过滤 -> 事件分发 -> 事件处理
其中,事件过滤可以分为:全局事件过滤和对象事件过滤,全局事件过滤的处理顺序先于对象事件过滤。
我们可以在事件过滤和事件分发过程中,拦截并处理指定事件;在事件处理过程中,完成我们想要的操作。