Python Qt GUI设计:5种事件处理机制(提升篇—3)

简介: 之前在Python Qt GUI设计:QTimer计时器类、QThread多线程类和事件处理类(基础篇—8)中,我们已经简单讲到,PyQt为事件处理提供了两种机制:高级的信号与槽机制以及低级的事件处理程序,本篇博文将系统讲解Qt的事件处理机类和制。

目录

1、常见事件类型


2、事件处理方法


2.1、重新实现事件函数


2.2、重新实现QObject.event()


2.3、安装事件过滤器


2.4、在QApplication中安装事件过滤器


2.5、重新实现QApplication的notify()方法


之前在Python Qt GUI设计:QTimer计时器类、QThread多线程类和事件处理类(基础篇—8)中,我们已经简单讲到,PyQt为事件处理提供了两种机制:高级的信号与槽机制以及低级的事件处理程序,本篇博文将系统讲解Qt的事件处理机类和制。


事件处理机制本身很复杂,是PyQt底层的知识点,当采用信号与槽机制处理不了时,才会考虑使用事件处理机制。


信号与槽可以说是对事件处理机制的高级封装,如果说事件是用来创建窗口控件的,那么信号与槽就是用来对这个窗口控件进行使用的。比如一个按钮,当我们使用这个按钮时,只关心clicked信号,至于这个按钮如何接收并处理鼠标点击事件,然后再发射这信号,则不用关心。但是如果要重载一个按钮,这时就要关心这个问题了。比如可以改变它的行为:在鼠标按键按下时触发clicked信号,而不是在释放时。


1、常见事件类型

Qt事件的类型有很多,常见的Qt事件如下所示:


键盘事件:按键按下和松开。

鼠标事件:鼠标指针移动、鼠标按键按下和松开。

拖放事件:用鼠标进行拖放。

滚轮事件:鼠标滚轮滚动。

绘屏事件:重绘屏幕的某些部分。

定时事件:定时器到时。

焦点事件:键盘焦点移动。

进入和离开事件:鼠标指针移入Widget内,或者移出。

移动事件::Widget的位置改变。

大小改变事件:Widget的大小改变。

显示和隐藏事件:Widget显示和隐藏。

窗口事件:窗口是否为当前窗口。

还有一些常见的Qt事件,比如Socket事件、剪贴板事件、字体改变事件、布局改变事件等。


具体事件类型和说明可参见说明文档:


42becf7dfbe04c1f888285658f57ba9d.png


2、事件处理方法

PyQt提供了如下5种事件处理和过滤方法(由弱到强),其中只有前两种方法使用最频繁。


2.1、重新实现事件函数

比如mousePressEvent()、keyPressEvent()、paintEvent()。这是最常规的事件处理方法。


通过示例了解重新实现事件函数的使用方法,效果如下所示:



5f4771cc43d3492ab5d5a0cb7b7d33b0.png

2564bf6eb7d2460a902bd9b316b2517b.png

c439abf987634f2585212de52fc09982.png



这个示例中包含了多种事件类型,所以比较复杂。


首先是类的建立,建立text和message两个变量,使用paintEvent函数把它们输出到窗口中。


update函数的作用是更新窗口,由于在窗口更新过程中会触发一次 paintEvent函数(paintEvent是窗口基类QWidget的内部函数),因此在本例中update函数的作用等同于paintEvent函数。


然后是重新实现窗口关闭事件与上下文菜单事件,对于上下文菜单事件,主要影响message变量的结果,paintEvent负责把这个变量在窗口底部输出。


绘制事件是代码的核心事件,它的主要作用是时刻跟踪text与message这两个变量的信息,并把 text的内容绘制到窗口的中部,把message的内容绘制到窗口的底部(保持5秒后就会被清空)。


以及最后一些鼠标、键盘的点击操作等。


实现代码如下所示:

