参考:
Qt 事件系统总结
Qt 事件
-
在 Qt 中,事件(event)是一些对象,它们都派生自抽象类
QEvent
-
事件是应用程序所关心的,程序内部发生的事或是外部行动的结果
-
当一个事件发生,Qt 会创建一个事件对象,它是一个派生自抽象类
QEvent
的类的实例,用来代表发生的事件 -
有时一个事件包含多个事件类型,比如鼠标事件
QMouseEvent
又可以分为鼠标按下、双击、滚轮滚动和移动等多种操作 -
事件由谁接收:事件可以被任何派生自
QObject
的类型的实例接收和处理- QObject 类的三大核心功能其中之一就是:事件处理。
QObject
通过event()
函数获取和分发事件。
- QObject 类的三大核心功能其中之一就是:事件处理。
-
事件由谁产生:
- 由操作系统或应用程序内部产生
- 使用
bool QEvent::spontaneous() const
判断事件是否来自于应用程序外部,如果事件来自于外部返回 true,否则返回 false
Qt 事件循环
主事件循环
- 每一个 Qt 程序,main 函数中一般都有唯一的 QCoreApplication/QGuiApplication/QApplication,并在末尾调用
exec()
。这样就开始 Qt 的事件循环 - 事件循环的本质是无限循环,使用
exec()
开启事件循环,如果事件循环不结束,exec()
后面的代码永远不会执行。 - 在执行
exec()
函数之后,程序将进入事件循环来监听应用程序的事件。事件多数情况下是被分发到一个队列中(事件队列),当队列中有事件时就不停的将队列中的事件发送给QObject
对象,当队列为空时就循环等待事件。 - 当事件发生时,Qt 将创建一个事件对象。Qt 中所有事件类都继承于
QEvent
,这也是事件不同于信号(信号与槽中的信号)的一点 —— 事件是类具有特定类型, 而信号是信号函数 QCoreApplication
中提供了一下处理事件的函数:
/// 给任何线程的任何对象发送任何事件都会调用该函数。可以重写该函数来达到全局的事件处理与控制的功能。
[virtual] bool QCoreApplication::notify(QObject *receiver, QEvent *event)
/// 直接使用 notify() 将事件发送给事件的接收者,返回事件处理程序返回的值。事件被发送后并不会被自动被销毁,因此事件对象常常可以声明在堆栈上作为自动变量。
[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
/// 添加事件到事件队列然后立即返回。事件必须声明在堆上。当控制返回到主事件循环时,所有存储在事件队列中的事件都将使用 notify 函数发送出去。
/// 事件按优先级排队,高优先级的事件先入队。事件优先级是一个整机变量。
/// 函数是【线程安全】的
[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
/// 立即分派在事件队列中的所有事件接收对象为 receiver 事件类型为 event_type 的事件。
/// 如果 receiver = nullptr ,所有事件类型为 event_type 都会被立即发送给接收者
/// 如果 event_type = 0, 所有发送给 receiver 的事件都会被立即发送给它
[static] void QCoreApplication::sendPostedEvents(QObject *receiver = nullptr, int event_type = 0)
/// 告诉应用以指定的返回码退出事件循环,exec() 将结束并返回该返回码,任何非零返回码意味着错误。
[static] void QCoreApplication::exit(int returnCode = 0)
/// 告诉应用正常退出事件循环。相当于 exit(0)。通常信号与该槽应该进行[队列连接],因为如果在主事件循环开始之前,信号发送导致的 quit() 回调是无效的(事件循环没有开始,何谈退出)
/// 使用队列连接确保槽函数不会再事件循环开始前执行。
[static slot] void QCoreApplication::quit()
QEventLoop 类
/// 开启事件循环
int exec(QEventLoop::ProcessEventsFlags flags = AllEvents)
void exit(int returnCode = 0)
/// 如果事件循环是运行着的,返回 true,否则返回 false。事件循环在 exec() 和 exit() 之间被认为是运行的
bool isRunning() const
[slot] void QEventLoop::quit()
-
事件循环是可以嵌套的,当在子事件循环中的时候,父事件循环中的事件实际上处于中断状态。这就相当于循环嵌套。
-
当子事件循环结束,exec() 返回之后才可以执行父循环中的事件。当然,这不代表在执行子循环的时候,类似父循环中的界面响应会被中断,因为往往子循环中也会有父循环的大部分事件,执行QMessageBox::exec(),QEventLoop::exec()的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是由于GUI界面的响应已经被包含到子循环中了,所以GUI界面依然能够得到响应。
-
如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出。
事件的转发与处理流程
Qt 程序需要在 main()
函数创建一个 QApplication
对象,然后调用它的 exec()
函数。这个函数就是开始 Qt 的事件循环。在执行 exec()
函数之后,程序将进入事件循环来监听应用程序的事件。
同步与异步事件
- 同步事件: 调用
QCoreApplication::sendEvent()
,会直接使用QCoreApplication::notify
将事件发送给事件接收方,事件会立即被执行。 - 异步事件:调用
QCoreApplication::postEvent()
, 会将事件加入到事件队列,等待事件循环进行处理。
事件分发器
Qt 中每个事件类型都有一个枚举类型 QEvent::Type
的数据成员,通过该枚举类型,在程序中可以区分不同的事件类型,根据不同的事件类型进行不同的动作。如下即为 QObject::event
的源码:
- 事件分发器根据事件的不同,将事件发送给不同的事件处理器进行处理,因此可以通过重写事件处理器函数,让指定事件发生发生时,执行我们想要的事件处理动作。
bool QObject::event(QEvent *e)
{
switch (e->type()) {
case QEvent::Timer:
timerEvent(