PyQt5信号与槽(四)事件处理机制入门

四、事件处理机制入门

  • 信号与槽只能解决窗口控件的某些特定行为,如果要对窗口控件做更深层次的研究,如自定义窗口等,则需要使用低级的事件处理机制。

1、事件和信号与槽的区别

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

2、常见事件类型

  • PyQt是对Qt的封装,Qt程序是事件驱动的,它的每个动作都由幕后某个事件所触发。Qt事件的类型有很多,常见的Qt事件如下。
    • 键盘事件:按键按下和松开。
    • 鼠标事件:用鼠标进行拖放。
    • 滚轮事件:鼠标滚轮滚动。
    • 绘屏事件:重绘屏幕的某些部分
    • 定时事件:定时器到时。
    • 焦点事件:键盘焦点移动。
    • 进入和离开事件:鼠标指针移入Widget内,或者移出。
    • 移动事件:Widget的位置改变。
    • 大小改变事件:Widget的大小改变。
    • 显示和隐藏事件:Widget显示和隐藏。
    • 窗口事件:窗口是否为当前窗口。
  • 还有一些常见的Qt事件,比如Socket事件、剪贴板事件、字体改变事件、布局改变事件等。

3、使用事件处理的方法

  • PyQt5提供了如下5种事件处理和过滤方法(由弱到强),其中只有前两种方法使用最频繁。
(1)重新实现事件函数
  • 比如mousePressEvent()、keyPressEvent()、painEvent()。这是最常规的事件处理方法。
(2)重新实现QObject.event()
  • 一般用在 PyQt没有提供该事件的处理函数的情况下,即增加新事件时。
(3)安装事件过滤器
  • 如果对QObject 调用installEventFilter,则相当于为这个 QObject 安装了一个事件过滤器,对于QObject的全部事件来说,它们都会先传递到事件过滤函数eventFilter中,在这个函数中我们可以抛弃或者修改这些事件,比如可以对自己感兴趣的事件使用自定义的事件处理机制,对其他事件使用默认的事件处理机制。由于这种万法会对调用installEventFilter 的所有QObject的事件进行过滤,因此如果要更过滤的事件比较多,则会降低程序的性能。
(4)在QApplication中安装事件过滤器
  • 这种方法比上一种方法更强大:QApplication的事件过滤器将捕获所有 QObject的所有事件,而且第一个获得该事件。也就是说,在将事件发送给其他任何一个事件过滤器之前(就是在第三种方法之前),都会先发送给QApplication的事件过滤器。
(5)重新实现QApplication的notify()方法
  • PyQt使用notify()来分发事件。要想在任何事件处理器之前捕获事件,唯一的方法就是重新实现QApplication的notify()。在实践中,在调试时才会使用这种方法。

4、经典案例分析