import sys
from PyQt5.QtCore import (QEvent, QTimer, Qt)
from PyQt5.QtWidgets import (QApplication, QMenu, QWidget)
from PyQt5.QtGui import QPainter
class Widget(QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self.justDoubleClicked = False
        self.key = ""
        self.text = ""
        self.message = ""
        self.resize(400, 300)
        self.move(100, 100)
        self.setWindowTitle("Events")
        QTimer.singleShot(0, self.giveHelp)  # 避免窗口大小重绘事件的影响,可以把参数0改变成3000(3秒),然后在运行,就可以明白这行代码的意思。
    def giveHelp(self):
        self.text = "请点击这里触发追踪鼠标功能"
        self.update() # 重绘事件,也就是触发paintEvent函数。
    '''重新实现关闭事件'''
    def closeEvent(self, event):
        print("Closed")
    '''重新实现上下文菜单事件'''
    def contextMenuEvent(self, event):
        menu = QMenu(self)
        oneAction = menu.addAction("&One")
        twoAction = menu.addAction("&Two")
        oneAction.triggered.connect(self.one)
        twoAction.triggered.connect(self.two)
        if not self.message:
            menu.addSeparator()
            threeAction = menu.addAction("Thre&e")
            threeAction.triggered.connect(self.three)
        menu.exec_(event.globalPos())
    '''上下文菜单槽函数'''
    def one(self):
        self.message = "Menu option One"
        self.update()
    def two(self):
        self.message = "Menu option Two"
        self.update()
    def three(self):
        self.message = "Menu option Three"
        self.update()
    '''重新实现绘制事件'''
    def paintEvent(self, event):
        text = self.text
        i = text.find("\n\n")
        if i >= 0:
            text = text[0:i]
        if self.key: # 若触发了键盘按钮,则在文本信息中记录这个按钮信息。
            text += "\n\n你按下了: {0}".format(self.key)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.TextAntialiasing)
        painter.drawText(self.rect(), Qt.AlignCenter, text) # 绘制信息文本的内容
        if self.message: # 若消息文本存在则在底部居中绘制消息,5秒钟后清空消息文本并重绘。
            painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter,
                             self.message)
            QTimer.singleShot(5000, self.clearMessage)
            QTimer.singleShot(5000, self.update)
    '''清空消息文本的槽函数'''
    def clearMessage(self):
        self.message = ""
    '''重新实现调整窗口大小事件'''
    def resizeEvent(self, event):
        self.text = "调整窗口大小为: QSize({0}, {1})".format(
            event.size().width(), event.size().height())
        self.update()
    '''重新实现鼠标释放事件'''
    def mouseReleaseEvent(self, event):
        # 若鼠标释放为双击释放,则不跟踪鼠标移动
        # 若鼠标释放为单击释放,则需要改变跟踪功能的状态,如果开启跟踪功能的话就跟踪,不开启跟踪功能就不跟踪
        if self.justDoubleClicked:
            self.justDoubleClicked = False
        else:
            self.setMouseTracking(not self.hasMouseTracking()) # 单击鼠标
            if self.hasMouseTracking():
                self.text = "开启鼠标跟踪功能.\n" + \
                            "请移动一下鼠标!\n" + \
                            "单击鼠标可以关闭这个功能"
            else:
                self.text = "关闭鼠标跟踪功能.\n" + \
                            "单击鼠标可以开启这个功能"
            self.update()
    '''重新实现鼠标移动事件'''
    def mouseMoveEvent(self, event):
        if not self.justDoubleClicked:
            globalPos = self.mapToGlobal(event.pos()) # 窗口坐标转换为屏幕坐标
            self.text = """鼠标位置:
            窗口坐标为:QPoint({0}, {1}) 
            屏幕坐标为:QPoint({2}, {3}) """.format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y())
            self.update()
    '''重新实现鼠标双击事件'''
    def mouseDoubleClickEvent(self, event):
        self.justDoubleClicked = True
        self.text = "你双击了鼠标"
        self.update()
    '''重新实现键盘按下事件'''
    def keyPressEvent(self, event):
        self.key = ""
        if event.key() == Qt.Key_Home:
            self.key = "Home"
        elif event.key() == Qt.Key_End:
            self.key = "End"
        elif event.key() == Qt.Key_PageUp:
            if event.modifiers() & Qt.ControlModifier:
                self.key = "Ctrl+PageUp"
            else:
                self.key = "PageUp"
        elif event.key() == Qt.Key_PageDown:
            if event.modifiers() & Qt.ControlModifier:
                self.key = "Ctrl+PageDown"
            else:
                self.key = "PageDown"
        elif Qt.Key_A <= event.key() <= Qt.Key_Z:
            if event.modifiers() & Qt.ShiftModifier:
                self.key = "Shift+"
            self.key += event.text()
        if self.key:
            self.key = self.key
            self.update()
        else:
            QWidget.keyPressEvent(self, event)
