16.PyQt5中的事件系统之事件(QEvent)的传递(分发)和处理

news2024/12/24 5:56:17

PyQt5中的事件系统之事件(QEvent)的传递(分发)和处理

使用Qt编程,几乎不用考虑事件,因为当产生某种事件时,Qt窗口部件都会发射一个相应的信号(即Qt会把事件转换为一个对应的信号),比如按钮被按下时,会产生一个MouseButtonPress 事件, Qt 会处理这一事件,并且会发射一个 clicked()单击信号,程序员可以直接处理 clicked()信号,而不必处理底层的事件。

对于事件,我们不需要知道Qt是怎么把事件转换为QEvent或其子类类型的对象的,我们只需要知道怎么传递和处理这些事件即可。比如对于按下鼠标事件,不需要知道Qt是怎样把该事件转换为QMouseEvent类型的对象的,只需要知道怎么传递和处理该事件即可。

20221121155204

1. QApplication、 QGuiApplication、 QCoreApplication 简介

  • 继承关系见下图,其中左侧为顶级父类
    20221121155935

  • 一个程 序中只能有一个 QCoreApplication 及其子类的对象

  • QCoreApplication:主要提供无 GUI 程序的事件循环

  • QGuiApplication:用于管理 GUI 程序的控制流和主要设置

  • QApplication:该类专门为 QGuiApplication 提供基于 QWidget 的程序所需的一些功能,主要用于处理部件的初始化、最终化。主要职责如下:

    • 使用用户的桌面设置初始化应用程序。
    • 执行事件处理,也就是说该类能从底层系统接收并分发事件。比如,使用
      QCoreApplication::sendEvent()或 QCoreApplication::postEvent()函数分发自定义事件。
    • 解析常用命令行参数并设置其内部状态。
    • 定义了应用程序的界面外观,可使用 QApplication::setStyle()进行更改。
    • 指定应程程序如何分配颜色。
    • 使用 QCoreApplication::translate()函数对字符串进行转换。
    • 通过 QApplication::desktop()函数处理桌面,通过 QCoreApplication::clipboard()函数处理剪贴板。
    • 管理应用程序的鼠标光标。比如使用 QGuiApplication::setOverrideCuresor()函数设置
      光标等。

2. 事件的传递

QApplication、 QGuiApplication、 QCoreApplication 简介

在调用QApplication类的exec()函数时,它会使Qt应用程序进入事件循环,这样就可以使应用程序在运行时接收到发生的各种事件。一旦有事件发生,Qt便会构建一个相应的QEvent子类的对象来表示它,然后将它传递给相应的QObject对象或其子对象。

事件传递的步骤:

  1. 基本规则:若事件未被目标对象处理,则把事件传递给其父对象处理,若父对象仍未处理,则再传递给父对象的父对象处理,重复这个过程,直至这个事件被处理或到达顶级对象为止。 注意:事件是在对象间传递的,这里是指对象的父子关系,而不是指类的父子关系。

  2. 在 Qt 中有一个事件循环,该循环负责从可能产生事件的地方捕获各种事件,并把这些事件转换为带有事件信息的对象,然后由 Qt 的事件处理流程分发给需要处理事件的对象来处理事件。

  3. 通过调用 QCoreApplication::exec()函数启动事件主循环,主循环从事件队列中获取事件,然后创建一个合适的 QEvent 对象或其子类类型的对象来表示该事件, 在此步骤中,事件循环首先处理所有发布的事件,直到队列为空,然后处理自发的事件,最后处理在自发事件期间产生的已发布事件。注意:发送的事件不由事件循环处理,该类事件会被直接传递给对象

  4. 然后, Qt 会调用 QCoreApplication::notify()函数对事件进行传递(或分发)

  5. 最后, QObject 对象调用 QObject::event()函数接收事件

    • event()函数是一个虚函数, 是 QObject 对象处理事件的入口

    • 在 QObject 的子类中通常会重写 event()函数,比如 QWidget 类就重写了 event()函数。

    • event()函数与事件处理函数的关系: event()函数负责把事件传递给目标对象并调用对应的事件处理函数处理事件,比如调用 QWidget::keyPressEvent()函数处理键盘按下事件等,注意, QObject 中的 event()函数并不能实现该功能,这是通过QObject 的子类中重写的 event()函数实现的,比如调用 QWidget::keyPressEvent()函数,就是由在 QWidget 类中重写的 event()函数来完成的。

    • event()函数对大多数事件都调用了默认的处理函数,但并不能包括全部的事件,因此,有时我们需要重写该函数,这也是我们处理 Qt 事件的方式之一。

    • event()函数不会处理事件,他只是根据事件的类型进行事件的传递(或分发),若返回 true 则表示这个事件被接受并进行了处理,否则事件未被处理,需进行进一步传递或丢弃。
      20221121175558

