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

相关文章
|
2月前
|
存储 安全 编译器
【Qt 底层机制之信号和槽 】深入探究Qt信号和槽背后的原理
【Qt 底层机制之信号和槽 】深入探究Qt信号和槽背后的原理
149 4
|
2月前
|
开发者 Python 容器
深入理解Python迭代器:迭代机制的核心与应用
本文介绍了Python迭代器的核心概念、工作原理和应用场景。迭代器是遍历容器类型数据结构(如列表、元组、字典和集合)的对象,遵循迭代器协议,具有记忆遍历位置和一次性特点。通过实现迭代器协议,开发者能为自定义类型定义迭代行为,实现高效处理大量数据和与其他迭代工具协同工作。迭代器与可迭代对象的区别在于,可迭代对象实现`__iter__()`方法,返回迭代器,而迭代器实现`__next__()`方法,用于逐个访问元素。理解并运用迭代器能提升Python代码的性能和可读性。
|
2月前
|
存储 开发框架 开发者
QT C++焦点事件:多角度解析实用技巧与方法
QT C++焦点事件:多角度解析实用技巧与方法
243 0
|
2月前
|
监控 算法 Java
如何确保Python的内存管理机制能够有效地工作?
【2月更文挑战第19天】【2月更文挑战第57篇】如何确保Python的内存管理机制能够有效地工作?
|
2月前
|
存储 Java Python
谈谈你对 Python 的内存管理机制的理解。
【2月更文挑战第19天】【2月更文挑战第55篇】谈谈你对 Python 的内存管理机制的理解。
|
2月前
|
存储 API C++
【Qt 信号槽】深入探索 Qt 信号和槽机制中的引用传递“ (“A Deep Dive into Reference Passing in Qt Signal and Slot Mechanism“)
【Qt 信号槽】深入探索 Qt 信号和槽机制中的引用传递“ (“A Deep Dive into Reference Passing in Qt Signal and Slot Mechanism“)
50 0
|
2月前
|
数据可视化 图形学 开发者
【Qt 底层机制之图形渲染引擎】深入理解 Qt 的 渲染机制:从基础渲染到高级图形
【Qt 底层机制之图形渲染引擎】深入理解 Qt 的 渲染机制:从基础渲染到高级图形
160 4
|
29天前
|
C++
qt事件过滤器的使用(可以用于控制屏幕背光等)
qt事件过滤器的使用(可以用于控制屏幕背光等)
5 0
|
29天前
|
Python
python使用tkinter库,封装操作excel为GUI程序
python使用tkinter库,封装操作excel为GUI程序
|
2月前
|
Java 程序员 测试技术
【Qt底层之内存管理机制】Qt 对象 父子关系、运行时机制与高效编程技巧
【Qt底层之内存管理机制】Qt 对象 父子关系、运行时机制与高效编程技巧
123 2

推荐镜像

更多