if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = Widget()
    form.show()
    app.exec_()

2.2、重新实现QObject.event()

一般用在PyQt没有提供该事件的处理函数的情况下,即增加新事件时。


对于窗口所有的事件都会传递给event函数,event函数会根据事件的类型,把事件分配给不同的函数进行处理。例如,对于绘图事件,event会交给paintEvent函数处理;对于鼠标移动事件,event会交给mouseMoveEvent函数处理;对于键盘按下事件,event会交给keyPressEvent函数处理。


有一种特殊情况是对Tab键的触发行为,event函数对Tab键的处理机制是把焦点从当前窗口控件的位置切换到Tab键次序中下一个窗口控件的位置,并返回True,而不是交给keyPressEvent函数处理。


因此这里需要在event函数中对按下Tab键的处理逻辑重新改写,使它与键盘上普通的键没什么不同。


在2.1、重新实现事件函数例子中补充以下代码,实现重新定义:


   '''重新实现其他事件,适用于PyQt没有提供该事件的处理函数的情况,Tab键由于涉及焦点切换,不会传递给keyPressEvent,因此,需要在这里重新定义。'''

    '''重新实现其他事件,适用于PyQt没有提供该事件的处理函数的情况,Tab键由于涉及焦点切换,不会传递给keyPressEvent,因此,需要在这里重新定义。'''
    def event(self, event):
        if (event.type() == QEvent.KeyPress and
                    event.key() == Qt.Key_Tab):
            self.key = "在event()中捕获Tab键"
            self.update()
            return True

效果如下所示:


e381e472050e474dadc82c6b3642bda3.png


2.3、安装事件过滤器

如果对QObject调用installEventFilter,则相当于为这个QObject安装了一个事件过滤器,对于QObject的全部事件来说,它们都会先传递到事件过滤函数eventFilter中,在这个函数中我们可以抛弃或者修改这些事件,比如可以对自己感兴趣的事件使用自定义的事件处理机制,对其他事件使用默认的事件处理机制。


由于这种方法会对调用installEventFilter的所有QObject的事件进行过滤,因此如果要过滤的事件比较多,则会降低程序的性能。


通过示例,了解事件过滤器的使用方法,效果如下所示:


1a47ab2ebe1641ae946065e2d919efcc.png


对于使用事件过滤器,关键是要做好两步。


对要过滤的控件设置installEventFilter,这些控件的所有事件都会被eventFilter函数接收并处理。


示例中,这个过滤器只对label1的事件进行处理,并且只处理它的鼠标按下事件(MouseButtonPress)和鼠标释放事件(MouseButtonRelease) 。


如果按下鼠标键,就会对label1装载的图片进行缩放(长和宽各缩放一半)。


实现代码如下所示:


