Qt 事件

事件概念

在信号槽机制中,我们了解到,用户的操作有可能会触发一个信号,通过绑定信号和槽函数,从而能够在信号触发时,来运行槽函数。

而事件概念和信号槽机制也十分类似,用户的各类操作也会触发事件,程序员也能够给事件关联上函数,当事件触发时就能够运行相应的代码

实际上事件机制就是信号槽的底层机制,Qt 将信号槽进行了封装,从而获取了信号槽机制。

针对计算机的不同事件,Qt 也分化了不同的事件,比如鼠标事件,键盘事件等。

既然信号槽是事件的封装,那么它的使用方法肯定和信号槽不一样,接着我们就来学习事件的使用方式吧。

事件分类

 一般用户有很多事件,我们了解其中比较常见的事件。

事件 描述
鼠标事件 鼠标左右键、滚轮、移动、鼠标的松开和按下
键盘事件 按键类型、按键按下和松开
定时器事件 时间达到
进入离开事件 鼠标进入和离开
滚轮事件 鼠标滚轮滚动
绘屏事件 重绘屏幕某些部分
显示隐藏事件 窗口的显示和隐藏
移动事件 窗口位置变化
窗口事件 是否为当前的窗口
大小改变事件 窗口大小的改变
焦点事件 键盘焦点移动
拖拽事件 用鼠标进行拖拽

事件的处理

事件的处理方式只有一种——重写相关的Event函数。

Qt 中的 Event 函数基本都是虚函数,所以可以重新实现,这里我们以鼠标的进入离开事件为例。


这里我们在 Qt Designer 中拖拽一个 QLabel 控件,重写 QLabel 控件的鼠标进入离开事件。

为了更好的进行实验,这里给 QLabel 设置了边框。

 然后我们创建一个新类。

选中 C++ 和 C++ class 之后。

设置类的名称,以及它的父类(Base Class) 

然后就得到了一个新类。 

不过这里缺少了一些头文件,我们根据需要自行添加。

由于每个控件都需要挂在对象树上,因此构造函数必须有 QWidget * parent 。

并且在新类中新增我们需要重写的虚函数——enterEvent 和 leaveEvent。

这里是一种多态的表现,因此函数名称必须和父类的函数名相同。

然后就是具体的实现。

构造函数将 parent 设置给 QLabel,即父类。

设置好函数后,我们需要将对应的 QLabel 进行提升操作,因为我们是在 Label 中重写的 QLabel,而 QLabel 可没重写,这里将 QLabel 提升为 Label,这样事件函数才能成功起作用。 

然后再按顺序进行操作,不过需要记住的是,这里 “提升的类名称” 选项中,类的名称不可出错。 

然后就会将 Qt Designer 中的 QLabel 提升为我们自己创建的 Label 类了。

 可以看到我们重写的事件函数确实起作用了。

鼠标事件

QMouseEvent 指的是鼠标的各种事件,比如鼠标单击、双击、释放、滚轮滑动等事件。

同时它自身还有各种属性。

比如鼠标点击的位置,针对窗口和屏幕有两种坐标。

接着我们来重写鼠标触发的各种事件。

 这里的鼠标事件是针对 Qt Creator 所创建的窗口,因此不用另外创建新类,因为 Qt Creator 已经创建好了,可以直接重写虚函数。

 鼠标点击事件

void vmousePressEvent(QMouseEvent* event);

在 Widget 类中重写了该函数,如果在窗口中点击了一下鼠标,就会触发该函数。

 

能够发现 x() 和 y() 这两个函数的值和  globalX() 和 globalY() 这两个函数的值不同。

实际上 x () 和 y() 这两个函数的值指的是鼠标点击位置相对于窗口左上角的位置,也就是说原点是窗口左上角。

而 globalX() 和 globalY() 两个函数的原点则是屏幕的左上角。

 而鼠标上一般有左右键和滚轮键,就像我们打 FPS 游戏时,左右键和滚轮键有不同的功能一样,QT 也能区分不同的鼠标按键。

不过需要注意,这里都是需要按下按键才能触发,滚轮滚动是没有效果的。 

如今鼠标可能不止有一个按键,会有各种按键,Qt 也为它们制定了相应的宏定义。

能够看到除了正常的左右键、滚轮按键外,还有许多其他按键。

LeftButton : 鼠标左键

RightButton: 鼠标右键

MidButton : 鼠标中键

 笔者使用的罗技鼠标,它的侧键我们也能够通过 Qt 来进行响应。

能够看到,侧键确实得到了响应。 

鼠标释放事件

在浏览网页的时候,一般如果点击某个页面,然后不松开鼠标的时候,页面一般是不会响应的,因为它们都是采用在鼠标释放的时候才会响应,也就是鼠标释放事件。

void mouseRealeseEvent(QMouseEvent* event)