3. 事件的处理

一个事件由一个特定的QEvent子类来表示,但是有时候一个事件又包含多个事件类型,比如鼠标事件又可以分为鼠标按下、双击和移动等多种操作。这些事件类型都是由QEvent类的枚举型QEvent::Type来表示,其中包含了一百多种事件类型,可以在QEvent类的帮助文档中进行查看。虽然QEvent的子类可以表示一个事件,但是却不能用来处理事件,那么应该怎样来处理一个事件呢?QCoreApplication 类的notify()方法的帮助文档给出了5种处理事件的方法:

  • 方法一:重新实现部件的paintEvent()、mousePressEvent()等事件处理函数。这是最常用的一种方法,不过只能用来处理特定部件的特定事件。例如实现拖放操作就是用的这种方法。

  • 方法二:子类化QApplication, 并重新实现QCoreApplication::notify()方法。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。重写该函数会很复杂,也因此此方法很少被使用。这是唯一能在事件过滤器之前捕获到事件的方式

  • 方法三:向QApplication对象上安装(注册)事件过滤器。因为一个程序只有一个QApplication对象,所以这样实现的功能与使用nofity()方法是相同的,优点是可以同时处理多个事件。一旦在QApplication对象上注册了事件过滤器,则程序中每个对象的每个事件都会在发送到其他事件过滤器之前,发送到这个eventFilter()方法。该方式对于调试非常有用。

  • 方法四:重新实现event()方法。QObject类的event方法可以在事件到达默认的事件处理函数之前获得该事件。该方式常被用来处理Tab键的默认意义。在重新实现event()方法时,必须对未明确处理的事件调用基类的event()方法,比如,若子类化QWidget,并重写了event()方法,但未调用父类的event(),则程序可能会不能正常显示界面。

  • 方法五:在QObject对象上安装(注册)事件过滤器。对象一旦使用installEventFilter()注册,传递给目标对象的所有事件都会先传递给这个监视对象的eventFilter()方法。如果同一对象上安装了多个事件处理器,则按照安装的逆序依次激活这些事件处理器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。

在实际编程中,最常用的是方法一,其次是方法五。因为方法二需要继承自QApplication类,而方法三需要使用一个全局的事件过滤器,这将减缓事件的传递,所以虽然这两种方法功能很强大,但是却很少被用到。

4. 代码示例

事件处理方式代码示例:

from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import QEvent, qDebug
from PyQt5 import QtGui

import sys

class MyWidget(QWidget):
  def __init__(self):
    super().__init__()

  def event(self, e: QEvent) -> bool:
    '''
    重写QObject::event
    '''
    if e.type() == QEvent.Type.KeyPress:
      qDebug(f'key down')
    return super().event(e)

  def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
    '''
    重写QWidget类的事件处理函数mousePressEvent
    '''
    qDebug(f'mouse down')
    return super().mousePressEvent(e)

if __name__ == '__main__':
  app = QApplication(sys.argv)

  my_widget = MyWidget()
  my_widget.show()

  sys.exit(app.exec_())

运行效果如下:

20221121200001

重写notify函数代码示例:

from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import qDebug, QEvent, QObject
import sys
import typing

class MyWidget(QWidget):
  def __init__(self):
    super().__init__()

  def event(self, e: QEvent) -> bool:
    qDebug(f'my event...') # 验证该函数是否被执行
    return super().event(e)


class MyApplication(QApplication):
  def __init__(self, argv: typing.List[str]) -> None:
    super().__init__(argv)

  def notify(self, o: QObject, e: QEvent) -> bool:
    qDebug(f'my notify...')
    # 若对象为my_widget且为键盘按下事件
    if o.objectName() == 'my_widget' and e.type() == QEvent.Type.KeyPress:
      qDebug(f'keydown')
    # 一定要调用父类的notify方法,否则本例中event不会被执行
    return super().notify(o, e)