# -*- coding: utf-8 -*-
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class EventFilter(QDialog):
    def __init__(self, parent=None):
        super(EventFilter, self).__init__(parent)
        self.setWindowTitle("事件过滤器")
        self.label1 = QLabel("请点击")
        self.label2 = QLabel("请点击")
        self.label3 = QLabel("请点击")
        self.LabelState = QLabel("test")
        self.image1 = QImage("images/cartoon1.ico")
        self.image2 = QImage("images/cartoon1.ico")
        self.image3 = QImage("images/cartoon1.ico")
        self.width = 600
        self.height = 300
        self.resize(self.width, self.height)
        self.label1.installEventFilter(self)
        self.label2.installEventFilter(self)
        self.label3.installEventFilter(self)
        mainLayout = QGridLayout(self)
        mainLayout.addWidget(self.label1, 500, 0)
        mainLayout.addWidget(self.label2, 500, 1)
        mainLayout.addWidget(self.label3, 500, 2)
        mainLayout.addWidget(self.LabelState, 600, 1)
        self.setLayout(mainLayout)
    def eventFilter(self, watched, event):
        if watched == self.label1: # 只对label1的点击事件进行过滤,重写其行为,其他的事件会被忽略
            if event.type() == QEvent.MouseButtonPress: # 这里对鼠标按下事件进行过滤,重写其行为
                mouseEvent = QMouseEvent(event)
                if mouseEvent.buttons() == Qt.LeftButton:
                    self.LabelState.setText("按下鼠标左键")
                elif mouseEvent.buttons() == Qt.MidButton:
                    self.LabelState.setText("按下鼠标中间键")
                elif mouseEvent.buttons() == Qt.RightButton:
                    self.LabelState.setText("按下鼠标右键")
                '''转换图片大小'''
                transform = QTransform()
                transform.scale(0.5, 0.5)
                tmp = self.image1.transformed(transform)
                self.label1.setPixmap(QPixmap.fromImage(tmp))
            if event.type() == QEvent.MouseButtonRelease: # 这里对鼠标释放事件进行过滤,重写其行为
                self.LabelState.setText("释放鼠标按钮")
                self.label1.setPixmap(QPixmap.fromImage(self.image1))
        return QDialog.eventFilter(self, watched, event) # 其他情况会返回系统默认的事件处理方法。
if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = EventFilter()
    dialog.show()
    sys.exit(app.exec_())


2.4、在QApplication中安装事件过滤器

这种方法比2.3、安装事件过滤器更强大,QApplication的事件过滤器将捕获所有QObject的所有事件,而且第一个获得该事件。也就是说,在将事件发送给其他任何一个事件过滤器之前(就是在第三种方法之前),都会先发送给QApplication的事件过滤器。


在2.3、安装事件过滤器示例基础上修改,屏蔽三个label标签控件的installEventFilter代码,这种事件处理方法确实过滤了所有事件,而不像第三种方法那样只过滤三个标签控件的事件。效果如下所示:


141ea2175bba4121b584a7aa420b2c4b.png


实现代码如下所示:


# -*- coding: utf-8 -*-
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class EventFilter(QDialog):
    def __init__(self, parent=None):
        super(EventFilter, self).__init__(parent)
        self.setWindowTitle("事件过滤器")
        self.label1 = QLabel("请点击")
        self.label2 = QLabel("请点击")
        self.label3 = QLabel("请点击")
        self.LabelState = QLabel("test")
        self.image1 = QImage("images/cartoon1.ico")
        self.image2 = QImage("images/cartoon1.ico")
        self.image3 = QImage("images/cartoon1.ico")
        self.width = 600
        self.height = 300
        self.resize(self.width, self.height)
        # self.label1.installEventFilter(self)
        # self.label2.installEventFilter(self)
        # self.label3.installEventFilter(self)
        mainLayout = QGridLayout(self)
        mainLayout.addWidget(self.label1, 500, 0)
        mainLayout.addWidget(self.label2, 500, 1)
        mainLayout.addWidget(self.label3, 500, 2)
        mainLayout.addWidget(self.LabelState, 600, 1)
        self.setLayout(mainLayout)
    def eventFilter(self, watched, event):
        print(type(watched))
        if watched == self.label1: # 只对label1的点击事件进行过滤,重写其行为,其他的事件会被忽略
            if event.type() == QEvent.MouseButtonPress: # 这里对鼠标按下事件进行过滤,重写其行为
                mouseEvent = QMouseEvent(event)
                if mouseEvent.buttons() == Qt.LeftButton:
                    self.LabelState.setText("按下鼠标左键")
                elif mouseEvent.buttons() == Qt.MidButton:
                    self.LabelState.setText("按下鼠标中间键")
                elif mouseEvent.buttons() == Qt.RightButton:
                    self.LabelState.setText("按下鼠标右键")
                '''转换图片大小'''
                transform = QTransform()
                transform.scale(0.5, 0.5)
                tmp = self.image1.transformed(transform)
                self.label1.setPixmap(QPixmap.fromImage(tmp))
            if event.type() == QEvent.MouseButtonRelease: # 这里对鼠标释放事件进行过滤,重写其行为
                self.LabelState.setText("释放鼠标按钮")
                self.label1.setPixmap(QPixmap.fromImage(self.image1))
        return QDialog.eventFilter(self, watched, event) # 其他情况会返回系统默认的事件处理方法。
