PyQt(Python+Qt)实现的GUI图形界面应用程序的事件捕获方法大全及对比分析
一、 概述
PyQt的图形界面应用中,事件处理类似于Windows系统的消息处理。一个带图形界面的应用程序启动后,事件处理就是应用的主循环,事件处理负责接收事件、分发事件、接收应用处理事件的返回结果,在程序中捕获应用关注的事件触发相关事件处理是良好UI开发的必经之路。那么在PyQt的图形界面应用中,有哪些方法可以捕获事件以进行处理呢?下面我们就来分析一下。
二、 应用层级的事件捕获
2.1、notify方法捕获应用事件
PyQt的事件处理是从应用主程序开始的,在PyQt应用主程序中,真正负责事件分发处理的是QApplication类的notify方法(或称为notify函数),该方法负责向接收者发送事件,返回接收事件对象的处理程序返回的值。因此要在应用中捕获事件并进行处理,只要通过从QApplication类派生自定义的应用类并重写notify方法就可以捕获应用接收到的所有事件。
2.1.1、notify的语法
notify(QObject receiver, QEvent event)
其中:
1、参数receiver表示将事件发送给谁;
2、event就是事件参数,类型为QEvent ,如果不了解请参考《PyQt学习随笔:Qt事件类QEvent详解》;
3、返回值为receiver的事件处理方法的返回值,如果返回值是False表示接收者对事件不感兴趣,需要应用将事件信息继续向下传到接收者的父级,依此类推,直至顶级对象,如果返回True表示消费了事件,事件不会再往下传递。
2.1.2、一段notify重写的示例代码
class App(QApplication):
def notify(self, eventobject: QObject, event: QEvent):
"""
本次重写notify是为了截获应用的所有事件,并针对鼠标和键盘按下事件输出事件相关的信息
:param eventobject: 事件接收对象
:param event: 具体事件
:return: True表示事件已经处理,False表示没有处理,需要继续往下传递
"""
eventtype = event.type()
flag = False
if eventtype==QEvent.Close or eventtype==QEvent.KeyPress or eventtype == QEvent.MouseButtonPress: #
flag=True
#if (isinstance(eventobject, QtGui.QWindow)):
#super().notify(eventobject, event)
# return False
if flag:
print(f"In app notify:事件类型值={eventtype},事件接收者:{eventobject},parent={eventobject.parent()},child={eventobject.children()}")
ret = super().notify(eventobject, event)
if flag:
print(f"App notify end,事件接收者:{eventobject},事件返回值={ret},app={self},parent={eventobject.parent()}")
return ret
2.1.3、重写notify方法后的应用主程序示例代码
由于重写notify方法需要使用从QApplication派生自定义类,因此应用主程序的应用对象应该从新派生类构建,实例代码如下:
if __name__ == '__main__':
app = App(sys.argv)
w = eventCap() #界面对象对应类
w.show()
sys.exit(app.exec_())
2.2、安装应用级的事件过滤方法
2.2.1、概述
要捕获应用级的事件,除了Notify方法外,还可以采用安装应用级的事件过滤方法。
事件过滤会接收到所有发给该对象的所有事件,事件过滤可以终止事件或继续将事件提交到这个对象往下处理。事件过滤通过对象的eventFilter() 方法来接收事件,如果事件需要被终止,则eventFilter()方法需要返回True,否则返回False。
一个对象上可以安装多个事件过滤,这时候最后安装的事件过滤在事件到达时最先处理。
2.2.2、安装应用级事件过滤的步骤
要安装应用级的事件,需要如下步骤:
1、 在某个用来进行事件监控的从QObject派生的自定义类中重写派生类的eventFilter方法;
2、 在需要监控的对象上调用installEventFilter安装事件监控,由于本部分介绍的是应用级的事件过滤,因此需要使用应用的实例对象来安装。
2.2.3、eventFilter方法的语法
bool eventFilter(QObject watched, QEvent event)
其中:
1、watched:监视对象,就是被安装了eventFilter的对象;
2、event:接收到的事件信息;
3、返回值:为True表示事件到此结束,即该事件不会再往下传递,否则会继续传递。
2.2.4、installEventFilter方法的语法
monitorObj.installEventFilter(QObject filterObj)
其中:
1、 monitorObj:需要进行事件刷选的对象;
2、 filterObj:重写了eventFilter方法的对象;
3、 该方法无返回值。
注意:monitorObj和filterObj在多线程应用中,这两个对象必需在同一个线程内,否则installEventFilter不起作用。
2.2.5、自定义事件刷选类代码示例
class eventMonitor(QObject):
def eventFilter(self, objwatched, event):
eventType = event.type()
flag = eventType == QEvent.MouseButtonPress or eventType == QEvent.KeyPress or eventType == QEvent.Close #
if flag:
print(f"In eventMonitor eventFilter:事件类型值={eventType},事件objwatched={objwatched},parent={objwatched.parent()},child={objwatched.children()}")
ret = super().eventFilter(objwatched, event)
if flag: self.log(f'eventMonitor eventFilter end,ret={ret}')
return ret
2.2.6、事件过滤安装代码示例
下面这段代码对应用和应用窗口的一个按钮安装了同一个事件刷选对象:
if __name__ == '__main__':
app = QApplication(sys.argv)
w = eventCap() #界面对象创建
w.show()
monitorObj = eventMonitor() #创建事件刷选监视对象
app.installEventFilter(monitorObj) #应用安装事件刷选
w.pushButton_eventtest.installEventFilter(monitorObj) #主窗口的pushButton_eventtest安装事件刷选
sys.exit(app.exec_())
三、 部件级的事件捕获方法
3.1、基于事件刷选进行部件级的事件捕获
对部件使用事件刷选就可以实现部件级的事件捕获,相关方法与应用级的事件刷选完全一样,只是在调用installEventFilter安装事件时,调用的对象由应用改成了对应部件对象。在2.2.6部分介绍的案例就同时安装了一个应用级的事件刷选和一个部件级的事件刷选。在此不再重复介绍。
3.2、重写部件类的event方法捕获对象的事件
3.2.1、概述
在PyQt的部件对象中,都有从QWidget中继承的方法event,而QWidget.event是对QObject类定义的虚拟方法event的实现。在部件类中,event方法是处理部件收到的所有消息,因此如果部件类是从QWidget等PyQt提供的部件类派生的自定义类,则可以在自定义类中重写event方法实现部件收到的所有事件的处理。
3.2.2、event方法的语法
bool event(QEvent e)
其中:
1、 参数e:为事件;
2、 返回值:如果事件被识别并处理应该返回True,对于没有被应用识别和处理的事件,需要调用父类的event方法以保证事件的正确处理,此时应该返回父类event方法的返回值。
3.2.3、注意
1、该方法中只能捕获确认是发给对应对象的事件,不能捕获通过该对象转发给上级的事件;
2、通过重写该方法可以捕获对象的所有事件,但Qt并不推荐这种使用方法,而应该通过重写具体事件的具体方法来捕获特定事件;
3、event和特定事件的事件处理方法针对一个特定事件处理时,先调用event再调用特定事件的事件处理方法;
4、如果event处理事件时,没有调用父类方法,则事件处理终止,对应的事件不能再被该事件的特定事件处理方法捕获;
5、键盘按下和释放事件的处理方式与其他事件不同,event()会检查键盘事件是否为tab和shift+tab释放事件,如果是尝试移动焦点。如果没有要将焦点移动到的小部件(或按键不是tab或shift+tab),event()调用keyPressEvent()处理该键盘按键事件。
3.2.4、示例代码
def event(self, eventobj):
eventtype = eventobj.type()
flag = False
if eventtype == QEvent.Close or eventtype == QEvent.MouseButtonPress or eventtype == QEvent.KeyPress: #
flag = True
if flag:
self.log(f"In event,事件类型值={eventtype},事件接收者:{self},parent={self.parent()},child={self.children()}")
ret = super().event(eventobj)
if flag:
self.log(f"Event end,事件返回值={ret}")
return ret
3.3、重写特定事件处理方法捕获对象的特定事件
3.3.1、概述
大多数时候,我们无需截获所有事件进行处理,只需要进行特定事件的捕获和处理,当然可以用前面几种方法加上事件类型判断来进行捕获和处理,但这些方法会对应用的整体事件处理产生性能影响,因此最好是需要处理什么事件就捕获什么事件。这种情况建议通过自定义类重写部件的特定事件处理函数来实现。
3.3.2、常用特定事件列表
keyPressEvent: 键盘按下事件
keyReleaseEvent: 键盘释放事件
mouseDoubleClickEvent: 鼠标双击事件
mouseMoveEvent: 鼠标移动事件
mousePressEvent: 鼠标按下事件
mouseReleaseEvent: 鼠标释放事件
timerEvent: 定时器事件
dragEnterEvent: 拖拽进入当前窗口事件
dragLeaveEvent: 拖拽离开当前窗口事件
dragMoveEvent: 拖拽移动事件
enterEvent: 进入窗口区域事件
leaveEvent: 离开窗口区域事件
closeEvent: 关闭窗口事件
paintEven:界面绘制事件
3.3.3、示例代码
def keyPressEvent(self, keyevent):
print(f"In keyPressEvent:键盘按键 {keyevent.text()},0X{keyevent.key():X} 被按下")
def mousePressEvent(self, mouseEvent):
print(f"In mousePressEvent:鼠标按下")
3.4、通过信号与槽函数机制捕获事件
信号与槽函数机制严格意义上来说已经不属于事件处理机制,但大多数部件的信号就是从部件常用的事件处理中产生的,利用信号连接一个应用实现的槽函数,就能实现特定事件的应用响应,因此也可以认为是用来捕获事件的一种机制。信号和槽函数是Qt最重要的机制之一,通过这种机制实现了界面和应用处理逻辑的分离,关于Qt信号和槽函数的内容可以查阅的资料很多,在此就不展开介绍。
四、 PyQt事件捕获几种方法的处理过程
4.1、事件处理流程
通过在一个应用中实现上面介绍的六种方法(两种应用级、四种部件级)来捕获事件,且不终止事件传递的情况下,会发现事件在这些方法的流转过程如下:
正常情况下,事件到达应用后,应用调用notify通知QWindow隐形窗口对象(关于该隐形窗口对象,请参阅《PyQt学习遇到的问题:重写notify发送的消息为什么首先给了一个QWindow对象?》后,再通知对应部件,部件收到后会进行事件处理,判断是否该接受该事件,如果接受了则事件处理终止,如果不接受则传给部件的父对象进行处理。
4.2、事件处理案例
4.2.1 案例背景
在一个名为app的应用中,有个名为w的主窗口,主窗口上有个名为testButton的按钮,应用已经启动。下面案例的跟踪信息是使用上面示例代码输出的信息,相关界面如下:
注意:上面界面窗口显示的跟踪信息没有包含eventFilter方法的,详细输出信息需要看程序的打印输出。
4.2.2 案例1:使用鼠标点击testButton的按钮的事件处理过程
截获的事件及解释如下:
1、 In app notify:事件类型值=2,事件接收者:<PyQt5.QtGui.QWindow object at 0x0000000004B17318>,parent=None,child=[]
事件说明:应用notify通知QWindow隐形窗口对象,事件类型值=2表示鼠标按键事件,事件类型取值及含义具体可参考《PyQt学习随笔:Qt事件QEvent.type类型常量及其含义资料汇总详细内容速查》;
2、In eventMonitor eventFilter:事件类型值=2,事件objwatched=<PyQt5.QtGui.QWindow object at 0x0000000004B17318>,parent=None,child=[]
3、eventMonitor eventFilter end,ret=False
事件说明:应用的事件刷选捕获到发给QWindow隐形窗口对象的鼠标按键事件
4、In app notify:事件类型值=2,事件接收者:<PyQt5.QtWidgets.QPushButton object at 0x0000000004638F78>,parent=<__main__.eventCap object at 0x0000000004638DC8>,child=[]
事件说明:应用notify通知QPushButton对象的鼠标按键事件
5、In eventMonitor eventFilter:事件类型值=2,事件objwatched=<PyQt5.QtWidgets.QPushButton object at 0x0000000004638F78>,parent=<__main__.eventCap object at 0x0000000004638DC8>,child=[]
6、eventMonitor eventFilter end,ret=False
事件说明:应用的事件刷选捕获到发给QPushButton对象的鼠标按键事件
7、In eventMonitor eventFilter:事件类型值=2,事件objwatched=<PyQt5.QtWidgets.QPushButton object at 0x0000000004638F78>,parent=<__main__.eventCap object at 0x0000000004638DC8>,child=[]
8、eventMonitor eventFilter end,ret=False
事件说明:主窗口部件的事件刷选捕获到发给QPushButton对象的鼠标按键事件
9、App notify end,事件接收者:<PyQt5.QtWidgets.QPushButton object at 0x0000000004638F78>,事件返回值=True,app=<__main__.App object at 0x0000000004638E58>,parent=<__main__.eventCap object at 0x0000000004638DC8>
10、App notify end,事件接收者:<PyQt5.QtGui.QWindow object at 0x0000000004B17318>,事件返回值=True,app=<__main__.App object at 0x0000000004638E58>,parent=None
事件说明:应用的两次notify调用结束返回
12、 In genevent:接收到信号,按钮<PyQt5.QtWidgets.QPushButton object at 0x0000000004638F78>被按下!
事件说明:槽函数genevent接收到QPushButton对象的鼠标按键事件
4.2.3 案例2:焦点选中testButton后,按下键盘按键‘a’事件处理过程
截获的事件及解释如下:
1、In app notify:事件类型值=6,事件接收者:<PyQt5.QtGui.QWindow object at 0x0000000004B17318>,parent=None,child=[]
事件说明:应用notify通知QWindow隐形窗口对象,事件类型值=6表示键盘按键事件
2、In eventMonitor eventFilter:事件类型值=6,事件objwatched=<PyQt5.QtGui.QWindow object at 0x0000000004B17318>,parent=None,child=[]
3、eventMonitor eventFilter end,ret=False
事件说明:应用的事件刷选捕获到发给QWindow隐形窗口对象的键盘按键事件
4、In app notify:事件类型值=6,事件接收者:<PyQt5.QtWidgets.QPushButton object at 0x0000000004638EE8>,parent=<__main__.eventCap object at 0x0000000004638DC8>,child=[]
事件说明:应用notify通知QPushButton对象的键盘按键事件
5、In eventMonitor eventFilter:事件类型值=6,事件objwatched=<PyQt5.QtWidgets.QPushButton object at 0x0000000004638EE8>,parent=<__main__.eventCap object at 0x0000000004638DC8>,child=[]
6、eventMonitor eventFilter end,ret=False
事件说明:应用的事件刷选捕获到发给QPushButton对象的键盘按键事件
7、In eventMonitor eventFilter:事件类型值=6,事件objwatched=<__main__.eventCap object at 0x0000000004638DC8>,parent=None,child=[<PyQt5.QtWidgets.QPushButton object at 0x0000000004638EE8>, <PyQt5.QtWidgets.QPushButton object at 0x0000000004638F78>, <PyQt5.QtWidgets.QLineEdit object at 0x0000000004B17048>, <PyQt5.QtWidgets.QWidget object at 0x0000000004B170D8>]
8、eventMonitor eventFilter end,ret=False
事件说明:主窗口部件的事件刷选捕获到发给QPushButton对象的键盘按键事件
9、In event,事件类型值=6,事件接收者:<__main__.eventCap object at 0x0000000004638DC8>,parent=None,child=[<PyQt5.QtWidgets.QPushButton object at 0x0000000004638EE8>, <PyQt5.QtWidgets.QPushButton object at 0x0000000004638F78>, <PyQt5.QtWidgets.QLineEdit object at 0x0000000004B17048>, <PyQt5.QtWidgets.QWidget object at 0x0000000004B170D8>]
事件说明:主窗口部件的event方法捕获到键盘按键事件,但处理未完成
10、In keyPressEvent:键盘按键 a,0X41 被按下
事件说明:主窗口部件的keyPressEvent方法捕获到键盘按键事件,处理完成
11、Event end,事件返回值=True
事件说明:主窗口部件的event方法结束
12、App notify end,事件接收者:<PyQt5.QtWidgets.QPushButton object at 0x0000000004638EE8>,事件返回值=True,app=<__main__.App object at 0x0000000004638E58>,parent=<__main__.eventCap object at 0x0000000004638DC8>
13、App notify end,事件接收者:<PyQt5.QtGui.QWindow object at 0x0000000004B17318>,事件返回值=True,app=<__main__.App object at 0x0000000004638E58>,parent=None
事件说明:应用的两次notify调用结束返回
从上面两个案例对比来说:
1、 步骤1-6基本相同,只是事件类型不同;
2、 第7-8步中,案例1由于鼠标按键事件被按钮接收了,所以窗口部件的事件刷选捕获到的是按钮的鼠标按键事件,案例2由于键盘事件按钮没有被接受,被往下传递给了其父节点主窗口,因此主窗口的事件刷选捕获到的是发给主窗口的键盘按键事件;
3、 案例1中由于按钮接受了鼠标按键事件,而按钮没有重写event及mousePressEvent方法,因此整个事件在步骤7-8后就进入结束了,而案例2的事件在步骤7-8传递给了主窗口,因此触发了后续步骤的event方法以及后续的keyPressEvent方法。
五、 PyQt事件捕获方法的对比
注:本文示例案例的完整代码请到《PyQt图形应用事件捕获案例.rar》下载。
博客地址:https://blog.csdn.net/LaoYuanPython
老猿Python博客文章目录:https://blog.csdn.net/LaoYuanPython/article/details/98245036
PyQt(Python+Qt)实现的GUI图形界面应用程序的事件捕获方法大全及对比分析的更多相关文章
- 第15.17节 PyQt(Python+Qt)入门学习:PyQt图形界面应用程序的事件捕获方法大全及对比分析
老猿Python博文目录 老猿Python博客地址 按照老猿规划的章节安排,信号和槽之后应该介绍事件,但事件在前面的随笔<PyQt(Python+Qt)实现的GUI图形界面应用程序的事件捕获方法 ...
- 第七章、PyQt图形界面应用程序的事件捕获方法
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一. 概述 PyQt的图形界面应用中,事件处理类似于Windows系统的消息处理.一个带图形界面的应 ...
- PyQt(Python+Qt)学习随笔
老猿Python博文目录 老猿Python博客地址 PyQt学习随笔 PyQt(Python+Qt)帮助文档官网及文档下载 PyQt(Python+Qt)学习随笔:PyQt帮助文档导入assistan ...
- 第15.14节 PyQt(Python+Qt)入门学习:Designer的Buttons按钮详解
一.引言 Qt Designer中的Buttons部件包括Push Button(常规按钮.一般称按钮).Tool Button(工具按钮).Radio Button(单选按钮).Check Box( ...
- PyQt(Python+Qt)学习随笔:利用QWidget部件的palette以及ColorGroup、colorRole局部调整部件的特定范围颜色
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 在<PyQt(Python+Qt)学习随笔:QWidget部件的 ...
- 第15.33节 PyQt(Python+Qt)入门学习:containers容器类部件QTabWidget选项窗部件简介
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 容器部件就是可以在部件内放置其他部件的部件,在Qt Designer中可以使用的容器部件有 ...
- 第15.31节 PyQt(Python+Qt)入门学习:containers容器类部件GroupBox分组框简介
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 容器部件就是可以在部件内放置其他部件的部件,在Qt Designer中可以使用的容器部件有 ...
- 第15.29节 PyQt(Python+Qt)入门学习:containers容器类部件QScrollArea滚动区域详解
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 Scroll Area提供了一个呈现在其他部件上的可滚动区域视图,滚动区域用于显示框架内的 ...
- 第15.28节 PyQt(Python+Qt)入门学习:Model/View架构中的便利类QTableWidget详解
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.引言 表格部件为应用程序提供标准的表格显示工具,在表格内可以管理基于行和列的数据项,表格中的最大 ...
随机推荐
- I am coming back
时隔两年,我回来了,回到这个我梦开始的地方,带着一个新的身份--研究生!
- 水题挑战6: CF1444A DIvision
A. Division time limit per test1 second memory limit per test512 megabytes inputstandard input outpu ...
- Effective Modern C++ ——条款2 条款3 理解auto型别推导与理解decltype
条款2.理解auto型别推导 对于auto的型别推导而言,其中大部分情况和模板型别推导是一模一样的.只有一种特例情况. 我们先针对auto和模板型别推导一致的情况进行讨论: //某变量采用auto来声 ...
- MySQL全面瓦解12:连接查询的原理和应用
概述 MySQL最强大的功能之一就是能在数据检索的执行中连接(join)表.大部分的单表数据查询并不能满足我们的需求,这时候我们就需要连接一个或者多个表,并通过一些条件过滤筛选出我们需要的数据. 了解 ...
- JS简单介绍与简单的基本语法
1.JavaScirpt是一门编程语言,是为前端服务的一门语言. (1)基础语法 (2)数据类型 (3)函数 (4)面向对象 2.还涉及到BOM和DOM (1)BOM(操作浏览器的一些功能) (2)D ...
- Serilog 源码解析——数据的保存(下)
上一篇中,我们提到了日志数据是如何进行解析了.然而,Serilog 灵活采用了不同的策略(Policy)决定一个日志对象如何解析到LogEventPropertyValue的子类对象中,即采用了ISc ...
- PHP+Ajax点击加载更多内容
css样式: <style type="text/css"> #more{margin:10px auto;width: 560px; border: 1px soli ...
- docker搭建渗透环境并进行渗透测试
目录 docker简介 docker的安装 docker.centos7.windows10(博主宿主机系统)之间相互通信 -docker容器中下载weblogic12c(可以略过不看) docker ...
- SHEIN:Java开发面经
SHEIN面经 我觉得除技术外,自信是一个非常关键的点. 一面 自我介绍: 谈谈实习经历: 讲讲你实习的收获: 如何设计规范的接口?(简历上有写,所以问到) 当你需要修改两个月前的代码时,如何去整理以 ...
- MySQL如何实现万亿级数据存储?
前言 业界对系统的高可用有着基本的要求,简单的说,这些要求可以总结为如下所示. 系统架构中不存在单点问题. 可以最大限度的保障服务的可用性. 一般情况下系统的高可用可以用几个9来评估.所谓的几个9就是 ...