本文目录
- PyQt5桌面应用系列
- 再来点事件
- 事件过滤器
- 例子
- 这是什么恶毒巫术?
- 需求分析
- 代码
- 额外的细节知
- 总结
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函数,针对特定事件实现逻辑
- 处理事件的方法三:注册事件过滤器
可以简单比较这三种方法,重载event和事件过滤器中需要自己判断事件的类型,调用QEvent对象的type方法,并于QtEvent中定义的枚举变量进行对比,之后可能还需要对QEvent对象进行手动的转换,例如鼠标移动事件里,QEvent实际上引用的是QMouseMoveEvent;重载特定时间对应的事件虚函数,则无需进行比较,对应的事件类也是准确的子类。
方法 | 特点 |
---|---|
重载event函数 | 一次处理所有事件;需要自己编写事件类型判断并做转换;调整逻辑难以清晰表达 |
重载特定事件函数 | 逻辑清晰;只针对特定的事件,按键、鼠标按键、鼠标移动等;根据情景调整事件处理的逻辑实现起来比较不清晰 |
事件过滤器 | 需要处理事件发送对象;需要自行判断事件类型并做类型转换;很方便的调整事件是否触发 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JFTNJMHp-1685755483481)(imgs/events.png)]
事件过滤器
对于重载函数,前面的几篇文章中也多多少少的涉及到了,也非常好理解,就是面向对象中的函数多态的典型用法。事件循环中针对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渐变画刷还是不要用了……会被打