当前位置: 首页 > news >正文

【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 在事件机制的基础之上,构建了独特的 信号槽 机制。

您可以这样理解它们的关系:

  1. 信号是事件的“语义化”表达:当一个底层事件发生时(如鼠标在按钮上释放),Qt 的组件(如 QPushButton)不仅会处理这个事件,还会根据事件的语义,“发射”一个对应的信号(如 clicked())。信号槽本质上是对事件机制的进一步封装和抽象,它将底层的、原始的用户操作,转换为了高级的、有明确意义的“信号”。

  2. 信号槽简化了交互逻辑:程序员的工作因此变得非常简单:只需要将某个信号(Signal)与一个特定的槽函数(Slot)连接起来。当信号被发射时,所有与之连接的槽函数都会被自动、异步地调用。

  3. 但是只有部分常用的事件被封装成了信号。


简而言之,事件是信号槽的底层支撑,信号槽是事件的上层建筑。事件机制赋予了 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 这个基类,就像一个公司里定义好了标准工作流程的“总部行政部”

    1. 总部行政部 (QWidget) 的规则:

      • 它有一个总接待处 (event() 函数)。任何寄给这个部门的信件、包裹、访客(这些就是“事件”),都会先送到这个总接待处。

      • 总接待处有一个标准的分发流程:一看是“发票”,就转给财务科;一看是“会议通知”,就转给会议科;一看是“办公用品”,就转给后勤科。

      • 这里的“财务科”、“会议科”、“后勤科”,就是 QWidget 定义好的那些典型事件处理函数,比如 mousePressEvent()(处理鼠标点击)、paintEvent()(处理绘画请求)。

    2. 子部门 (QWidget的子类) 的诞生:

      • 现在,公司要成立一个新的“市场部”。这个市场部继承了总部行政部的所有规则。

      • 这意味着,市场部默认也拥有一个总接待处(继承自总部的event() 函数),并且这个接待处的行为模式和总部的一模一样。它也会把“发票”转给财务科,把“会议通知”转给会议科。

      • 同时,市场部也继承了财务科、会议科、后勤科这些“处理能力”。也就是说,如果有人点击了市场部的办公室窗户(mousePressEvent),或者市场部需要打扫卫生(paintEvent),它知道该怎么按标准流程处理

    例子来啦!

    现在,我们看看这个比喻如何对应到你提到的几点:

    情况一:默认情况(子类不自己做任何特殊改动)

    • 你创建了一个 QPushButton(一个按钮)。它就相当于我们上面说的“市场部”。

    • 当用户点击这个按钮时:

      1. 点击事件这个“访客”先被送到按钮的总接待处 (继承自QWidget的event())

      2. 总接待处一看是“鼠标按下”事件,立刻按照标准流程,把它转交给“鼠标按下处理科 (继承自QWidget的mousePressEvent())”

      3. 这个科室按照标准方式处理,让按钮看起来像被按下去了一样。

    • 结论: 正因为按钮这个“子部门”继承了总部的“接待处”和各个“科室”,所以它能自动、正确地处理点击事件。你什么都没做,它就已经会工作了。

    情况二:子部门想搞点特殊化(子类重写 event())

    • 现在,“市场部”觉得总部流程太死板。他们决定自定义自己的总接待处 (重写自己的event()函数)

    • 他们规定:“所有来自‘键盘按键’的加急文件,我们总接待处要亲自处理,并且不做记录(直接返回true);其他普通文件,还是按老规矩转给对应的科室。”

    • 这意味着什么?

      • 能力: 市场部现在有了分发事件的能力,并且这个能力是它自己定制的。

      • 风险: 如果这个新的总接待处很懒,收到“办公用品”包裹后,直接扔了,不转给后勤科,那么市场部就永远领不到新的办公用品了(对应的标准事件,如绘图事件,就不会被处理)。

      • 正确做法: 通常是总接待处只处理特别关心的一两件事,其他所有事都还是交给总部原来的流程去办。这样就既有了特殊性,又保证了正常运作。

    情况三:子部门想改变某个具体工作的做法(子类重写特定事件处理函数)

    • 市场部对“如何布置办公室(对应paintEvent绘画事件)”有自己独特的审美。他们不想用总部后勤科提供的标准白墙。

    • 于是,重写了“后勤科”的工作手册 (重写paintEvent()函数)

    • 现在,当需要布置(绘制)办公室时:

      1. 事件依然通过总接待处按标准流程分发到“后勤科”。

      2. 但这次,执行工作的是市场部自己定义的“后勤科”,它把墙刷成了充满活力的蓝色和橙色,而不是标准的白色。

    • 结论: 子类通过继承并重写这些特定的处理函数,来改变对某个特定事件的反应,而不用操心事件是怎么被分派过来的。

    三.重写事件处理函数简单示例

    在这个例子中,我们来处理一下鼠标进入和鼠标离开事件

    它们对应的事件处理函数是

    大家注意这个函数是个虚函数。

    大概意思就是:

    此事件处理器可以在子类中重新实现,用于接收传递到事件参数中的部件进入事件。

    当鼠标光标进入该部件时,会向部件发送此事件。

    大家注意这个函数是个虚函数。

    此事件处理器可以在子类中重新实现,用于接收传递到事件参数中的部件离开事件。

    当鼠标光标离开该部件时,会向部件发送此离开事件。


    好,我们现在就来创建一个项目

    注意我们项目的基类是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啊。

    我们来讲讲

    当你用鼠标的时候:

    1. 按一下,再松开,这叫“单击”。

    2. 非常快地按两下,这叫“双击”。

    问题出在哪儿呢?

    对于电脑程序来说,当你第一次按下又松开的时候,它其实不知道你到底是想“单击”还是“双击”的第一下。它很着急,可能立刻就把“单击”的效果给你看了。

    比如:

    • 在桌面上,单击一个文件是选中它(文件变蓝),双击才是打开它。

    • 如果你双击一个文件,你会看到:

      • 第一次单击:文件被选中了(变蓝了)。

      • 第二次单击:文件才被打开。

    这就是 bug 的来源!

    如果程序员没写好代码,一个双击操作就会先触发一次“单击”的逻辑(比如选中文件),然后再触发“双击”的逻辑(打开文件)。这可能会导致:

    • 界面闪动:先选中,再打开,看起来很不流畅。

    • 逻辑错误:可能在某些情况下,一个操作会无意中启动两个不同的功能,造成混乱。

    所以,正确的做法是?

    程序员需要让程序“聪明”一点:在检测到第一次点击后,先稍微等一下,看看有没有紧接着的第二次点击。如果有,那就是“双击”;如果没有,那才是真正的“单击”。这样就能把两者区分开,避免误触发了。

    4.4.鼠标移动事件

    这个鼠标移动事件对应的事件处理函数是mouseMoveEvent()

    它的原型是

    啥意思呢?

    我讲讲看最核心的东西啊:

    它的行为完全由一个叫做“鼠标追踪”的开关控制:

    • 追踪关闭(默认状态)

      • 此时,mouseMoveEvent 只在一种情况下会被调用:按住一个鼠标按钮(如左键)并移动鼠标

      • 如果你没有按下任何键,只是移动鼠标,程序会忽略这些移动,这个函数不会被调用

    • 追踪开启(手动设置)

      • 此时,只要鼠标在你的控件上移动,无论是否按下按键,每一次移动都会调用 mouseMoveEvent 函数。

    如何开启追踪?

    在你的控件类(如构造函数中)调用 setMouseTracking(true);


    之前呢我们都是通过创建一个子类Label来继承这个QLabel,然后重写这个子类的事件处理函数。

    刚才重写鼠标事件的操作,都是在自定义的 Label 中完成的,此时鼠标只有在 Label 范围内进行动作的时候,才能捕获到。

    也可以把这些操作直接放到 Widget (QWidget 子类) 来完成。

    这样的话,鼠标在整个窗口中进行的各种动作都能获取到了

    我们可以创建一个以QWidget为基类的项目


    可以看到这个Widget类就是这个QWidget类的子类。

    那么我们就可以直接在这个Widget类里面重写一下这个事件处理函数,那么我们就能在整个窗口捕获到这个鼠标事件了。

    我们运行一下

    我们发现无论我们怎么移动,就是没有打印任何东西。

    这是为啥呢?

    事实上呢:鼠标移动是一种高频事件。即使轻微移动,每秒也可触发数十次事件。如果每次移动都执行复杂逻辑(如实时计算、重绘界面),极易消耗过多资源,导致程序卡顿。

    Qt 的默认行为:按需触发

    为保证性能,Qt 默认关闭了无按键状态的鼠标移动追踪。

    这意味着:

    • 当你仅仅移动鼠标时,程序不会收到 mouseMoveEvent

    • 只有当至少按住一个鼠标按键(如左键拖动)时,mouseMoveEvent 才会被触发。

    如何更改此行为:启用全局追踪

    如果你需要实时追踪鼠标位置(例如用于绘图软件的光标预览或游戏中的鼠标注视),必须显式启用全局鼠标追踪。

    具体做法:

    1. 在窗口或部件的构造函数中调用 setMouseTracking(true)

    2. 启用后,无论是否按下鼠标按键,只要鼠标在该部件上移动,都会触发 mouseMoveEvent


    我们还是运行上面那个程序

    我们按着鼠标左键(随便哪一个键)一直移动,发现它就一直会打印


    那么我要是想要不按任何键,就要触发这个mouseMoveEvent的话,我们应该在上面代码的基础之上加上下面这句

    我们运行一下

    这个时候我们不需要按下鼠标上的任何键,就能自动捕获这个鼠标移动事件

    4.5.鼠标滚轮事件

    鼠标滚轮事件的事件处理函数是wheelEvent()。

    它的原型如下:


    我们重新创建一个项目(QWidget项目)

    我们运行一下

    我们在这个窗口上鼠标往前滚

    往后滚

    我们发现通过正负就能判断我们是往哪边滚动。

    当然我们也可以通过一个变量来累加这个移动的距离

    我们运行一下

    我们一直往后滚

    往前滚

    还是很不错的吧。

    五.和键盘相关的事件处理函数

    5.1.键盘按下

    其实很简单啊,就是keyPressEvent()

    我们创建一个项目(还是以QWidget类作为项目基类)

    我们现在运行一下

    鼠标先点击一下上面那个窗口

    然后我们点击a键,发现打印了下面这个

    我们再试一试其他按键

    那么这些数字是啥意思呢?

    事实上啊,Qt已经将键盘上面每一个按键都定义成一个数字了。

    ……

    ……

    还有很多我们没列出来,所以,我们完全没有必要像上面那样去打印这些数字,我们直接进行匹对即可

    我们运行一下

    选中下面这个窗口

    我们按下A键

    我们按下其他按键,却没有任何反应。

    5.2.组合按键

    我们怎么应对组合键呢?

    Qt里面就有一个Qt::KeyboardModifier是专门用于组合键。

    我们只需要记住下面这个表格即可

    常量描述
    Qt::NoModifier0x00000000没有按下任何修饰键。
    Qt::ShiftModifier0x02000000键盘上的 Shift 键被按下。
    Qt::ControlModifier0x04000000键盘上的 Ctrl 键被按下。
    Qt::AltModifier0x08000000键盘上的 Alt 键被按下。
    Qt::MetaModifier0x10000000键盘上的 Windows 徽标键被按下。

    我们修改一下上面那个例子的代码

    我们运行一下

    这些都是没有问题的。

    六. 和定时器相关的事件

    QTimer 是 Qt 框架中用于实现定时器功能的类。

    在 QTimer 的底层,实际上是 QTimerEvent 定时器事件。

    说实话,这个QTimerEvent 定时器事件的使用还是不太方便的,我们往下看:

    1. 定时器的创建与启动

      • 使用QObject::startTimer(int interval)函数来启动一个定时器。参数interval是以毫秒为单位的时间间隔。

      • 例如,如果你希望每隔1000毫秒(1秒)触发一次定时器,可以这样调用:startTimer(1000)

      • 这个函数返回一个整型的定时器ID(timerId)。这个ID在同一个QObject对象中是唯一的,你可以用它来区分多个定时器。

    2. 定时器事件的处理

      • 当定时器超时时,Qt会生成一个QTimerEvent事件,并传递给启动定时器的对象的timerEvent(QTimerEvent *event)虚函数。

      • 因此,你需要在你自己的类中(该类必须是QObject的派生类)重写timerEvent函数,以便处理定时器事件。

      • timerEvent函数内部,你可以通过event->timerId()来获取触发事件的定时器ID,然后根据不同的ID执行不同的操作。

    3. 定时器的停止与清理

      • 使用QObject::killTimer(int timerId)来停止一个定时器。你需要传入要停止的定时器的ID。

      • 停止后,该定时器将不再触发事件。注意,定时器不会自动停止,除非你显式调用killTimer,或者对象被销毁。

    4. 注意事项

      • 定时器事件是异步的,它们由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。所以我们这里不多说

    http://www.dtcms.com/a/442504.html

    相关文章:

  • 数字人民币钱包抉择:匿名自由与实名安全的法律风险评估
  • 做网站需要用到什么北京养老网站开发
  • 专门做电商的网站有哪些广告推广策略
  • Java EE初阶启程记06---synchronized关键字
  • QT(c++)开发自学笔记:1.串口
  • 最小外接矩形2显示四条边缘
  • 什么是网站建设与维护潘嘉严个人网站
  • 天津做网站哪家好如何申请域名后缀
  • MCP(trae)+ wireshark-提高干活效率
  • 机器视觉3D检测中,.ply(Polygon File Format) 3D点云格式
  • 如何网站优化排名做兼职比较正规的网站
  • 兰亭妙微高端网站设计案例:品牌官网如何做到美学与转化并重
  • 网站主机提供商:选择与您业务相匹配的托管服务
  • Linux学习笔记--insmod 命令
  • 做树状图的网站html全屏网站
  • 网站页面设计师如何编辑网站模板
  • 网站服务器网络企业网站设计方案
  • 数仓一些问题
  • 君正T32开发笔记之AOV实例介绍
  • 包含HPA配置与资源限制示例
  • 做影视网站用主机还是用服务器合肥网络优化公司有几家
  • 网站开发属于哪个部门贵阳网站建设电话
  • fiddler详解
  • 系统版本管理规范:从分支策略到发布流程
  • 【LLM-RL】GRPO->DAPO->GSPO训练区别
  • 建设中心小学网站上海16个区排名
  • Linux下查看和关闭进程的方法
  • 【ROS2学习笔记】动作
  • 李宏毅machine learning 2021学习笔记——Vit
  • 第三十一篇|AI 驱动的教育数据建模:以冈山外语学院为样本的结构化分析