if __name__ == '__main__':
  app = MyApplication(sys.argv)
  my_widget = MyWidget()
  my_widget.setObjectName('my_widget')
  my_widget.show()

  sys.exit(app.exec_())

运行效果如下:

20221121202104

本例中可以看到notify和event方法的执行顺序。

事件传递示例代码:

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtCore import qDebug, QEvent
import sys

class MyWidget(QWidget):
  def __init__(self):
    super().__init__()

  def event(self, e: QEvent) -> bool:
    if e.type() == QEvent.Type.KeyPress:
      qDebug(f'{self.objectName()} : key down ')
    if e.type() == QEvent.Type.MouseButtonPress:
      qDebug(f'{self.objectName()} : mouse down')
    if e.type() == QEvent.Type.MouseButtonRelease:
      qDebug(f'{self.objectName()} : mouse release')
    return super().event(e)

class MyButton(QPushButton):
  def event(self, e: QEvent) -> bool:
    if e.type() == QEvent.Type.KeyPress:
      # 如果是键盘按下事件,将事件传递给父对象处理
      qDebug(f'{self.objectName()} : key down')
      return False

    if e.type() == QEvent.Type.MouseButtonPress:
      # 如果是鼠标按下事件,则处理完毕,事件不传递
      qDebug(f'{self.objectName()} : mouse down')
      return True
    return super().event(e)

if __name__ == '__main__':
  app = QApplication(sys.argv)
  my_widget = MyWidget()
  my_btn = MyButton('test', my_widget)

  # 设置对象名
  my_widget.setObjectName('my_widget')
  my_btn.setObjectName('my_btn')
  my_widget.show()

  sys.exit(app.exec_())

运行效果如下:

20221121204259

小手一抖,点个赞再走哦~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/25768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Servlet是什么?怎么使用?

前言: 服务器里面资源分为动态资源和静态资源 动态资源:Servlet、Jsp 静态资源:HTML、CSS、JS 一、概念 1.什么是servlet? 本质上是一个接口,提供了规范。是java提供的一门动态的web资源开发技术。 2.servlet体…

【C++】string类超详细解析

参考文献:C标准库官网 前言:在C/C的学习过程当中一定一定要多刷题,牛客网作为国内内容超级丰富的IT题库,尤其是它的C、C,有从入门到大厂真题,而且大部分的考试题目也是从中抽取,还有很多面经&am…

智慧职教解决方案-最新全套文件

智慧职教解决方案-最新全套文件一、建设背景二、建设思路三、建设方案四、获取 - 智慧职教全套最新解决方案合集一、建设背景 职业教育目前存在的问题: 发展理念相对落后国际化程度不高基本制度不健全层次结构不合理基础能力相对薄弱社会吸引力不强行业企业参与不…

20221121将行车记录仪记录的MJPEG格式的AVI片段合并的MKV转换为MP4

20221121将行车记录仪记录的MJPEG格式的AVI片段合并的MKV转换为MP4 2022/11/21 21:51 (一) 缘起,用行车记录仪录制的爬拉胡线(惠州大南山)的AVI视频,一个片段5分钟。 使用mkvtoolnix-gui将AVI合并成为MKV视…

垂直定位系统实验平台

系统概述 本系统由控制系统和被控对象两部分组成,可根据课程需要进行双轴机构的轴数增减和循序渐进的运动控制实训。 控制系统部分由水平轴执行机构、人机界面机构、电源机构、驱动元件与控制器等组成。控制方式:人机界面模拟控制。 控制对象部分由水…

如何制作传统节日网站(纯HTML代码)