重写该函数时,如果鼠标释放了就会触发该函数。

 

 能够看到,按下的时候,鼠标点击事件还是触发了的,松开时才触发鼠标释放事件。

鼠标双击事件

void mouseDoubleClickEvent(QMouseEvent* event) 

一般电脑的应用程序都需要双击才能打开(除非放在开始界面),因此鼠标双击也算一种事件。

 而从结果来看,双击的第一次点击也会触发鼠标点击事件,而只有第二次点击才会触发双击事件

一般双击事件要求第一次点击和第二次点击之间的间隔很短,才能触发。

鼠标移动事件

viod mouseMoveEvent(QMouseEvent* event) 

一般来说,该虚函数即使重写了也没有效果,因为追踪或者计算鼠标移动是一个计算量很大的工程,鼠标可能一瞬间移动很多个像素,如果计算量十分大,为了程序流畅运行,一般 Qt 默认只有按下鼠标才能追踪鼠标,不过在 QWidget 中,我们能供通过函数来设置追踪。

void setMouseTracking(bool flag) 

首先我们设置允许追踪鼠标。 

然后再重写事件函数。 

 ​​​​

这样就能够追踪了。

需要记住,QMainWindow 即使通过 setMouseTracking 后,也只能通过按住鼠标后才能追踪,QWidget 则在设置后,即便没有按住鼠标也能追踪。这是因为 QMainWindow 会自动添加一个 CentralWidget,从而导致 QMainWindow 无法检测鼠标移动事件。

鼠标滚轮事件

void wheelEvent(QWheelEvent* event)

 鼠标滚轮事件通过 QWheelEvent 来实现,而滚轮滑动的距离可以通过 delta 函数获取。

向前滚动则是整数,向后滚动则是负数。 

 需要注意,该事件是滚轮事件,因此采用的传参也是 QWheelEvent 而非 QMouseEvent。

 键盘事件

一般各种工具都有快捷键,比如 ctrl + c 是复制, 因此键盘也需要有事件存在。

Qt 的各种控件都提供了 shortCut 作为快捷键设置,我们也能够采取重写虚函数的方式来设置快捷键。

单个按键

我们给一个 PushButton 按键设置快捷键,同样的先新建一个类,并且继承QPushButton。

然后给按键设置对应的动作。 

然后将 ui 文件的控件给提升之后,就能够成功的执行该事件内部的操作。 

多个按键

如果想实现 ctrl + c 这种组合按键,就需要了解 Qt 的 KeyboardModifier 。

名称 描述
Qt::NoModifier 无修改键
Qt::ShiftModifier shift 键
Qt::ControlModifier

Ctrl 键

Qt::AltModifier

Alt 键

Qt::MetaModifier

Meta 键(Window上的Windows键,MacOS 上的 Command 键)

Qt::KeypadModifier 使用键盘上的数字进行输出时,Num Lock 处于打开状态
Qt::GroupSwitchModifer 输入法 之间切换

而 QKeyEvent 类中有一个 modifiers 函数,用来检测是否有这些修改键。

这样就能实现 CTRL + A 的复合按键。

可以看到确实实现了。 

 定时器事件

Qt 中的定时器分为 QTimerEvent 和 QTimer 两个类。

  • QTimerEvent 类:用以描述定时器事件,通过 startTimer 函数启动定时器,该函数需要一个以 ms 为单位的整数作为设定的时间间隔,返回一个 timerId,用以标识一个定时器。
  • QTimer 类:一个类,用来实现定时器,可以使用信号槽机制。

QTimerEvent 

QObject 类中就有 QTimerEvent 存在,因此可以直接使用。

void timerEvent(QTimerEvent* event) 

而QObject 还提供了 startTimer 函数 和 killTimer 函数 用来启动和结束定时器

 因此我们实际上可以使用 QObject 提供的事件函数来实现定时器功能。


首先我们在 Widget 的头文件中新增一个成员,用来记录定时器 Id。

然后在 Widget 的构造函数中启动定时器,并且记录定时器 ID。

startTimer 所需的参数单位是 ms,并且返回定时器的Id,类似文件描述符 

 然后重写 timerEvent 事件函数。

可以看到确实实现了定时器的功能。 

 可以看到,通过 timerEvent 设置的定时器比 QTimer 复杂点,需要判断定时器的ID,而 QTimer 类则不需要,通过信号槽机制,可以不用判断定时器的 ID 就能直接使用。

QTimer类

 首先先设置一个 QTimer 类的指针。

然后在构造函数中 new 一个 QTimer 对象,并且启动该定时器。

此外通过 connect 将槽函数和信号连接在一起。 

 然后设置好槽函数。

 能够看到确实实现了定时器功能。

窗口大小改变事件和移动事件

