【Qt】事件
目录
一. 事件和信号的关系和区别
二.事件
2.1.什么是事件
2.2.处理事件的机制
三.重写事件处理函数简单示例
四.和鼠标相关的事件处理函数
4.1.鼠标按下
4.2.鼠标释放
4.3.鼠标双击事件
4.4.鼠标移动事件
4.5.鼠标滚轮事件
五.和键盘相关的事件处理函数
5.1.键盘按下
5.2.组合按键
六. 和定时器相关的事件
七.关于窗口移动和大小改变的事件处理函数
八.再提event()
一. 事件和信号的关系和区别
虽然 Qt 是一个卓越的跨平台 C++ 开发框架,但其许多能力的根源并非凭空创造,而是来自于操作系统本身。程序最终运行在操作系统之上,依赖于系统提供的核心支撑,例如:
-
事件处理
-
文件操作
-
多线程编程
-
网络编程
-
多媒体处理
Qt 的强大之处在于,它通过一套优雅的抽象层,封装了不同操作系统(如 Windows, macOS, Linux)的底层 API,为开发者提供了统一、高效的编程接口。
在用户交互这个核心领域,事件 和 信号槽 是 Qt 体系中两个至关重要且相互关联的概念。
我可以先告诉大家:信号就是部分事件的封装!!!!
一、事件的基石作用
事件 是操作系统级别的核心机制。当用户进行任何操作时——例如移动鼠标、按下键盘、点击窗口按钮——操作系统都会感知并生成一个对应的事件,然后将其投递到目标应用程序的消息队列中。
Qt 框架完美地封装了这套底层事件系统。在 Qt 的世界里,所有事件都被抽象为 QEvent
类或其子类的对象。程序员可以通过重写特定组件(如 QWidget
)的事件处理函数(例如 mousePressEvent()
, keyPressEvent()
)来响应这些事件。这为处理用户交互提供了最根本、最直接的方式。
二、信号槽:对事件的高级抽象与封装
尽管事件机制非常强大,但直接处理事件代码往往不够直观和便捷,尤其是在需要组件间通信时。为了提升开发效率和代码的可读性,Qt 在事件机制的基础之上,构建了独特的 信号槽 机制。
您可以这样理解它们的关系:
-
信号是事件的“语义化”表达:当一个底层事件发生时(如鼠标在按钮上释放),Qt 的组件(如
QPushButton
)不仅会处理这个事件,还会根据事件的语义,“发射”一个对应的信号(如clicked()
)。信号槽本质上是对事件机制的进一步封装和抽象,它将底层的、原始的用户操作,转换为了高级的、有明确意义的“信号”。 -
信号槽简化了交互逻辑:程序员的工作因此变得非常简单:只需要将某个信号(Signal)与一个特定的槽函数(Slot)连接起来。当信号被发射时,所有与之连接的槽函数都会被自动、异步地调用。
-
但是只有部分常用的事件被封装成了信号。
简而言之,事件是信号槽的底层支撑,信号槽是事件的上层建筑。事件机制赋予了 Qt 程序深度定制和响应一切用户操作的能力;而信号槽机制则在此基础上,为最常见的交互场景提供了简洁、高效、解耦的编程模型。
二.事件
2.1.什么是事件
事件是应⽤程序内部或者外部产⽣的事情或者动作的统称。
在Qt中使⽤⼀个对象来表⽰⼀个事件。
所有的Qt事件均继承于抽象类QEvent。
事件是由系统或者Qt平台本⾝在不同的时刻发出的。
当⽤⼾按 下⿏标、敲下键盘,或者是窗⼝需要重新绘制的时候,都会发出⼀个相应的事件。
⼀些事件是在⽤⼾ 操作时发出,如键盘事件、⿏标事件等,另⼀些事件则是由系统本⾝⾃动发出,如定时器事件。
常见的Qt事件如下:
这些子类里面包含了比较常见的事件
可以看到,非常丰富啊!!
2.2.处理事件的机制
1. 事件处理的基本过程
在 Qt 框架中,所有派生自 QObject
的类都具备处理事件的能力,而其中最主要的是从 QWidget
派生的窗口类和界面组件类。这是因为大多数事件(例如鼠标点击、键盘输入等)都源于用户与图形界面的交互。
当一个类接收到由应用程序分发的事件时,首先会调用其 event()
函数进行处理。
event()
是定义在 QObject
类中的一个虚函数,其原型如下:
bool QObject::event(QEvent *e)
该函数的参数 e
是一个指向 QEvent
类型的事件对象,通过调用 e->type()
可以获取具体的事件类型,从而判断是何种事件发生。
任何从 QObject
派生的类都可以通过重写 event()
函数来自定义事件处理逻辑。
在实现自定义的 event()
函数时,必须明确设置是否“接受”该事件。
QEvent
类提供了两个相关方法:
-
accept()
:表示事件已被接收,当前对象将负责处理该事件; -
ignore()
:表示事件被忽略,当前对象不处理该事件。
被接受的事件将由当前接收者进行处理;
而被忽略的事件则不会在此处终止,而是继续向上一级传播,通常传递给接收者的父容器组件,并由父容器组件的 event()
函数继续处理。
这个过程称为事件传播(propagation)。
事件可能会沿组件层级(父子关系)依次向上传递(往父类组件传递),直至被某个组件处理,或者最终传递到顶层窗口。
2.QWidget 类的典型事件处理函数
QWidget 类是所有界面组件类的基类。
Qt在QWidget 类内重新实现了函数event(),并针对一些典型类型的事 件定义了专门的事件处理函数,函数 event()会根据事件类型自动去运行相应的事件处理函数。
也就是说:
在 Qt 的事件处理机制中,QWidget
类的 event()
函数扮演着一个核心的“调度中心”角色。当该函数接收到一个事件(例如 QMouseEvent
)时,它会首先检查该事件的类型,然后根据类型判断是否存在已经定义好的对应事件处理函数(如 mousePressEvent()
、mouseReleaseEvent()
等)。
- 如果存在相应的处理函数,
event()
会调用对应事件处理函数来完成事件的具体响应; - 如果当前控件未对该类型事件定义处理函数,事件将不会在当前层级被“消化”,而是会继续向其父控件传播,直到被某个层级的控件处理或最终被忽略。
例如
- 当事件类型为
QEvent::MouseMove
时,event()
会调用mouseMoveEvent()
进行处理; - 如果事件类型为
QEvent::Paint
,则会调用paintEvent()
。
这两个事件处理函数的原型如下:
void QWidget::mouseMoveEvent(QMouseEvent *event); // 处理 QEvent::MouseMove 类型事件
void QWidget::paintEvent(QPaintEvent *event); // 处理 QEvent::Paint 类型事件
这些函数的参数 event
是对应具体事件类的对象,其中包含了事件的详细信息。
QWidget 中定义的典型事件处理函数均为受保护的虚函数,这意味着它们无法被外部类直接调用,但可以在派生类中被重新实现,以定制特定行为。
例如,如果我们自定义一个从 QWidget 派生的窗口类 Widget
,且不需要完全重写 event()
函数,而仅需对某些典型事件(如绘制事件)进行特殊处理,则可以直接在 Widget
中重新实现对应的处理函数,如 paintEvent()
。在该函数内部,可以编写绘制窗口背景图片等自定义绘制逻辑。
除了上述示例,QWidget 还定义了众多其他典型事件的处理函数,每个函数专门处理某一特定类型的事件。
需要注意的是,多个事件处理函数的参数 event
可能属于同一事件类,因为一个事件类(如 QMouseEvent
)可能用于表示多种相关的事件类型(如鼠标移动、按下、释放等)。
QWidget 中常见的典型事件处理函数如下表所示
当然这里只是一小部分而已!!!
- 注意:QWidget 中定义的典型事件处理函数均为受保护的虚函数,这意味着它们无法被外部类直接调用,但可以在派生类中被重新实现。
如果从QWidget或其派生类继承自定义了一个类,需要对某种类型的事件进行处理, 那么只需重新实现上表中与事件类型对应的事件处理函数。
- 注意:QWidget 类内部并没有为所有可能的事件类型都定义对应的专门事件处理函数。
QWidget 类只为那些最常用、最通用的事件(如鼠标、键盘、绘制、窗口尺寸变化等)提供了专门的、可供重写的虚函数。
如果需要处理的事件在QWidget中没有定义事件处理函数,就需要重新实现函数event(),判断事件类型后调用自己定义的事件处理函数。
3.QWidget的子类的event()也都有事件分发的能力
我们都必须知道:QWidget 类是所有界面组件类的基类,根据继承机制,子类能继承父类的所有属性和方法,那么也就是说明了,我们QWidget 类的所有子类都会继承QWidget 类的event()。
也就是说每一个子类的event()在默认情况下就是调用了QWidget 类的event()。
这个事件分发的行为,在 QWidget
及其所有子类的 event()
函数中都存在,但它的“源头”和“默认实现”在 QWidget
这一层。
让我们来分解一下:
1. 默认行为来自基类 (QWidget)
当您创建一个自定义类 MyWidget
(继承自 QWidget
)而没有重写它的 event()
函数时,它调用的event()就是其父类 QWidget::event()
的实现。
正是这个 QWidget
类中的 event()
函数实现包含了我们之前讨论的分发逻辑:它查看事件的类型,然后调用相应的特定处理函数,如 mousePressEvent()
或 keyPressEvent()
。
2. 子类可以重写 event()
任何从 QWidget
派生的子类都可以选择重新实现 event()
函数。
这时,情况就发生了变化:
-
子类拥有了完全的控制权:子类的
event()
函数现在成为了事件进入该对象的第一个入口。 -
责任与风险:子类在它的
event()
函数中,可以:-
处理特定事件后,阻止进一步分发:例如,它处理了空格键事件,然后直接返回
true
,那么keyPressEvent()
将永远不会被调用。 -
在处理前后调用基类的 event():这是最常见和正确的做法。子类可以先处理自己关心的一两种特殊事件,对于其他不处理的事件,直接交给基类(即
QWidget::event()
)去进行标准的分发。这样就保证了标准事件(如鼠标、绘图等)仍然能通过默认路径传递给对应的xxxEvent()
函数。 -
完全接管,不调用基类:如果子类的
event()
处理了所有事件且从不调用QWidget::event()
,那么该组件对于所有事件来说,其特定的xxxEvent()
函数都将失效。
-
注意了哈:这个继承是以QWidget类为起点,然后继承给QWidget类的子类,再从QWidget类的子类继承给QWidget类的子类的子类,如果说QWidget类的子类暗中重写了event(),那么QWidget类的子类的子类就会继承QWidget类的子类的修改后的event(),依次类推。
4.QWidget的子类都会继承QWidget类内定义好的典型事件处理函数
QWidget 类是所有界面组件类的基类,根据继承机制,子类能继承父类的所有属性和方法,那么也就是说明了,我们QWidget 类的所有子类不仅仅会继承QWidget 类的event(),还会继承QWidget类定义好的那些事件处理函数。
QWidget 的所有子类,无论是 Qt 自带的(如 QPushButton、QLabel),还是您自己自定义的,都会自动继承 QWidget 类中定义好的一系列虚函数形式的事件处理函数。
也就是子类会继承下面这些QWidget类中定义好的典型事件处理函数
当然这里只是一小部分而已!!!
注意这些都是虚函数啊!!!
注意了哈:这个继承是以QWidget类为起点,然后继承给QWidget类的子类,再从QWidget类的子类继承给QWidget类的子类的子类,如果说QWidget类的子类暗中重写了典型事件处理函数,那么QWidget类的子类的子类就会继承QWidget类的子类的修改后的典型事件处理函数,依次类推。
好了,到现在大家对这个事件机制有没有更深入的了解了?
我相信是有的,因为我讲的很细了。
如果还有不懂的,我们完全可以看看下面这个例子
把 QWidget 想象成一个“标准的公司部门”
想象一下,QWidget
这个基类,就像一个公司里定义好了标准工作流程的“总部行政部”。
-
总部行政部 (QWidget) 的规则:
-
它有一个总接待处 (event() 函数)。任何寄给这个部门的信件、包裹、访客(这些就是“事件”),都会先送到这个总接待处。
-
总接待处有一个标准的分发流程:一看是“发票”,就转给财务科;一看是“会议通知”,就转给会议科;一看是“办公用品”,就转给后勤科。
-
这里的“财务科”、“会议科”、“后勤科”,就是
QWidget
定义好的那些典型事件处理函数,比如mousePressEvent()
(处理鼠标点击)、paintEvent()
(处理绘画请求)。
-
-
子部门 (QWidget的子类) 的诞生:
-
现在,公司要成立一个新的“市场部”。这个市场部继承了总部行政部的所有规则。
-
这意味着,市场部默认也拥有一个总接待处(继承自总部的event() 函数),并且这个接待处的行为模式和总部的一模一样。它也会把“发票”转给财务科,把“会议通知”转给会议科。
-
同时,市场部也继承了财务科、会议科、后勤科这些“处理能力”。也就是说,如果有人点击了市场部的办公室窗户(
mousePressEvent
),或者市场部需要打扫卫生(paintEvent
),它知道该怎么按标准流程处理。
-
例子来啦!
现在,我们看看这个比喻如何对应到你提到的几点:
情况一:默认情况(子类不自己做任何特殊改动)
-
你创建了一个
QPushButton
(一个按钮)。它就相当于我们上面说的“市场部”。 -
当用户点击这个按钮时:
-
点击事件这个“访客”先被送到按钮的总接待处 (继承自QWidget的event())。
-
总接待处一看是“鼠标按下”事件,立刻按照标准流程,把它转交给“鼠标按下处理科 (继承自QWidget的mousePressEvent())”。
-
这个科室按照标准方式处理,让按钮看起来像被按下去了一样。
-
-
结论: 正因为按钮这个“子部门”继承了总部的“接待处”和各个“科室”,所以它能自动、正确地处理点击事件。你什么都没做,它就已经会工作了。
情况二:子部门想搞点特殊化(子类重写 event())
-
现在,“市场部”觉得总部流程太死板。他们决定自定义自己的总接待处 (重写自己的event()函数)。
-
他们规定:“所有来自‘键盘按键’的加急文件,我们总接待处要亲自处理,并且不做记录(直接返回
true
);其他普通文件,还是按老规矩转给对应的科室。” -
这意味着什么?
-
能力: 市场部现在有了分发事件的能力,并且这个能力是它自己定制的。
-
风险: 如果这个新的总接待处很懒,收到“办公用品”包裹后,直接扔了,不转给后勤科,那么市场部就永远领不到新的办公用品了(对应的标准事件,如绘图事件,就不会被处理)。
-
正确做法: 通常是总接待处只处理特别关心的一两件事,其他所有事都还是交给总部原来的流程去办。这样就既有了特殊性,又保证了正常运作。
-
情况三:子部门想改变某个具体工作的做法(子类重写特定事件处理函数)
-
市场部对“如何布置办公室(对应
paintEvent
绘画事件)”有自己独特的审美。他们不想用总部后勤科提供的标准白墙。 -
于是,重写了“后勤科”的工作手册 (重写paintEvent()函数)。
-
现在,当需要布置(绘制)办公室时:
-
事件依然通过总接待处按标准流程分发到“后勤科”。
-
但这次,执行工作的是市场部自己定义的“后勤科”,它把墙刷成了充满活力的蓝色和橙色,而不是标准的白色。
-
-
结论: 子类通过继承并重写这些特定的处理函数,来改变对某个特定事件的反应,而不用操心事件是怎么被分派过来的。
三.重写事件处理函数简单示例
在这个例子中,我们来处理一下鼠标进入和鼠标离开事件
它们对应的事件处理函数是
大家注意这个函数是个虚函数。
大概意思就是:
此事件处理器可以在子类中重新实现,用于接收传递到事件参数中的部件进入事件。
当鼠标光标进入该部件时,会向部件发送此事件。
大家注意这个函数是个虚函数。
此事件处理器可以在子类中重新实现,用于接收传递到事件参数中的部件离开事件。
当鼠标光标离开该部件时,会向部件发送此离开事件。
好,我们现在就来创建一个项目
注意我们项目的基类是QWidget啊
我们去右边修改一下它的属性,把它的边框改一下
接下来我们需要创建一个继承自 QLabel 的子类,并在该子类中重写特定的事件处理函数。
有人可能会问:为什么不直接重写 QLabel 本身的事件处理函数呢?
实际上,这是不可行的,因为我们无法直接访问或修改 QLabel 的源代码。QLabel 作为 Qt 框架中的一个标准组件,其实现已经被编译成库文件,我们无法直接编辑其内部实现。因此,通过继承并重写其事件处理函数,我们可以在不触及原始代码的情况下,灵活地扩展或修改 QLabel 的默认行为。
创建了一个.h文件,一个.cpp文件,我们去看看
我们发现有报错,很显然啊,这是一个老问题,我们需要加上QLabel的头文件
此外,我们还需要作一些修改
现在构造函数也作了简单的调整。这都是必要的,这决定了我们是不是能编译它。
我们现在就在这个Label类里面对事件处理函数进行重写。
这个时候事件处理函数就算是重写完成了,但是这个项目并没有就此结束
我们回到我们的.ui文件里面
我们发现这个控件是QLabel,而不是我们的Label。
我们必须要将这个控件换成我们的Label类!!!
那么怎么进行替换呢?
注意了啊:你只能将一个控件"提升为"它的子类(派生类)。
换句话说:被提升的目标类型必须是原始控件类型的派生类。
其实也很好理解:因为子类会继承父类的所有属性和方法
注意:因为Label类继承了我们的QLabel类,也就是说Label类是QLabel的子类,所以才能进行替换
我们点击添加
我们点击提升
点击完之,我们就发现了
现在我们就来运行程序
我们拖动鼠标穿过这个控件上下移动
怎么样?还是可以的吧!!
此时就是说明了两个事件就被我们捕获了。
四.和鼠标相关的事件处理函数
4.1.鼠标按下
在这个例子中,我们来处理一下鼠标按下和鼠标释放事件
它们对应的事件处理函数是
大家注意这个函数是个虚函数。
大概意思就是:
此事件处理器可以在子类中重新实现,用于接收传递到事件参数中的部件进入事件。
当鼠标在该部件按下时,会向部件发送此事件。
好,我们现在就来创建一个项目
注意我们项目的基类是QWidget啊
我们去右边修改一下它的属性,把它的边框改一下
接下来我们需要创建一个继承自 QLabel 的子类,并在该子类中重写特定的事件处理函数。
创建了一个.h文件,一个.cpp文件,我们去看看
我们发现有报错,很显然啊,这是一个老问题,我们需要加上QLabel的头文件
此外,我们还需要作一些修改
现在构造函数也作了简单的调整。这都是必要的,这决定了我们是不是能编译它。
我们回到我们的.ui文件里面
我们发现这个控件是QLabel,而不是我们的Label。
我们必须要将这个控件换成我们的Label类!!!
那么怎么进行替换呢?
注意了啊:你只能将一个控件"提升为"它的子类(派生类)。
换句话说:被提升的目标类型必须是原始控件类型的派生类。
注意:因为Label类继承了我们的QLabel类,也就是说Label类是QLabel的子类,所以才能进行替换
我们点击添加
我们点击提升
点击完之,我们就发现了
好了,一切准备工作都做完了,我们就在这个Label类里面对事件处理函数进行重写。
现在我们就来运行程序
我们拖动鼠标在这个Label控件里面到处进行鼠标左键点击
我们发现每次点击都会打印出鼠标当时所在位置。
当我们在靠近Label控件左上角点击的话
我们发现会获得一个比较解决(0,0)的坐标值
也就是说原点就是这个控件的左上角。
我们重新修改一下事件处理函数
我们运行一下
我们再次在这个Label控件左上角点击一下
我们发现这个globalX,globalY获取的是以屏幕左上角为原点的坐标。
除此之外,我们试一试鼠标右键点击一下这个Label控件
我们发现无论是鼠标左键,右键按下,滚轮按下,鼠标上的所有侧键……它们都会触发这个事件
这可怎么办?其实我们可以加一些判定条件
事实上呢,Qt给鼠标按钮还设置了挺多内置变量的
我们就可以根据这些变量来修改一下我们的代码
我们运行一下
我们在这个Label控件里面随便按下鼠标左右键
当然,如果我们需要处理更多键的话,大家也是可以去了解的。
4.2.鼠标释放
鼠标释放事件的事件处理函数是mouseReleaseEvent()。
它的原型如下:
我们写一下这个代码啊,注意我这个代码是基于上面那个项目的
我们运行一下
我们把鼠标移动到这Label控件里面,鼠标左键按下(注意按着别放),打印出下面这个
接着我们松开鼠标左键,发现它打印了下面这个
所以我们clicked()信号就相当于是一个鼠标按下事件+鼠标释放事件。
4.3.鼠标双击事件
鼠标双击事件的事件处理函数是mouseDoubleClickEvent().
它的原型如下:
我们接着在上面那个代码的基础之上接着修改代码
我们运行一下
这个时候我们在这个控件这里双击一下
我们发现这个双击还触发了单击啊!!只有第二次点击才被认定为双击
这个其实是一个隐藏的bug啊。
我们来讲讲
当你用鼠标的时候:
-
按一下,再松开,这叫“单击”。
-
非常快地按两下,这叫“双击”。
问题出在哪儿呢?
对于电脑程序来说,当你第一次按下又松开的时候,它其实不知道你到底是想“单击”还是“双击”的第一下。它很着急,可能立刻就把“单击”的效果给你看了。
比如:
-
在桌面上,单击一个文件是选中它(文件变蓝),双击才是打开它。
-
如果你双击一个文件,你会看到:
-
第一次单击:文件被选中了(变蓝了)。
-
第二次单击:文件才被打开。
-
这就是 bug 的来源!
如果程序员没写好代码,一个双击操作就会先触发一次“单击”的逻辑(比如选中文件),然后再触发“双击”的逻辑(打开文件)。这可能会导致:
-
界面闪动:先选中,再打开,看起来很不流畅。
-
逻辑错误:可能在某些情况下,一个操作会无意中启动两个不同的功能,造成混乱。
所以,正确的做法是?
程序员需要让程序“聪明”一点:在检测到第一次点击后,先稍微等一下,看看有没有紧接着的第二次点击。如果有,那就是“双击”;如果没有,那才是真正的“单击”。这样就能把两者区分开,避免误触发了。
4.4.鼠标移动事件
这个鼠标移动事件对应的事件处理函数是mouseMoveEvent()
它的原型是
啥意思呢?
我讲讲看最核心的东西啊:
它的行为完全由一个叫做“鼠标追踪”的开关控制:
-
追踪关闭(默认状态):
-
此时,
mouseMoveEvent
只在一种情况下会被调用:按住一个鼠标按钮(如左键)并移动鼠标。 -
如果你没有按下任何键,只是移动鼠标,程序会忽略这些移动,这个函数不会被调用。
-
-
追踪开启(手动设置):
-
此时,只要鼠标在你的控件上移动,无论是否按下按键,每一次移动都会调用
mouseMoveEvent
函数。
-
如何开启追踪?
在你的控件类(如构造函数中)调用 setMouseTracking(true);
。
之前呢我们都是通过创建一个子类Label来继承这个QLabel,然后重写这个子类的事件处理函数。
刚才重写鼠标事件的操作,都是在自定义的 Label 中完成的,此时鼠标只有在 Label 范围内进行动作的时候,才能捕获到。
也可以把这些操作直接放到 Widget (QWidget 子类) 来完成。
这样的话,鼠标在整个窗口中进行的各种动作都能获取到了
我们可以创建一个以QWidget为基类的项目
可以看到这个Widget类就是这个QWidget类的子类。
那么我们就可以直接在这个Widget类里面重写一下这个事件处理函数,那么我们就能在整个窗口捕获到这个鼠标事件了。
我们运行一下
我们发现无论我们怎么移动,就是没有打印任何东西。
这是为啥呢?
事实上呢:鼠标移动是一种高频事件。即使轻微移动,每秒也可触发数十次事件。如果每次移动都执行复杂逻辑(如实时计算、重绘界面),极易消耗过多资源,导致程序卡顿。
Qt 的默认行为:按需触发
为保证性能,Qt 默认关闭了无按键状态的鼠标移动追踪。
这意味着:
-
当你仅仅移动鼠标时,程序不会收到
mouseMoveEvent
。 -
只有当至少按住一个鼠标按键(如左键拖动)时,
mouseMoveEvent
才会被触发。
如何更改此行为:启用全局追踪
如果你需要实时追踪鼠标位置(例如用于绘图软件的光标预览或游戏中的鼠标注视),必须显式启用全局鼠标追踪。
具体做法:
-
在窗口或部件的构造函数中调用
setMouseTracking(true)
。 -
启用后,无论是否按下鼠标按键,只要鼠标在该部件上移动,都会触发
mouseMoveEvent
。
我们还是运行上面那个程序
我们按着鼠标左键(随便哪一个键)一直移动,发现它就一直会打印
那么我要是想要不按任何键,就要触发这个mouseMoveEvent的话,我们应该在上面代码的基础之上加上下面这句
我们运行一下
这个时候我们不需要按下鼠标上的任何键,就能自动捕获这个鼠标移动事件
4.5.鼠标滚轮事件
鼠标滚轮事件的事件处理函数是wheelEvent()。
它的原型如下:
我们重新创建一个项目(QWidget项目)
我们运行一下
我们在这个窗口上鼠标往前滚
往后滚
我们发现通过正负就能判断我们是往哪边滚动。
当然我们也可以通过一个变量来累加这个移动的距离
我们运行一下
我们一直往后滚
往前滚
还是很不错的吧。
五.和键盘相关的事件处理函数
5.1.键盘按下
其实很简单啊,就是keyPressEvent()
我们创建一个项目(还是以QWidget类作为项目基类)
我们现在运行一下
鼠标先点击一下上面那个窗口
然后我们点击a键,发现打印了下面这个
我们再试一试其他按键
那么这些数字是啥意思呢?
事实上啊,Qt已经将键盘上面每一个按键都定义成一个数字了。
……
……
还有很多我们没列出来,所以,我们完全没有必要像上面那样去打印这些数字,我们直接进行匹对即可
我们运行一下
选中下面这个窗口
我们按下A键
我们按下其他按键,却没有任何反应。
5.2.组合按键
我们怎么应对组合键呢?
Qt里面就有一个Qt::KeyboardModifier是专门用于组合键。
我们只需要记住下面这个表格即可
常量 | 值 | 描述 |
---|---|---|
Qt::NoModifier | 0x00000000 | 没有按下任何修饰键。 |
Qt::ShiftModifier | 0x02000000 | 键盘上的 Shift 键被按下。 |
Qt::ControlModifier | 0x04000000 | 键盘上的 Ctrl 键被按下。 |
Qt::AltModifier | 0x08000000 | 键盘上的 Alt 键被按下。 |
Qt::MetaModifier | 0x10000000 | 键盘上的 Windows 徽标键被按下。 |
我们修改一下上面那个例子的代码
我们运行一下
这些都是没有问题的。
六. 和定时器相关的事件
QTimer 是 Qt 框架中用于实现定时器功能的类。
在 QTimer 的底层,实际上是 QTimerEvent 定时器事件。
说实话,这个QTimerEvent 定时器事件的使用还是不太方便的,我们往下看:
-
定时器的创建与启动
-
使用
QObject::startTimer(int interval)
函数来启动一个定时器。参数interval
是以毫秒为单位的时间间隔。 -
例如,如果你希望每隔1000毫秒(1秒)触发一次定时器,可以这样调用:
startTimer(1000)
。 -
这个函数返回一个整型的定时器ID(timerId)。这个ID在同一个QObject对象中是唯一的,你可以用它来区分多个定时器。
-
-
定时器事件的处理
-
当定时器超时时,Qt会生成一个
QTimerEvent
事件,并传递给启动定时器的对象的timerEvent(QTimerEvent *event)
虚函数。 -
因此,你需要在你自己的类中(该类必须是QObject的派生类)重写
timerEvent
函数,以便处理定时器事件。 -
在
timerEvent
函数内部,你可以通过event->timerId()
来获取触发事件的定时器ID,然后根据不同的ID执行不同的操作。
-
-
定时器的停止与清理
-
使用
QObject::killTimer(int timerId)
来停止一个定时器。你需要传入要停止的定时器的ID。 -
停止后,该定时器将不再触发事件。注意,定时器不会自动停止,除非你显式调用
killTimer
,或者对象被销毁。
-
-
注意事项
-
定时器事件是异步的,它们由Qt的事件循环处理。因此,你的程序必须运行在Qt的事件循环中(即调用了
QCoreApplication::exec()
或类似函数)。 -
定时器间隔并不是绝对精确的,它会受到系统负载和其他事件的影响。如果你需要高精度的定时,可能需要使用其他机制(如QTimer类,它也是基于事件循环,但提供了更高级的接口)。
-
如果定时器间隔设置为0,则定时器会在每次事件循环中没有其他事件时触发,这可以用来实现“空闲处理”。
-
多个定时器之间是独立的,每个定时器都会独立地触发事件。
-
我们创建一个新项目(QWidget作为基类)
接下来我们来
我们运行一下
它会一直倒计时,直到0
就是这么使用的。说实话还是很复杂的,我不太建议大家使用这个,我建议大家使用QTimer即可。
七.关于窗口移动和大小改变的事件处理函数
事实上
窗口移动的事件处理函数是moveEvent()
窗口大小改变的事件处理函数是resizeEvent()
我们创建一个新的项目(基于QWidget的)
我们直接运行程序
我们发现窗口一被创建,就触发了这两个事件。
我们持续移动窗口
我们持续改变窗口大小
还是很不错的吧。
八.再提event()
讲到这里,大家应该对这个重写事件处理函数应该游刃有余了。
我们都说触发了事件是会先去到event()里面进行处理的,然后这个event()再进行分发操作,分发给对应的事件处理函数。
那么其实我们也可以通过重写这个event函数来控制是不是需要进行分发操作!!!
这个就和event()的返回值有密切关系了
返回值的含义
-
返回true:表示事件已经被处理,并且不需要再进一步传播。也就是说,这个事件不会再去调用对应的事件处理函数。
-
返回false:表示事件没有被处理,那么event()函数会继续将事件传递给对应的事件处理函数。
那么我们现在就来看一看这个过程
event()的返回值是true
我们创建一个项目(以QWidget作为基类)
我们运行一下啊
我们在这个窗口上按下鼠标左键
我们发现事件处理函数——mousePressEvent()确实是不会被调用。
event()的返回值是false
我们还是运行一下
我们多次用鼠标左键点击
发现确实是会调用哈!!
事实上呢,我们很少去修改这个event(),因为它很危险,稍不留神就会写出bug。所以我们这里不多说