if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = EventFilter()
    app.installEventFilter(dialog)
    dialog.show()
    sys.exit(app.exec_())


2.5、重新实现QApplication的notify()方法

PyQt使用notify()来分发事件,要想在任何事件处理器之前捕获事件,唯一的方法就是重新实现QApplication的notify(),在实践中,在调试时才会使用这种方法,实际中基本用不多,这里不再赘述了。

————————————————

版权声明:本文为CSDN博主「不脱发的程序猿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/m0_38106923/article/details/120935073

相关文章
|
1月前
|
测试技术 开发者 Python
Python(GUI)之活动积分记录表
本文介绍了一套使用Python的Tkinter库构建的学生活动积分记录系统。该系统允许教师选择班级和学生,输入加分分数及原因,并将数据保存至文件,旨在简化学生积分管理流程,提升教学效率。
47 6
|
1月前
|
UED 开发者 Python
Python中的异常处理机制
Python中的异常处理机制
40 2
|
1月前
|
数据可视化 开发者 Python
Python GUI开发:Tkinter与PyQt的实战应用与对比分析
【10月更文挑战第26天】本文介绍了Python中两种常用的GUI工具包——Tkinter和PyQt。Tkinter内置于Python标准库,适合初学者快速上手,提供基本的GUI组件和方法。PyQt基于Qt库,功能强大且灵活,适用于创建复杂的GUI应用程序。通过实战示例和对比分析,帮助开发者选择合适的工具包以满足项目需求。
119 7
|
2月前
|
存储 Windows
(13) Qt事件系统(two)
文章详细介绍了Qt事件系统,包括事件分发、自定义事件、事件传播机制、事件过滤以及事件与信号的区别。
111 3
(13) Qt事件系统(two)
|
1月前
|
监控 Java 开发者
Python的垃圾收集机制有哪些?
Python的垃圾收集机制有哪些?
|
2月前
|
机器学习/深度学习 数据采集 传感器
使用Python实现深度学习模型:智能极端天气事件预测
使用Python实现深度学习模型:智能极端天气事件预测
176 3
|
2月前
|
编解码 程序员
(12)Qt事件系统(one)
本文详细介绍了Qt事件系统,包括各种系统事件、鼠标事件、键盘事件、定时器等的处理方法和示例代码。
96 0
|
2月前
|
Linux Android开发 开发者
【Python】GUI:Kivy库环境安装与示例
这篇文章介绍了 Kivy 库的安装与使用示例。Kivy 是一个开源的 Python 库,支持多平台开发,适用于多点触控应用。文章详细说明了 Kivy 的主要特点、环境安装方法,并提供了两个示例:一个简单的 Hello World 应用和一个 BMI 计算器界面。
80 0
|
2月前
|
机器学习/深度学习 算法框架/工具 Python
基于深度学习的手写数字识别项目GUI(Deep Learning Project – Handwritten Digit Recognition using Python)
基于深度学习的手写数字识别项目GUI(Deep Learning Project – Handwritten Digit Recognition using Python)
108 0
|
2月前
|
文字识别 开发者 iOS开发
Python反爬机制-验证码(一)
Python反爬机制-验证码(一)
30 0

热门文章

最新文章