🎉精彩专栏推荐 💭文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (10…

clean-label backdoor attacks 论文笔记

#论文笔记# 1. 论文信息 论文名称Clean-Label Backdoor Attacks作者Alexander Turner(MIT)会议/出版社ICLR 2019pdf本地pdf 在线pdf代码trojanzoo-clean-label**** Label-Consistent其他这篇文章和 Label-Consistent Backdoor Attacks 基本相同 简介:这篇文章是最…

Java内部类解析

作者:~小明学编程 文章专栏:JavaSE基础 格言:目之所及皆为回忆,心之所想皆为过往 目录 什么是内部类 静态内部类 静态内部类访问外部类的规则 外部类访问静态内部类的规则 实例化静态内部类 非静态内部类 内部类访问外部类…

【网安神器篇】——hydra爆破工具

作者名:Demo不是emo 主页面链接:主页传送门创作初心:舞台再大,你不上台,永远是观众,没人会关心你努不努力,摔的痛不痛,他们只会看你最后站在什么位置,然后羡慕或鄙夷座右…

2.16 这些都是我常用的修图工具【玩赚小红书】

一、美图秀秀 P图、拼图、贴纸文字、抠图、视频剪辑等该有的功能都有,有很多现成的高颜值模板可以直接套用。 软件官网:https://pc.meitu.com/ 支持:windows,linux,苹果,安卓手机,苹果手机 ​…

是js高级啊~

JavaScript高级 1.函数 1.1. this绑定规则优先级 隐式绑定的this 大于 默认的this显式绑定的this 大于 隐式绑定的thisnew绑定this 大于 隐式绑定的thisnew绑定的this 大于 bind绑定的优先级bind绑定的this 大于 apply、call绑定的this 备注: new关键字不可以和…

使用idea工具实现新建java项目并连接mysql数据库

1.新建java项目 如果刚打开idea工具,则选择Create new Project。 在里新建使用File > New > Project 新建package包和class文件 右键src > new > package后输入包名 在刚建的包下右键选择 new > Java class后并输入类名 链接mysql Java 连…

win10系统下使用opencv-dnn部署yolov5模型

文章目录前言一、环境1、硬件2、软件二、YOLO模型三、新建Qt项目1、pro文件2、main函数四、YOLO 类封装1、yolo.h2、yolo.cpp3、classes.txt五、效果前言 最近工作中需要用yolov5训练模型,然后在win10系统下,完成推理部署。本篇主要介绍使用opencv-dnn模…

7判断环的入口结点8输出倒数第k个

7.判断环的入口结点 第一次第二次看感觉都无从下手,还是看了题解,主要思路是: 假设前面无环的结点有a个,环有b个结点,则设置快慢结点之后,fast结点一次走两步,slow结点一次走一步。则相遇时fast…

《第一行代码》核心知识点:活动(Activity)的儿子叫碎片(Fragment)

《第一行代码》核心知识点:活动Activity的儿子叫碎片Fragment前言四、活动(Activity)的儿子叫碎片(Fragment)4.1 碎片是神马?4.2 碎片的基本使用4.3 向容器中动态添加碎片4.4 活动与碎片之间通信方法4.5 碎片的生命周期4.6 使用限…

重载单目运算符以及重载运算符的注意事项

一、单目运算符的重载 单目运算符可分为两种: 1)可以放在前面,也可以放在后面的单目,如: -- 2)只能放在前面的运算符:! (正号) -(负号&#x…

Python函数详解(四)——Python函数参数使用注意事项

今天继续给大家介绍Python相关知识,本文主要内容是Python函数参数使用注意事项。 在上文Python函数详解(三)——函数的参数传递进阶中,我们学习了函数参数的进阶内容。今天,我们来学习函数的参数使用注意事项。 一、P…

【微信小程序】使用 Cryptiojs 解密微信绑定手机号码

很抱歉断更了一段时间,因为最近在做一个项目比较忙,正好项目中小程序板块需要解密手机号码来提交给接口,小程序中虽然提供了获取手机号按钮点击事件:bindgetphonenumber,但是该事件的处理函数中只能获取到加密过的手机…

关于如何调节Mahony AHRS算法的参数

文章目录一、Mahony算法的控制系统特征多项式二、Kp, Ki参数调节方法三、其他自适应调参法我在之前的博客AHRS互补滤波(Mahony)算法及开源代码中曾提及Mahony算法的难点在于如何调节PI参数。 最近看到参考文献[1],提出了基于无阻尼自由频率设…

C# 将一种类型的数组转换为另一种类型的数组

将一种类型的数组转换为另一种类型的数组 public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] array, Converter<TInput, TOutput> converter); 参数: array: 要转换为目标类型的从零开始的一维 System.Array。 converter: …