一、事件简介
QT将系统产生的消息转化为QT事件,QT事件被封装为对象,所有的QT事件均继承抽象类QEvent,用于描述程序内部或外部发生的动作,任意的QObject对象都具备处理QT事件的能力。
事件是由系统或者QT平台本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。常见的Qt事件如下:
- 键盘事件(QKeyEvent): 处理键盘按键的按下和松开事件。
- 鼠标事件(QMouseEvent): 处理鼠标移动、鼠标按键的按下和松开事件。
- 拖放事件(QDragEvent 和 QDropEvent): 用鼠标进行拖放操作时涉及的事件,包括拖拽和放置。
- 滚轮事件(QWheelEvent): 处理鼠标滚轮的滚动事件。
- 绘图事件(QPaintEvent): 在控件需要重新绘制时触发,用于自定义绘制。
- 定时事件(QTimerEvent): 在定时器到期时触发,用于执行周期性任务。
- 焦点事件(QFocusEvent): 处理键盘焦点的移动,如窗口控件获得或失去焦点。
- 进入和离开事件(QEnterEvent 和 QLeaveEvent): 处理鼠标移入或移出窗口控件的事件。
- 移动事件(QMoveEvent): 处理窗口控件的位置改变事件。
- 大小改变事件(QResizeEvent): 处理窗口控件的大小改变事件。
- 显示和隐藏事件(QShowEvent 和 QHideEvent): 处理窗口控件的显示和隐藏事件。
- 窗口事件(QWindowStateChangeEvent): 处理窗口是否为当前窗口的状态变化事件。
常用的鼠标事件:
- void mousePressEvent(QMouseEvent *); 按下
- void mouseReleaseEvent(QMouseEvent *); 弹起
- void mouseMoveEvent(QMouseEvent *); 按下时并移动
- void mouseDoubleClickEvent(QMouseEvent *); 双击(会触发按下和弹起);
通过QMouseEvent 可以判断出是个按键:
- event->button() == Qt::LeftButton
- event->button() == Qt::RightButton
- event->button() == Qt::MidButton
常用的键盘事件:
- void keyPressEvent(QKeyEvent *); 按下
- void keyReleaseEvent(QKeyEvent *); 弹起
通过QKeyEvent 可以判断出按键的键值:
- event->key == Qt::Key_Up;
常用的窗口事件:
- void closeEvent(QCloseEvent *); 窗口关闭
- void paintEvent(QPaintEvent *); 窗口显示/绘图事件
- void moveEvent(QMoveEvent *); 窗口移动
二、事件的分类
方式一:根据事件的来源和传递方式,事件可分为以下三大类:
- 自发事件:这是由窗口系统生成的,这些事件置于系统队列中,并由事件循环一个接一个地处理。
- 发布的事件(Posted events):该类事件由 Qt 或应用程序(属于自定义事件)生成,这些事件发布后由 Qt 排队,并由事件循环处理。
- 发送的事件(Sent events):该类事件由 Qt 或应用程序(属于自定义事件)生成,这些事件直接发送到目标对象,不经过事件循环处理。
方式二:事件被细分为很多种类型(有一百多种),每一种类型使用 QEvent 类中的枚举常量进行表示,比如 QMouseEvent 管理的鼠标事件有鼠标双击、移动、按下等类型,这些类型分别使用 QEvent::Type 枚举类型中的枚举常量 MouseButtonDblClick、MouseMove、MouseButtonPress 表示。所有的类型分类请查阅帮助文档。可使用函数Type QEvent::type() const;获取事件的类型。
三、事件处理流程
我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在Qt框架内部为我们提供了一些事件处理机制,当窗口事件产生之后,事件会经过:事件派发(Qt应用程序对象发送的)—>事件过滤—>事件分发—>事件处理几个阶段。
每一个Qt应用程序都对应一个唯一的QApplication应用程序对象,然后调用这个对象的exec()函数,这样Qt框架内部的事件检测就开始了(程序将进入事件循环来监听应用程序的事件)。
事件传递基本原则:
- 若事件未被目标对象处理,则把事件传递给其父对象处理,若父对象仍未处理,则再传递给父对象的父对象处理,重复这个过程,直至这个事件被处理或到达顶级对象为止。注意:事件是在对象间传递的,这里是指对象的父子关系,而不是指类的父子关系。
- 在 Qt 中有一个事件循环,该循环负责从可能产生事件的地方捕获各种事件,并把这些事件转换为带有事件信息的对象,然后由 Qt 的事件处理流程分发给需要处理事件的对象来处理事件。
事件在Qt中产生之后,其分发过程是这样的:
1)通过调用 QCoreApplication::exec()函数启动事件主循环,主循环从事件队列中获取事件,然后创建一个合适的 QEvent 对象或其子类类型的对象来表示该事件,在此步骤中,事件循环首先处理所有发布的事件,直到队列为空,然后处理自发的事件,最后处理在自发事件期间产生的已发布事件。注意:发送的事件不由事件循环处理,该类事件会被直接传递给对象;
2)当事件产生之后,Qt使用应用程序对象调用notify()函数将事件发送到指定的窗口;
3)事件在发送过程中可以通过事件过滤器eventFilter()函数进行过滤,默认不对任何产生的事件进行过滤;
4)当事件发送到指定窗口后,窗口的事件分发器event()函数(每一个窗口都有)会对收到的事件进行分类;
5)事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函数),比如鼠标事件。
每个事件处理器函数都对应一个唯一的事件,这为我们重写定义事件的处理动作提供了便利。Qt提供的这些事件处理器函数都是回调函数,也就是说作为使用者我们只需要指定函数的处理动作,关于函数的调用是不需要操心的,当某个事件被触发,Qt框架会调用对应的事件处理器函数。
Qt里的事件基本都是在Protected Functions,要想调用这些基本,其必须是继承QObject或QWidget。
窗口的事件处理器非常好找,规律是这样的:1)受保护的虚函数;2)函数名分为2部分:事件描述+Event;3)函数带一个事件类型的参数。
定时器事件
Qt里定时器设置2种方法:1种是调用QTimer类,另外一种是就是调用QObject类的Protected Function函数里的TimerEvent()事件(定时器事件)。
四、事件循环机制
Qt中的事件循环(Event Loop)是一种机制,用于处理事件和信号,并且驱动应用程序的整个事件处理过程。
事件循环主要由Qt的事件系统和对象的信号与槽机制组成。
以下是关于事件循环机制的一些说明:
1.事件处理流程:在事件循环机制下,Qt应用程序会进入一个无限循环,该循环会不断处理各种事件,直到应用程序退出。在事件循环中,Qt会不停的检查事件是否发生,然后会分发相应的事件到合适的对象进行处理。
2.事件源:事件源是导致事件发生的对象,可以是用户操作、系统事件或其他应用程序内部事件。列如,按钮的点击、定时器的超时、网络数据的到达等都可以作为事件源。
3.事件类型和事件过滤器:Qt事件系统提供了一系列事件类型,用于标识不同类型的事件,列如鼠标点击事件、键盘事件、定时器事件等。事件过滤器可以被用来截获和修改事件的处理过程。通过重写事件过滤器函数,可以在对象处理事件之前先拦截和处理事件。
4.事件分发和派发:当事件发生时,Qt会将事件分发给合适的对象进行处理。事件的分发是根据事件分发规则和事件传递机制来进行的。经过事件分发后,事件最终会被派发到目标对象的事件处理函数(列如QWidget的事件处理函数)进行具体的处理。
5.信号和槽机制:信号与槽机制是一种Qt特有的机制,用于对象之间的通信和事件处理。当对象发生某个特定的事件或者状态发生变化时,可以通过发射信号来通知其他对象。其他对象可以将自己的槽函数与信号进行连接,以响应信号的发射并执行相应的处理逻辑。
6.线程和事件循环:每个线程都有一个独立的事件循环,这是关键!!!在多线程应用程序中,每个线程通常都有自己的事件循环,用来处理来自线程内部或外部的事件和信号.线程之间可以通过信号与槽机制进行异步的事件通信。
五、事件与信号区别
Qt 的事件和Qt中的signal不一样. 信号通常用来"使用"widget, 而事件用来"实现" widget。比如一个按钮, 我们使用这个按钮的时候, 我们只关心他clicked()的signal, 至于这个按钮如何接收处理鼠标事件,再发射这个信号,我们是不用关心的。但是如果我们要重载一个按钮的时候,我们就要面对event了。 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候.。
信号通过事件实现,事件可以过滤,事件更底层,事件是基础,信号是扩展。
信号和槽机制用于对象之间的通信,而事件机制用于对象的行为和状态变化的处理。
信号是由对象产生的,而事件则不一定是由对象产生的(比如由鼠标产生的事件),事件通常来自底层的窗口系统,但也可以手动发送自定义的事件,可见信号和事件的来源是不同的。
事件既可以同步使用,也可以异步使用 (取决于调用 sendEvent()还是 postEvents()),而使用信号和槽总是同步的。事件的另一个好处是可以被过滤。
六、常用事件函数(系统定义)
Qt 事件对象(QEvent)的两个函数:
- accept() 通知QT平台,事件处理函数要处理这个事件;
- ignore() 则通知QT平台,事件处理函数不处理这个事件。
如果一个事件处理函数调用了一个事件对象的accept()函数,这个事件就不会被继续传播给其父组件;如果事件对象调用了事件的ignore()函数,QT平台会从其父组件中寻找另外的接受者。
在事件处理函数中,可以使用isAccepted()来查询某个事件是不是已经被接受。
通常很少会使用accept()和ignore()函数,如果希望忽略事件,只要调用父类的相应事件处理函数即可。
由于无法确认父类中相应的事件处理函数有没有额外的操作,如果在子类中直接使用ignore()函数忽略事件,QT会去寻找其他的接受者,父类的操作会被忽略(因为没有调用父类的同名函数),这可能会有潜在的危险。
为了避免子类去调用accept()和ignore()函数,而是尽量调用父类实现,QT做了特殊的设计:事件对象默认是accept的,而作为所有组件的父类QWidget的默认实现则是调用ignore()。
因此,如果子类重新实现事件处理函数,不调用QWidget的默认实现,就等于接受事件;如果要忽略事件,只需调用QWidget的默认事件处理函数实现。
七、常用事件函数(自定义重写)
事件分发器event
事件对象创建完毕后,QT将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型分发给不同的事件处理器(event handler)。event()函数主要用于事件的分发。如果期望在事件分发前做一些操作,可以重写event()函数。
举例如下:
/* 在QWidget组件中监听左键按下事件 */
bool MyWidget::event(QEvent *event)
{
if (event