18. [Python GUI] PyQt5中的事件系统之事件过滤器EventFilter

PyQt5中的事件过滤器EventFilter

1. 什么是事件过滤器EventFilter

Qt事件系统一个非常强大的功能是:QObject实例在看到它自己的事件之前,可以通过设置另外一个QObject实例先监视这些事件。在一个部件中监控其他多个部件的事件,这便是通过Qt提供的事件过滤器来实现的。

事件过滤器和其他部件不同,它不是一个类,只是由两个函数组成的一种操作,用来完成一个部件对其他部件的事件的监视。要对一个部件使用事件过滤器,那么就要先使用它的installEventFilter()方法为其安装事件过滤器,这个方法的参数表明了监视对象。

2. Qt实现事件过滤器的步骤

  • Qt 调用
    void QObject::installEventFilter (QObject* filterObj)
    把 filterObj 对象设置安装(或注册)为事件过滤器, filterObj 也称为过滤器对象。 事件过滤器通常在构造函数中进行注册

  • 在上一步注册的 filterObj 对象,通过调用
    bool QObject::eventFilter(QObject* obj, QEvent* e);
    来接收拦截到的事件。也就是说拦截到的事件在 filterObj 对象中的 eventFilter 函数中处理。 eventFilter 的第一个参数 obj 指向的是事件本应传递到的目标对象

  • 使用 QObject::removeEventFilter(QObject *obj)函数可以删除事件过滤器

3. 事件过滤器处理事件的规则

  • 过滤器对象的 eventFilter()函数可以接受或拒绝拦截到的事件,若该函数返回 false,则表示事件需要作进一步处理,此时事件会被发送到目标对象本身进行处理(注意:这里并未向父对象进行传递),若 evetnFilter()返回 true,则表示停止处理该事件,此时目标对象和后面安装的事件过滤器就无法获得该事件

  • 若同一对象安装了多个事件过滤器,则最后安装的过滤器首先被激活

4. 为什么使用事件过滤器

使用事件过滤器可以简化程序代码。比如按钮 1 和标签 1,对按下 A 键的事件响应相同的操作,若不使用事件过滤器, 则需要分别子类化按钮和标签部件,并重新实现各自的事件处理函数。 再如使用同一个子类化按钮的类 C 创建的按钮 1和按钮 2,对按下键 A, 按钮 1 和按钮 2 需要作不同的响应,若不使用事件过滤器,则他们的响应是相同的,若使用事件过滤器,则可以拦截按钮1或按钮2的事件并作特殊处理。

5. 理解事件过滤器

观察者模式:其原理为,设有一目标对象 S,它有多个观察该对象的对象 G1, G2, G3,当 S 发生变化时, S 的观察者会依情形改变自身。应用于 Qt 事件过滤器,则是,首先使用 S 的成员函数 installEventFilter 函数把 G1, G2, G3 设置为 S 的观察者,所有本应传递给 S 的事件 E,则先传递给观察者 G1, G2, G3, 然后观察者调用其成员函数 eventFilter对传递进来的事件进行处理,若 eventFilter 返回 true 表示事件处理完毕,返回 false 则返回给被观察者 S 进行处理。 见下图。

20221121214932

6. 示例代码

事件过滤器的使用示例代码:

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

import sys

class MyFilterA(QObject):
  '''
  该类的对象用作过滤器对象,使用事件过滤器需要继承自QObject
  '''
  def eventFilter(self, obj: 'QObject', event: 'QEvent') -> bool:
    if event.type() == QEvent.Type.MouseButtonPress:
      qDebug(f'{self.objectName()} : MyFilterA: mouse press')
      # 返回true表示该事件不再进一步处理
      return True
    # 返回false,表示其余事件交还给目标对象处理,本例应返回false
    return False

class MyFilterB(QObject):
  '''
  该类的对象用作过滤器对象,使用事件过滤器需要继承自QObject
  '''
  def eventFilter(self, obj: 'QObject', event: 'QEvent') -> bool:
    if event.type() == QEvent.Type.MouseButtonPress:
      qDebug(f'{self.objectName()} : MyFilterB: mouse press')
    return False

class MyWidget(QWidget):
  def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
    qDebug('MyWidget: mouse press')

class MyPushButton(QPushButton):
  def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
    qDebug('MyPushButton: mouse press')

if __name__ == '__main__':
  app = QApplication(sys.argv)
  
  my_filter_a = MyFilterA()
  my_filter_b = MyFilterB(my_filter_a)
  my_widget = MyWidget()
  my_btn_1 = MyPushButton('AAA', my_widget)
  my_btn_2 = MyPushButton('BBB', my_widget)
  
  # 设置对象名
  my_filter_a.setObjectName('my_filter_a')
  my_filter_b.setObjectName('my_filter_b')
  my_widget.setObjectName('my_widget')
  my_btn_1.setObjectName('my_btn_1')
  my_btn_2.setObjectName('my_btn_2')
  
  # 注册过滤器对象
  my_btn_1.installEventFilter(my_filter_a)
  my_btn_2.installEventFilter(my_filter_b)
  
  my_widget_layout = QHBoxLayout()
  my_widget_layout.addWidget(my_btn_1)
  my_widget_layout.addWidget(my_btn_2)
  my_widget.setLayout(my_widget_layout)
  
  my_widget.show()
  sys.exit(app.exec_())

运行效果:

20221121234230

运行结果说明:

  • 按下buttonA后,过滤器my_filter_a拦截到了mouse press事件,做出了自己的动作后(此处为打印),不再做进一步处理
  • 而按下buttonB后,过滤器my_filter_b也拦截到了mouse press事件,并做出了自己的动作(此处为打印),然后将此事件继续传递给目标对象,即MyPushButtont对象,MyPushButton对象捕捉到该事件后,继续自己的处理逻辑,至此事件处理结束。

安装多个事件过滤器的代码简明示例如下:

# 定义三个filter
filter1, filter2, filter3

# 依次安装三个filter
my_object.installEventFilter(filter1)
my_object.installEventFilter(filter2)
my_object.installEventFilter(filter3)

安装事件过滤器的顺序为filter1, filter2, filter3,调用时是逆序进行,即filter3->filter2->filter1


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

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smart_cat

你的鼓励将是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值