Qt 事件
事件介绍
事件是应用程序或者外部的事情或者动作的统称。在Qt平台中使用一个对象来表示一个事件。所有的Qt事件均继承于抽象类QEvent。事件是由系统或者Qt平台本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件是在用户操作时发出,如键盘事件、鼠标事件等,另一些事件则是由系统本身自动发出,如定时器事件。常见的Qt事件如下:
常见事件描述:
事件名称 | 描述 |
---|---|
⿏标事件 | ⿏标左键、⿏标右键、⿏标滚轮,⿏标的移动,⿏标按键的按下和松开 |
键盘事件 | 按键类型、按键按下、按键松开 |
定时器事件 | 定时时间到达 |
进⼊离开事件 | ⿏标的进⼊和离开 |
滚轮事件 | ⿏标滚轮滚动 |
绘屏事件 | 重绘屏幕的某些部分 |
显⽰隐藏事件 | 窗⼝的显⽰和隐藏 |
移动事件 | 窗⼝位置的变化 |
窗⼝事件 | 是否为当前窗⼝ |
⼤⼩改变事件 | 窗⼝⼤⼩改变 |
焦点事件 | 键盘焦点移动 |
拖拽事件 | ⽤⿏标进⾏拖拽 |
事件的处理
事件的处理一般常用做法是: 重写相关的Event函数;
在Qt中,几乎所有的Event函数都是虚函数,所以都可以重写。如:在鼠标的进入和离开事件时,直接重新实现enterEvent()和leaveEvent()函数即可。
enterEvent函数和leaveEvent函数原型如下:
进入离开事件
鼠标进入事件
eg1: 使用enterEvent事件来实现,鼠标进入一个Label文本框,就在控制台输出“进入文本框”;
首先在ui文件上设计一个带边框的文本框:
在重新设一个新类,然后让这个类继承于Label控件,这个新类的名字我们就取名为“MyLabel”:
然后在MyLabel类中重写enterEvent函数(注意:在重写enterEvent函数的时候记得给MyLabel添加一个MyLabel(QWidget*)的构造函数,方便Qt将我们创建的MyLabel对象挂载到对象树上去):
通过Qt Creater 将ui文件上的QLabel控件提升为MyLabel控件:
运行程序:
鼠标点击事件
eg2: 当鼠标在Label文本框中点击时,则获取到鼠标坐标:
- 通过代码设置MyLabel的位置和大小:
- 重写mousePressEvent函数:
- 运行结果:
其中"label: " 打印的是相对于label文本框中的坐标,"global: " 打印的是相对于整个屏幕的左上角的坐标;
按键事件
Qt 中的按键事件是通过QKeyEvent类来实现的。当键盘上的按键被按下或者释放时,键盘事件就会被触发;
单个按键按下事件
eg: 当某个按键被按下时,输出"xxx按键被按下了";
核心代码:
运行结果:
组合按键按下事件
Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改键。在 Qt 中,键盘事件可以与修改键
⼀起使⽤,以实现⼀些复杂的交互操作.KeyboardModifier 中修改键的具体描述如下:
Qt::NoModifier | ⽆修改键 |
Qt::ShiftModifier | Shift 键 |
Qt::ControlModifier | Ctrl 键 |
Qt::AltModifier | Alt 键 |
Qt::MetaModifier | Meta键(在Windows上指Windows键,在macOS上指Command键) |
Qt::KeypadModifier | 使⽤键盘上的数字键盘进⾏输⼊时,Num Lock键处于打开状态 |
Qt::GroupSwitchModifier | ⽤于在输⼊法 组之间 切换 |
核心代码:
运行结果:
鼠标事件
在 Qt 中,⿏标事件是⽤ QMouseEvent 类来实现的。当在窗⼝中按下⿏标或者移动⿏标时,都会产⽣⿏标事件。
利⽤ QMouseEvent 类可以获取⿏标的哪个键被按下了以及⿏标的当前位置等信息。
鼠标单击事件
在 Qt 中,⿏标按下是通过虚函数 mousePressEvent() 来捕获的;
eg:获取单击鼠标类型
核心代码:
运行结果:
鼠标释放事件
⿏标释放事件是通过虚函数 mouseReleaseEvent() 来捕获的。
核心代码:
运行结果:
鼠标双击事件
⿏标双击事件是通过虚函数:mouseDoubleClickEvent() 来实现的
核心代码:
运行结果:
鼠标移动事件
⿏标移动事件是通过虚函数:mouseMoveEvent() 来实现的。同时为了实时捕获⿏标位置信息,需要通过函数 setMouseTracking() 来追踪⿏标的位置.
注意: setMouseTracking() 函数默认是 false,需要设置为 true,才能实时捕获⿏标位置信息。否则只有当⿏标按下时才能捕获其位置信息。因为如果默认开启为true的话,我们哪怕稍微移动一小段鼠标,鼠标移动事件就会触发一大片,主线程处理不过来,可能会导致此程序卡死;
核心代码:
运行结果:
滚轮事件
在 Qt 中,⿏标滚轮事件是通过 QWheelEvent 类来实现的。滚轮滑动的距离可以通过 delta() 函数获
取。
delta() 函数原型如下:
int QGraphicsSceneWheelEvent::delta() const
其中返回值代表滚轮滑动的距离。正数表⽰滚轮相对于⽤⼾向前滑动,负数表⽰滚轮相对于⽤⼾向后滑动。
核心代码:
运行结果:
定时器事件
Qt 中在进⾏窗⼝程序的处理过程中,经常要周期性的执⾏某些操作,或者制作⼀些动画效果,使⽤定时器就可以实现。所谓定时器就是在间隔⼀定时间后,去执⾏某⼀个任务。定时器在很多场景下都会使⽤到,如弹窗⾃动关闭之类的功能等。
Qt中的定时器分为 QTimerEvent 和 QTimer 这2个类。
- QTimerEvent类 ⽤来描述⼀个定时器事件。在使⽤时需要通过 startTimer() 函数来开启⼀个定时
器,这个函数需要输⼊⼀个以毫秒为单位的整数作为参数来表明设定的时间,它返回的整型值代表
这个定时器。当定时器溢出时(即定时时间到达)就可以在 timerEvent() 函数中获取该定时器的
编号来进⾏相关操作。- QTimer类 来实现⼀个定时器,它提供了更⾼层次的编程接⼝,如:可以使⽤信号和槽,还可以设
置只运⾏⼀次的定时器。
QTimerEvent 类
eg: 在ui界面上放置两个QLCDNumber,上面那个以每一秒增加一次,下面这个以每秒两次增加一次;
- 通过Qt Creater设计两个QLCDNumber:
- 在Qidget类中设计两个成员变量用来记录两个不同的定时器:
- 使用startTimer函数开启两个定时器,一个以1000ms进行一次timerEvent事件,一个以2000ms进行一次timerEvent事件:
- 重写timerEvent事件:
- 运行结果:
QTimer 类
⽰例:在UI界⾯放置⼀个 Label 标签,两个按钮,分别是 “开始” 和 “停⽌” ,当点击 “开始” 按钮时,
开始每隔1秒计数⼀次,点击 “停⽌” 按钮时,暂停计数。
- 首先在ui界面设计初始界面:
- 给两个按钮分别设计对应的槽函数:
给定时器信号timeout绑定槽函数:
4. 运行结果:
事件分发器
概述
在 Qt 中,事件分发器(Event Dispatcher) 是⼀个核⼼概念,⽤于处理 GUI 应⽤程序中的事件。事件分发器负责将事件从⼀个对象传递到另⼀个对象,直到事件被处理或被取消。每个继承⾃ QObject类 或QObject类 本⾝都可以在本类中重写 bool event(QEvent *e) 函数,来实现相关事件的捕获和拦截。
事件分发器工作原理
在 Qt 中,我们发送的事件都是传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函
数。所有的事件都会进⼊到这个函数⾥⾯,那么我们处理事件就要重写这个 event() 函数。event() 函
数本⾝不会去处理事件,⽽是根据 事件类型(type值)调⽤不同的事件处理函数。事件分发器就是⼯
作在应⽤程序向下分发事件的过程中,如下图:
如上图,事件分发器⽤于分发事件。在此过程中,事件分发器也可以做拦截操作。事件分发器主要是通过 bool event(QEvent *e) 函数来实现。其返回值为布尔类型,若为 ture,代表拦截,不向下分
发。
上面是比较官方的理解,下面我来说一说自己对于Qt事件分发器的理解:
举个具体的例子:假设一个ui界面上有一个Label控件,现在我在这个Label控件上鼠标左键点击了一下,讲道理来说这是会触发这个Label控件的鼠标点击事件的,如果我们重写了mousePressEvent()事件,那么接下来Qt程序就会去调用这个事件处理函数,来处理用户做出点击操作,但是现在有了Qt事件分发器这一层,因此用户产生的这个点击事件,会先到事件分发器这一层,在这一层中用户可以来决定是否继续向下分发这个事件,如果用户在这一层拦截了这个鼠标点击事件,那么mousePressEvent()事件就不会被执行,转而去执行用户在事件分发层设计的一些拦截逻辑;相反,如果用户下放了这个事件,那么这个鼠标点击事件最终会被它的默认处理动作,也就是mousePressEvent()事件来进行处理。
在这其中,每个控件的 bool event(QEvent* ev); 接口被当作每个控件自己的事件分发器,对于一个控件来说,如果想要享受事件分发器的功能,那么就请重写event()接口,同时如果确实想要拦截的话,那么返回值请return true;否则return false;
eg: 拦截一下Label控件的鼠标点击事件:
核心代码:
运行结果:
事件过滤器
在 Qt 中,⼀个对象可能经常要查看或拦截另外⼀个对象的事件,如对话框想要拦截按键事件,不让别的组件接收到,或者修改按键的默认值等。通过上⾯的学习,我们已经知道,Qt 创建了 QEvent事件对象之后,会调⽤QObject 的 event()函数 处理事件的分发。显然,我们可以在 event()函数 中实现拦截的操作。由于 event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个event()函数。这当然相当⿇烦,更不⽤说重写 event()函数还得⼩⼼⼀堆问题。好在 Qt 提供了另外⼀种机制来达到这⼀⽬的:事件过滤器。
事件过滤器是在应⽤程序分发到 event事件分发器 之前,再做⼀次更⾼级的拦截。如下图⽰:
上面是对于事件过滤器比较官方的解释,我来说一说我自己对于事件过滤器的理解:
诚然,上面我们了解到的事件分发器似乎也能做到过滤的作用,但是事件分发器的过滤只能针对于一个控件本身所发出的事件进行过滤,如果有多个不同的控件,都有事件需要进行过滤操作,那么每个控件就都得重写自己的事件分发器(bool event(QEvent*ev);) 这显然是费时费力的方式,因此为了高效的完成事件过滤工作,Qt提出了事件过滤器的概念;
事件过滤器的一般使用步骤:
- 创建事件过滤器
要实现事件过滤器,需要创建一个继承于QObject的类,并重写里面的eventFilter()函数;该函数会在事件到达控件对象时被调用,开发者可以在其中处理事件并返回布尔值来指示是否拦截该事件。如果返回true,表示事件已被拦截,如果返回false,则表示事件尚未被处理,继续向下传递;
- 安装事件过滤器
使用QObject类中的installEventFilter()函数将事件过滤器安装到目标对象上。安装事件过滤器的对象可以是任何继承自QObject的类,包括窗口、控件等。安装完成后,当目标对象接收到事件时,事件过滤器就会被调用。
- 事件处理与分发
在eventFilter()函数内部,你可以对事件进行预处理,然后根据需要调用QEvent::accept()来接受事件,或QEvent::ignore()来忽略事件。如果事件不被过滤器处理,它应该返回false以允许事件继续传递给其原始的接收者。
eg: 演示事件过滤器的使用
- 使用Qt Design在界面上设计一个Label,并带有边框:
- 重写一个Label类,让其继承自QLabel,并将其命名为MyLabel:
3. 在Widget类中创建过滤器,也就是重写bool eventFilter()函数:
4. 将ui界面上的QLabel提升为MyLabel
5. 将这个过滤器设置进MyLabel这个控件里面去:
6. 运行结果