(1)前两种事件处理案例
	# -*- coding:utf-8 -*-
	"""
	    # @Time:2022/12/12 0012 11:10
	    # @Author:晚秋拾叶
	    # @File:qt07_eventRetranslate.py
	    # @PyCharm之Python
	"""
	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):
	        super().__init__()
	        self.justDoubleClicked = False
	        self.key = ""
	        self.text = ""
	        self.message = ""
	        self.resize(400, 300)
	        self.move(100, 100)
	        self.setWindowTitle("Events")
	        # 避免受窗口大小重绘事件的影响,可以把参数0改变成5000(5秒),然后在运行,就可以明白这行代码的意思。
	        # 原作者没说,singleShot其实就是定时器
	        QTimer.singleShot(3000, self.giveHelp)
	
	    '''1.请点击这里触发追踪鼠标功能'''
	
	    def giveHelp(self):
	        self.text = "1. 请点击这里触发追踪鼠标功能"
	        self.update()  # 重绘事件,也就是触发paintEvent函数。
	
	    '''2.重新实现关闭事件'''
	
	    def closeEvent(self, event):
	        print("Closed")
	
	    '''3.重新实现上下文菜单事件'''
	
	    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("&Three")
	            threeAction.triggered.connect(self.three)
	        menu.exec_(event.globalPos())
	
	    '''4.上下文菜单槽函数'''
	
	    def one(self):
	        self.message = "2.1.Menu option One"
	        self.update()
	
	    def two(self):
	        self.message = "2.2.Menu option Two"
	        self.update()
	
	    def three(self):
	        self.message = "2.3 Menu option Three"
	        self.update()
	
	    '''5.清空消息文本的槽函数'''
	    def clearMessage(self):
	        self.message = ""
	
	    '''6.重新实现调整窗口大小事件'''
	
	    def resizeEvent(self, event):
	        self.text = "4. 调整窗口大小为: QSize({0}, {1})".format(
	            event.size().width(), event.size().height())
	        self.update()
	
	    '''7.重新实现鼠标释放事件'''
	
	    def mouseReleaseEvent(self, event):
	        # 若鼠标释放为双击释放,则不跟踪鼠标移动
	        # 若鼠标释放为单击释放,则需要改变跟踪功能的状态,如果开启跟踪功能的话就跟踪,不开启跟踪功能就不跟踪
	        if self.justDoubleClicked:
	            self.justDoubleClicked = False
	        else:
	            self.setMouseTracking(not self.hasMouseTracking())  # 单击鼠标
	            if self.hasMouseTracking():
	                self.text = "7. 开启鼠标跟踪功能.\n" + \
	                            "请移动一下鼠标!\n" + \
	                            "单击鼠标可以关闭这个功能"
	            else:
	                self.text = "6. 关闭鼠标跟踪功能.\n" + \
	                            "单击鼠标可以开启这个功能"
	            self.update()
	
	    '''8.重新实现鼠标移动事件'''
	
	    def mouseMoveEvent(self, event):
	        if not self.justDoubleClicked:
	            globalPos = self.mapToGlobal(event.pos())  # 窗口坐标转换为屏幕坐标
	            self.text = """5. 鼠标位置:
	            窗口坐标为:QPoint({0}, {1}) 
	            屏幕坐标为:QPoint({2}, {3}) """.format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y())
	            self.update()
	
	    '''9.重新实现鼠标双击事件'''
	
	    def mouseDoubleClickEvent(self, event):
	        self.justDoubleClicked = True
	        self.text = "8. 你双击了鼠标"
	        self.update()
	
	    '''10.重新实现键盘按下事件'''
	
	    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)
	
	    '''11.重新实现其他事件,适用于PyQt没有提供该事件的处理函数的情况,
	    Tab键由于涉及焦点切换,不会传递给keyPressEvent,因此,需要在这里重新定义。'''
	
	    def event(self, event):
	        if (event.type() == QEvent.KeyPress and
	                event.key() == Qt.Key_Tab):
	            self.key = "9. 在event()中捕获Tab键"
	            self.update()
	            return True
	        return QWidget.event(self, event)
	
	    '''12.重新实现绘制事件'''
	
	    def paintEvent(self, event):
	        text = self.text
	        i = text.find("\n\n")
	        if i >= 0:
	            text = text[0:i]
	        if self.key:  # 若触发了键盘按钮,则在文本信息中记录这个按钮信息。
	            text += "\n\n3. 你按下了: {0}".format(self.key)
	        painter = QPainter(self)
	        painter.setRenderHint(QPainter.TextAntialiasing)
	        # 绘制信息文本的内容
	        painter.drawText(self.rect(), Qt.AlignCenter, text)
	
	        # 若消息文本存在则在底部居中绘制消息,5秒钟后清空消息文本并重绘。
	        if self.message:	            
	            painter.drawText(self.rect(), Qt.AlignBottom|Qt.AlignHCenter, self.message)	            
	            QTimer.singleShot(5000, self.clearMessage)
	            QTimer.singleShot(5000, self.update)
	
	
	if __name__ == "__main__":
	    app = QApplication(sys.argv)
	    form = Widget()
	    form.show()
	    app.exec_()
  • 运行情况
