PyQt5桌面应用开发(19):事件过滤器
PyQt5桌面应用系列
- PyQt5桌面应用开发(1):需求分析
- PyQt5桌面应用开发(2):事件循环
- PyQt5桌面应用开发(3):并行设计
- PyQt5桌面应用开发(4):界面设计
- PyQt5桌面应用开发(5):对话框
- PyQt5桌面应用开发(6):文件对话框
- PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
- PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
- PyQt5桌面应用开发(9):经典布局QMainWindow
- PyQt5桌面应用开发(10):界面布局基本支持
- PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16
- PyQt5桌面应用开发(12):QFile与线程安全
- PyQt5桌面应用开发(13):QGraphicsView框架
- PyQt5桌面应用开发(14):数据库+ModelView+QCharts
- PyQt5桌面应用开发(15):界面动画
- PyQt5桌面应用开发(16):定制化控件-QPainter绘图
- PyQt5桌面应用开发(17):类结构+QWebEngineView
- PyQt5桌面应用开发(18):自定义控件界面设计与实现
- PyQt5桌面应用开发(19):事件过滤器
再来点事件
在利用PyQt或者Qt进行界面设计和GUI程序编制时,最核心的是展示一个可交互或信息呈现的图形界面。因此,事件成为了非常重要且值得反复思考和学习的内容。事件是更新图形界面系统所必需的核心元素。Qt采用QObject方式为每个对象配置定时器和事件处理循环,并调用对象exec()(在PyQt中是exec_)来启动该循环,以便按时处理相应时间,包括系统中发生的事件、用户操作以及逻辑上产生的信号等。当跨线程出现事件时,Qt还通过线程安全队列保证对相关信息访问和改变具有一致性。
对于界面上跟用户交互的时间,有三类处理方法:
- 处理事件的方法一:重载event函数,调用QEvent.type()与QEvent的枚举进行比较
- 处理事件的方法二:重载各种xxxEvent函数,针对特定事件实现逻辑
- 处理事件的方法三:注册事件过滤器
可以对这三种方法进行简单比较。在重载事件和使用事件过滤器时,需要自行判断事件类型并调用QEvent对象的type方法与QtEvent中定义的枚举变量进行比较。此外,还可能需要手动转换QEvent对象,例如,在鼠标移动事件中,实际上引用的是QMouseMove类。而在重载特定时间对应的虚函数时,则无需进行比较,并且相应的事件类也是准确的子类。
方法 | 特点 |
---|---|
重载event函数 | 一次处理所有事件;需要自己编写事件类型判断并做转换;调整逻辑难以清晰表达 |
重载特定事件函数 | 逻辑清晰;只针对特定的事件,按键、鼠标按键、鼠标移动等;根据情景调整事件处理的逻辑实现起来比较不清晰 |
事件过滤器 | 需要处理事件发送对象;需要自行判断事件类型并做类型转换;很方便的调整事件是否触发 |
事件过滤器
对于重载函数,前面的几篇文章中也多多少少的涉及到了,也非常好理解,就是面向对象中的函数多态的典型用法。事件循环中针对QWidget中虚函数接口编程,而各个子类中重载的虚函数则实现实际的逻辑。这个实际的逻辑在编码的时候就已经定义好,如果需要切换不同的逻辑,则需要在函数中定义各种判断条件,这些判断条件就会增加程序中不同组件之间的耦合,在比较简单的场合,这是很容易实现的。如果判断的逻辑比较复杂,从设计模式的依赖倒置等方法出发,则应该将不同的逻辑提取出来,实现单独的组件。这就是事件过滤器。
事件过滤器需要实现一个函数def eventFilter(self, a0: QObject, a1: QEvent):
,对于实现这个函数的兑现,就可以通过installEventFilter
和removeEventFilter
来动态调整是否启用eventFilter
所包含的事件处理逻辑。
例子
这是什么恶毒巫术?
接下来,我们来一点点光影魔法(下面的内容对眼睛极为不友好……)
需求分析
要实现的报表:
- 斜纹、圈纹和闪光
交互:
- 鼠标移动,图案变幻
- 滚轮:调整图案密集程度
- 键盘事件:调整图案形式
- 键盘事件:启动和停止闪瞎
代码
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_())
额外的细节知
- Gradient设置渐变画刷
- QLinearGradient:线性变化
- QRadialGradient:径向变化
- QConicalGradient:圆周变化
- 鼠标滚轮事件wheelEvent函数
- QWheelEvent
- QEvent.Wheel
- angleDelta().y()上下的滚轮调整数值
- mouseMoveEvent函数
- QMouseMoveEvent
- QEvent.MouseMove
- setMouseTracking(True):无需按下按键触发mouseMoveEvent
总结
- 事件过滤器是一种可以在运行时改变的事件处理机制;
- 只需要实现接口eventFilter函数,就能作为过滤器安装和卸载;
- Gradient渐变画刷还是不要用了……会被打