PyQt5桌面应用开发(19):事件过滤器

PyQt5桌面应用系列

再来点事件

在利用PyQt或者Qt进行界面设计和GUI程序编制时,最核心的是展示一个可交互或信息呈现的图形界面。因此,事件成为了非常重要且值得反复思考和学习的内容。事件是更新图形界面系统所必需的核心元素。Qt采用QObject方式为每个对象配置定时器和事件处理循环,并调用对象exec()(在PyQt中是exec_)来启动该循环,以便按时处理相应时间,包括系统中发生的事件、用户操作以及逻辑上产生的信号等。当跨线程出现事件时,Qt还通过线程安全队列保证对相关信息访问和改变具有一致性。

对于界面上跟用户交互的时间,有三类处理方法:

  1. 处理事件的方法一:重载event函数,调用QEvent.type()与QEvent的枚举进行比较
  2. 处理事件的方法二:重载各种xxxEvent函数,针对特定事件实现逻辑
  3. 处理事件的方法三:注册事件过滤器

可以对这三种方法进行简单比较。在重载事件和使用事件过滤器时,需要自行判断事件类型并调用QEvent对象的type方法与QtEvent中定义的枚举变量进行比较。此外,还可能需要手动转换QEvent对象,例如,在鼠标移动事件中,实际上引用的是QMouseMove类。而在重载特定时间对应的虚函数时,则无需进行比较,并且相应的事件类也是准确的子类。

方法特点
重载event函数一次处理所有事件;需要自己编写事件类型判断并做转换;调整逻辑难以清晰表达
重载特定事件函数逻辑清晰;只针对特定的事件,按键、鼠标按键、鼠标移动等;根据情景调整事件处理的逻辑实现起来比较不清晰
事件过滤器需要处理事件发送对象;需要自行判断事件类型并做类型转换;很方便的调整事件是否触发

在这里插入图片描述

事件过滤器

对于重载函数,前面的几篇文章中也多多少少的涉及到了,也非常好理解,就是面向对象中的函数多态的典型用法。事件循环中针对QWidget中虚函数接口编程,而各个子类中重载的虚函数则实现实际的逻辑。这个实际的逻辑在编码的时候就已经定义好,如果需要切换不同的逻辑,则需要在函数中定义各种判断条件,这些判断条件就会增加程序中不同组件之间的耦合,在比较简单的场合,这是很容易实现的。如果判断的逻辑比较复杂,从设计模式的依赖倒置等方法出发,则应该将不同的逻辑提取出来,实现单独的组件。这就是事件过滤器。

事件过滤器需要实现一个函数def eventFilter(self, a0: QObject, a1: QEvent):
,对于实现这个函数的兑现,就可以通过installEventFilterremoveEventFilter来动态调整是否启用eventFilter所包含的事件处理逻辑。

例子

这是什么恶毒巫术?

接下来,我们来一点点光影魔法(下面的内容对眼睛极为不友好……)

在这里插入图片描述

需求分析

要实现的报表:

  1. 斜纹、圈纹和闪光

交互:

  1. 鼠标移动,图案变幻
  2. 滚轮:调整图案密集程度
  3. 键盘事件:调整图案形式
  4. 键盘事件:启动和停止闪瞎

代码

import sys

import numpy as np
from PyQt5.QtCore import QEvent, Qt, QObject
from PyQt5.QtGui import QColor, QGradient, QRadialGradient, QLinearGradient, QConicalGradient, QWheelEvent
from PyQt5.QtWidgets import QApplication, QWidget


def random_color() -> QColor:
    return QColor(*np.random.randint(0, 255, 4))


class Pattern(int):
    Linear = 0
    Radial = 1
    Conical = 2


class EventWidget(QWidget):
    def __init__(self, parent=None):
        super(EventWidget, self).__init__(parent)
        self.installEventFilter(self)
        self.setMouseTracking(True)
        palette = self.palette()
        palette.setBrush(self.backgroundRole(), QGradient(QGradient.Blessing))
        self.setPalette(palette)

        self.pattern = Pattern.Radial
        self.pattern_count = 12

        self.setToolTip("L: 更换花纹;C+space:停止;space:启用;鼠标滚轮:增加/减少条纹")

    def eventFilter(self, a0: QObject, a1: QEvent):

        if a0 == self:
            if a1.type() == QEvent.MouseMove:
                palette = self.palette()
                rect = self.geometry()
                gradient = None
                if self.pattern == Pattern.Linear:
                    gradient = QLinearGradient(0, 0, rect.width(), rect.height())
                if self.pattern == Pattern.Radial:
                    gradient = QRadialGradient(rect.width() / 2, rect.height() / 2, rect.width() / 2)
                if self.pattern == Pattern.Conical:
                    gradient = QConicalGradient(rect.width() / 2, rect.height() / 2, 0)
                n = self.pattern_count

                for i in range(n + 1):
                    gradient.setColorAt(i / n, random_color())
                gradient.setSpread(QGradient.RepeatSpread)
                palette.setBrush(self.backgroundRole(), gradient)

                self.setPalette(palette)
            if a1.type() == QEvent.Wheel:
                a1: QWheelEvent
                if a1.angleDelta().y() > 0:
                    self.pattern_count += 1
                else:
                    self.pattern_count -= 1
                    if self.pattern_count <= 0:
                        self.pattern_count = 100

        return super(EventWidget, self).eventFilter(a0, a1)

    def event(self, a0: QEvent) -> bool:

        if a0.type() == QEvent.KeyPress and a0.key() == Qt.Key_Tab:
            rect = self.geometry()
            if a0.modifiers() == Qt.ControlModifier:
                dx = -10
            else:
                dx = 10
            self.setGeometry(rect.x() + dx, rect.y(), rect.width(), rect.height())
        if a0.type() == QEvent.KeyPress and a0.key() == Qt.Key_Space:
            if a0.modifiers() == Qt.ControlModifier:
                self.removeEventFilter(self)
            else:
                self.installEventFilter(self)
        if a0.type() == QEvent.KeyPress and a0.key() == Qt.Key_L:
            self.pattern = (self.pattern + 1) % 3
        if a0.type() == QEvent.KeyPress and a0.key() == Qt.Key_K:
            self.pattern = (self.pattern - 1) % 3
        return super(EventWidget, self).event(a0)


if __name__ == '__main__':
    app = QApplication([])

    win = EventWidget()

    win.resize(600, 600)
    win.setWindowTitle("这是什么恶毒巫术!")
    win.show()

    sys.exit(app.exec_())

额外的细节知

  1. Gradient设置渐变画刷
    1. QLinearGradient:线性变化
    2. QRadialGradient:径向变化
    3. QConicalGradient:圆周变化
  2. 鼠标滚轮事件wheelEvent函数
    1. QWheelEvent
    2. QEvent.Wheel
    3. angleDelta().y()上下的滚轮调整数值
  3. mouseMoveEvent函数
    1. QMouseMoveEvent
    2. QEvent.MouseMove
    3. setMouseTracking(True):无需按下按键触发mouseMoveEvent

总结

  1. 事件过滤器是一种可以在运行时改变的事件处理机制;
  2. 只需要实现接口eventFilter函数,就能作为过滤器安装和卸载;
  3. Gradient渐变画刷还是不要用了……会被打
posted @ 2023-06-03 09:28  大福是小强  阅读(94)  评论(0编辑  收藏  举报  来源