点击提示上下文菜单按键提示窗口大小
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
跟踪鼠标坐标关闭鼠标跟踪开启鼠标跟踪双击鼠标
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
Tab键功能键菜单标签提示关闭退出
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
  • 代码分析
    • 这个程序比较有意思。首先建一个窗口类控件,然后建立两个变量text和mesage,再使用paintEvent函数输出到窗口中。
    • 整个类方法中,几乎都用了一个update函数,目的就是更新窗口。由于窗口更新过程中会触发一次painEvent函数(此函数是窗口基类QWidget的内部函数),因此在本例中update函数的作用等同于paintEvent函数。
    • 接着是重新实现窗口关闭事件与上下文菜单事件。对于上下文菜单事件,主要影响message变量的结果,paintEvent负责把这个变量在窗口底部输出。
    • 绘制事件是代码的核心事件,它的主要作用是时刻跟踪text与message这两个变量的信息,并把text的内容绘制到窗口的中部,把message的内容绘制到窗口的底部(保持5秒后就会消失)。
    • 在上下文菜单实现上,把Three加了条件判断,结合singleShot()计时器函数,产生了程序中的效果:在One和Two的message还在的时候,右击菜单没有Three。
    • 接下来是重新实现调整窗口大小事件和鼠标释放事件,窗口大小通过拖曳触发。鼠标双击锁定,鼠标坐标事件发生,直到鼠标单击。鼠标单击产生绑定和释放的两种情况,改变跟踪功能的的状态,要么坐标数据跟随光标,要么屏幕无变化,直到再次单击后再跟踪。
    • 以上和程序里实现键盘按下等事件,都是第一种“重新实现事件函数”的方法。
    • 第二种事件处理方法是重载event函数。对于窗口所有事件都会传递给event函数,event函数会根据事件的类型,把事件分配给不同的函数进行处理。比如对于绘图事件,event会交给paintEvent函数处理;对于键盘按下事件,event会交给keyPressEvent函数处理。有一种特殊情况是对Tab键的触发行为,event函数对Tab键的处理机制是把焦点从当前窗口控件的位置切换到Tab键次序中下一个窗口控件的位置,并返回True,而不是交给keyPressEvent函数处理。因此这里需要在event函数中对按下Tab键的处理逻辑重新改写,使它与普通的键没什么不同。
(2)第三种情况举例
# -*- coding:utf-8 -*-
"""
    # @Time:2022/12/12 0012 21:53
    # @Author:晚秋拾叶
    # @File:qt07_enent_filter.py
    # @PyCharm之Python
"""
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/cartoon2.ico")
        self.image3 = QImage("images/cartoon3.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_())			
  • 运行效果
按下鼠标以前按下鼠标以后
在这里插入图片描述在这里插入图片描述
  • 代码分析
    • 这个程序的效果就是按下鼠标键,就会对label1装载的图片进行缩放(长宽各缩一半)。
    • 对于使用事件过滤器,关键是要做好两步
      • (1)对要过滤的控件设置installEventFilter。经过设置的控件都会被后面eventFilter函数接收并处理。
      • (2)在eventFilter函数中处理这些控件的事件信息。在本例中,用if条件语句过滤其他两个图标,不让它们显示。
(3)第四种事件处理方法
  • 就是在QApplication中安装事件过滤器,与第三种相比,只是简单修改两处代码。

  • 屏蔽三个label标签控件的installEventFilter代码:

    # self.label1.installEventFilter(self)
    # self.label2.installEventFilter(self)
    # self.label3.installEventFilter(self)
    
  • 对于在QApplication中安装installEventFilter,下面代码的意思是Dialog控件中的所有事件都要经过eventFilter函数处理,而不仅仅是三个标签控件的事件。

    if __name__ == '__main__':
        app = QApplication(sys.argv)
        dialog = EventFilter()
        app.installEventFilter(dialog)
        dialog.show()
        sys.exit(app.exec_())
    
  • 为了更好地展示第四种事件处理方法与第三种的区别,这里在eventFilter函数中添加了一行代码:

    def eventFliter(self, watched, event):
    	print(type(watched))	# 这是要加的那一行代码
    

    运行结果如下图
    在这里插入图片描述

  • 可见,第四种事件处理方法和第三种结果一样,但更简单。第五种事件处理方法基本用不到,就不用写了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山哥ol

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值