窗口的改变和移动也是一种事件,我们可以重写该函数来来捕获该事件。

 

可以看到移动窗口出现了很多 point 位置,而改变大小则出现了很多 size 的数据。 

 

事件分发器

在 QT 中,事件分发器是一个比较核心的概念,用于处理事件,它的作用是将事件从一个对象传递到另一个对象,直到事件被处理。每个继承了 QObject 类和 QObject 类本身的类,都可以重写 event 函数,以此实现事件分发器。

 bool event(QEvent* event)

返回值为 true 则表示拦截事件,不继续分发

工作原理

每当程序发生一个事件,都会传入到 QObject 类中的 event 函数之中,而 event 函数本身并不处理事件而是通过事件类型(type) 来调用不同的事件处理函数。 

因此我们如果需要 event 函数处理事件,可以自己重写 event 函数。

为了体现 event 分发器的作用,我们写两个事件函数,来对比体会 event 函数的作用。

接着我们实现 mousePressEvent 和 event 函数。

从结果可以看到,每次单击鼠标所产生的事件都是在 event 函数中处理,而非 mousePressEvent 

从这里也可以看出,产生事件时,都是优先分发到 event 函数中。

事件过滤器

通过上面的学习,我们了解到事件都是优先分发给 event 函数,但是重写一个 event 函数有很多需要注意的地方,比如分发事件需要处理好逻辑关系,区分各种不同的事件,因为 event 函数需要接收所有事件,这是很麻烦的,而 QT 提供了事件过滤器机制,用于在事件分发到 event 函数之前做一次过滤,或者拦截。

事件过滤器的使用

过滤器的使用有两步:

  1. 重写eventfilter() 函数; 
  2. 安装事件过滤器

首先我们创建好项目后,在 Designer 界面拖拽一个 Label 控件。

然后新建一个类,并且按事件函数的使用那样,写好事件函数,并提升 Designer 中的 Label 为自己新建的类。 

 

按之前学习的那样,提升完控件,并且写好事件函数后,我们在 Widget.h 重写 eventFilter 函数。

bool eventFilter(QObject* obj, QEvent* e)

返回值为true表明拦截,不用继续分发。 

  然后实现 eventFilter 函数。

最后在 widget.cpp 中安装事件过滤器 。

从结果可以看到本应该分发到 event 函数的事件都在 eventFilter 被拦截了。 

事件过滤器相比于事件分发器,由于事件过滤器在安装了过滤器的控件上,先于事件分发器来得到事件,因此如果想要监听某个控件的所有事件可以优先考虑事件过滤器。

然而实际上无论是事件过滤器还是事件分发器,它们能做到的事都能通过重写各种事件函数来完成,因此这里只做了解即可。

总结

本文总结了事件的概念,也写了多种事件的函数,用以了解事件的作用,并且还了解了事件分发器和过滤器的使用以及注意事项,相信对大家有所帮助。

相关推荐

  1. <span style='color:red;'>Qt</span> <span style='color:red;'>事件</span>

    Qt 事件

    2024-06-18 09:02:05      18 阅读
  2. <span style='color:red;'>Qt</span> <span style='color:red;'>事件</span>

    Qt 事件

    2024-06-18 09:02:05      12 阅读
  3. Qt事件

    2024-06-18 09:02:05       9 阅读
  4. QT——事件

    2024-06-18 09:02:05       8 阅读
  5. <span style='color:red;'>Qt</span> <span style='color:red;'>事件</span>

    Qt 事件

    2024-06-18 09:02:05      8 阅读
  6. Qt event事件发送

    2024-06-18 09:02:05       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-18 09:02:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-18 09:02:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-18 09:02:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-18 09:02:05       18 阅读

热门阅读

  1. 【Qt6.3 基础教程 04】探索Qt项目结构和配置文件

    2024-06-18 09:02:05       5 阅读
  2. 服务器雪崩的应对策略之----隔离

    2024-06-18 09:02:05       7 阅读
  3. 洛谷 AT_abc358_c [ABC358C] Popcorn 题解

    2024-06-18 09:02:05       11 阅读
  4. LINUX 精通 3.2

    2024-06-18 09:02:05       7 阅读
  5. 测试用例设计:提升测试覆盖率的策略与方法

    2024-06-18 09:02:05       9 阅读
  6. HTML页面定时刷新指南

    2024-06-18 09:02:05       6 阅读
  7. Docker的常见问题

    2024-06-18 09:02:05       7 阅读
  8. 1985H1 Maximize the Largest Component (Easy Version)

    2024-06-18 09:02:05       7 阅读
  9. sping怎么解决循环依赖

    2024-06-18 09:02:05       6 阅读
  10. Redis命令

    2024-06-18 09:02:05       7 阅读
  11. spi service实现类加载代码

    2024-06-18 09:02:05       7 阅读