Qt 5.12实战
上QQ阅读APP看书,第一时间看更新

3.11 事件

3.11.1 基本概念

事件是由程序内部或外部产生的事情或某种操作的统称。比如,用户按下键盘或鼠标,就会产生一个键盘事件或鼠标事件(这是由程序外部产生的事件);当窗口第一次显示时,会产生一个绘制事件,以通知窗口需要重新绘制自身,从而使窗口可见(这是由程序内部产生的事件)。事件有两个来源:程序外部和程序内部。

对于外部产生的事件,比如用户的操作(单击鼠标、按下键盘),首先会被操作系统内核中的设备驱动程序所感知,然后操作系统将这些消息(与所进行操作的相关信息数据)放入GUI应用程序(Qt应用程序)的消息队列,Qt程序依次读取这些消息,进行分发,转化为事件类QEvent(将操作数据代码化),再进入事件处理函数进行处理。在事件处理函数中,我们通过参数(事件类QEvent或其子类的指针对象)能够解析出操作的详细信息,比如鼠标按下的是左键还是右键、键盘按下的是哪个键等,有了用户操作的详细数据信息,我们就可以进行相应的处理了。

Qt程序内部产生的事件(比如定时器超时)也是一样的,只不过Qt直接将事件转为事件类,然后分发、处理。

前面两段话有点抽象,我们将其细化一下。首先事件要被Qt程序所获取,那么具体是谁来做这个事情呢?

Qt中的事件循环是由QApplication.exec()开始的。当该语句执行后,应用程序便建立起了一个事件循环机制,该机制不断地从系统的消息队列中获取与应用程序有关的消息,并根据事件携带的信息将事件对应到目的窗口或控件,由于Qt中窗口和控件都是继承自QObject类,因此具有事件处理能力(QObject类的三大核心功能之一就是事件处理)。QObject类是所有Qt类的基类,是Qt对象模型的核心。QObject类通过调用event()函数获取事件,所有需要处理事件的类都必须继承自QObject,通过重定义event()函数实现自定义事件的处理,或者将事件交给父类处理。

3.11.2 事件的描述

在Qt中,使用抽象类QEvent及其子类来描述事件。所有事件都是QEvent类的派生类对象,用于表示在应用程序中发生的事情,或者是应用程序需要知道的外部活动的结果。

QEvent类是所有事件类的基类。事件对象包含事件参数:基本的QEvent类只包含一个事件类型参数,QEvent子类包含了额外的描述特定事件的参数。例如,子类QMouseEvent用于描述与鼠标相关的事件,子类QKeyEvent用于描述与键盘相关的事件等。

Qt中常见的事件有鼠标事件(QMouseEvent)、键盘事件(QKeyEvent)、绘制事件(QPaintEvent)、窗口尺寸改变事件(QResizeEvent)、滚动事件(QScrollEvent)、控件显示事件(QShowEvent)、控件隐藏事件(QHideEvent)、定时器事件(QTimerEvent)等。

3.11.3 事件的类型

事情类型用枚举QEvent::Type来表示。这个枚举类型定义了Qt中有效的事件类型,比如QEvent::ApplicationStateChange表示应用程序的状态已更改、QEvent::FileOpen表示文件打开请求(QFileOpenEvent)等。

3.11.4 事件的处理

Qt的主事件循环(QCoreApplication::exec())从事件队列中获取本地窗口的系统事件,将它们转化为QEvents类对象,然后将转换后的事件发送给 QObjects类对象。函数event()不处理事件,根据传递的事件类型,它调用该特定类型事件的事件处理程序来进行处理。

一般来说,事件来自底层窗口系统(spontaneous()返回true),但是也可以调用QCoreApplication::sendEvent()和QCoreApplication::postEvent()(spontaneous()返回false)来手动发送事件。

QObjects类通过调用QObject::event()函数来接收事件。该函数可以在子类中重新实现,来处理自定义的事件以及添加额外的事件类型,其中QWidget::event()就是一个很著名的例子。默认情况下,像QObject::timerEvent()和QWidget::mouseMoveEvent()这样的事件可以被发送给事件处理函数。QObject::installEventFilter()允许一个对象拦截发往另一个对象的事件。

我们不需要知道Qt是怎样把事件转换为QEvent类对象或其子类对象的,只需要处理这些事件或在事件函数中发出的信号即可。比如对于按下鼠标按钮的事件,不需要知道Qt是怎样把该事件转换为QMouseEvent类对象的(QMouseEvent类是用于描述鼠标事件的类),只需要知道从QMouseEvent类对象的变量中获取具体的事件即可。在处理鼠标按下事件的函数中,它的参数就是一个QMouseEvent类型的指针变量,我们可以通过该变量判断按下的是鼠标左键还是鼠标右键,代码如下:

3.11.5 事件的传递

事件的传递也称事件的分发。它的基本规则是:若事件未被目标对象处理,则把事件传递给父对象处理;若父对象仍未处理,则传递给父对象的父对象处理;重复这个过程,直至事件被处理或到达顶层对象为止。注意:事件是在对象间传递的,这里是指对象的父子关系,而不是指类的父子关系。

在Qt中有一个事件循环,该循环负责从可能产生事件的地方捕获各种事件,并把这些事件转换为带有事件信息的对象,然后由Qt的事件处理流程分发给负责处理事件的对象来处理事件。

通过调用QApplication::exec()函数启动事件主循环。主循环从事件队列中获取事件,然后创建一个合适的QEvent类的对象或其子类的对象来表示该事件。在此步骤中,事件循环首先处理所有发布的事件,直到队列为空;然后处理自发的事件;最后处理在自发事件期间产生的已发布事件。注意:发送的事件不由事件循环处理,该类事件会被直接传递给对象。