PyQt5教程 (结合designer使用)--- (五)Events and signals

在这里插入图片描述

Events不只由用户操作app来生成,还有其他方式,如网络连接、定时器等。
Application对象使用exec_方法后,程序进入主循环,主循环捕捉事件并发送到目标对象
在这里插入图片描述

事件模型中有三个参与者,事件源、事件、事件目标
Event source 将处理事件的任务委托(delegate)给 event target
在这里插入图片描述

Slot 插槽
一个slot可以是一个python的可执行对象,当它所连接的signal发生时会执行

信号与槽2

Pyqt开发过程中,主要的代码也就是实现槽函数的地方。
但是!!!不只可以发送自定义的signal,还可以发送自定义的信号!

如下例,自定义信号与槽函数

from PyQt5.QtCore import QObject,pyqtSignal

#信号对象
class QTypeSignal(QObject):
    #定义一个信号
    sendmsg=pyqtSignal(str,str)       #这里决定了发送信号的参数个数及类型!!!

def get(msg1,msg2 ):
        print("QSlot get msg => " + msg1+' '+msg2)  #这里是槽函数,任一个函数都可以成为槽函数,只要求参数对应发送源
if __name__ == '__main__':
    #实例化信号对象
    send=QTypeSignal()
    print('_____-把信号绑定到槽函数上_)___')
    send.sendmsg.connect(get)      #指定槽函数为get
    send.sendmsg.emit('第一参数','第二个参数')
    print('_____-把信号与槽函数解绑_)___')  
    send.sendmsg.disconnect(get)   #断开连接
send.sendmsg.emit('第一参数','第二个参数') 	#没有效果

可以看到,第一次执行完send.run()后执行了槽函数,输出了第二行字符串
断开后再执行send.run()就没效果了。
在这里插入图片描述

直接看代码感觉好像:定义信号对象好像没有什么意义。本质就是pyqtSignal对象调用emit方法进行发送,调用connect方法来绑定槽函数,使用disconnect来断开连接

但是下面代码

from PyQt5.QtCore import QObject,pyqtSignal
def get(msg1,msg2):
    print("QSlot get msg => " + msg1+' '+msg2)  #这里是槽函数,任一个函数都可以成为槽函数,只要求参数对应发送源
if __name__ == '__main__':
    signal=pyqtSignal(str,str)
    print('_____-把信号绑定到槽函数上_)___')
    signal.connect(get)      #指定槽函数为get
    signal.emit('第一参数','第二个参数')
    print('_____-把信号与槽函数解绑_)___')  
    signal.disconnect(get)   #断开连接
signal.emit('第一参数','第二个参数')              #再run就没有效果了

会报错
AttributeError: ‘PyQt5.QtCore.pyqtSignal’ object has no attribute ‘connect’
而调试发现前面的代码中的send.sendmsg是<class ‘PyQt5.QtCore.pyqtBoundSignal’>类型
查看文档可以看到pyqtBoundSignal拥有方法connect、disconnect、emit!而pyqtSignal并没有
在这里插入图片描述
在这里插入图片描述

查阅文档如下
Unbound and Bound Signals

A signal (specifically an unbound signal) is a class attribute. When a signal is referenced as an attribute of an instance of the class then PyQt5 automatically binds the instance to the signal in order to create a bound signal. This is the same mechanism that Python itself uses to create bound methods from class functions.
大意就是当signal对象作为一个类的属性时,会自动转换为Bound Signal,从而可以进行连接、发送等操作。
注意是类属性,不能是实例属性!!!
若是类定义改成如下,则仍然报相同的错误
class QTypeSignal(QObject):
#定义一个信号
def init(self):
QObject.init(self) #使用父类的初始化方法
self.sendmsg=pyqtSignal(str,str) #这里决定了发送信号的参数个数及类型!!!

另外一个要注意的地方就是必须要继承QObject类!否则又会报错如下

在这里插入图片描述

Objects created from a QObject can emit signals.

在这里插入图片描述
在这里插入图片描述

在GUI编程中,当改变一个控件的状态时(如单击了按钮),通常需要通知另一个控件,也就是实现了对象之间的通信。在早期的GUI编程中使用的是回调机制,在Qt中则使用一种新机制——信号与槽。
当事件或者状态发生改变时,就会发出信号。同时,信号会触发所有与这个事件(信号)相关的函数(槽)。信号与槽可以是多对多的关系。一个信号可以连接多个槽,一个槽也可以监听多个信号。
在这里插入图片描述
在这里插入图片描述

class MyWidget(QWidget):     #这里继承的是QWidget,间接继承了QObject
    # 无参数的信号
    Signal_NoParameters = pyqtSignal()     
    # 带一个参数(整数)的信号      
    Signal_OneParameter = pyqtSignal(int)         
    # 带一个参数(整数或者字符串)的重载版本的信号        
    Signal_OneParameter_Overload = pyqtSignal([int],[str])  
    # 带两个参数(整数,字符串)的信号      
        Signal_TwoParameters = pyqtSignal(int,str)    
    # 带两个参数([整数,整数]或者[整数,字符串])的重载版本的信号      
    Signal_TwoParameters_Overload = pyqtSignal([int,int],[int,str])

一个类可以定义多个信号,要发送哪个信号,就emit哪个

在这里插入图片描述

class MyWidget(QWidget):           #将信号与槽都定义在同一个类了
    def setValue_NoParameters(self):   
        '''无参数的槽函数'''  
        pass  

    def setValue_OneParameter(self,x):   
        '''带一个参数(整数)的槽函数'''  
        pass

def setValue_OneParameter_String(self,x):   
        '''带一个参数(字符串)的槽函数'''  
        pass 

    def setValue_TwoParameters(self,x,y):   
        '''带两个参数(整数,整数)的槽函数'''  
        pass  

    def setValue_TwoParameters_String(self,x,y):   
        '''带两个参数(整数,字符串)槽函数'''  
        Pass

因为python函数不对参数进行限定,因此,调用作为槽函数时,根据发送的信息类型决定使用哪个槽函数

在这里插入图片描述

# 连接无参数的信号
widget.Signal_NoParameters.connect(self.setValue_NoParameters )         #使用self,这些代码应该是写在类定义里面,这里的代码都只是展示写法,没法执行。                                 

# 连接带一个整数参数的信号
widget.Signal_OneParameter.connect(self.setValue_OneParameter)                                         

# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[int].
    connect(self.setValue_OneParameter)                              

# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[str].
    connect(self.setValue_OneParameter_String )                     

# 连接一个信号,它有两个整数参数
widget.Signal_TwoParameters.connect(self.setValue_TwoParameters )                                        

# 连接带两个参数(整数,整数)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,int].
    connect(self.setValue_TwoParameters )                      

# 连接带两个参数(整数,字符串)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,str].
    connect(self.setValue_TwoParameters_String )              
widget.show()

在这里插入图片描述

class MyWidget(QWidget):  

    def mousePressEvent(self, event):  
        # 发射无参数的信号
        self.Signal_NoParameters.emit() 
        # 发射带一个参数(整数)的信号
        self.Signal_OneParameter.emit(1) 
        # 发射带一个参数(整数)的重载版本的信号
        self.Signal_OneParameter_Overload.emit(1)
        # 发射带一个参数(字符串)的重载版本的信号
        self.Signal_OneParameter_Overload.emit("abc")
        # 发射带两个参数(整数,字符串)的信号
        self.Signal_TwoParameters.emit(1,"abc")
        # 发射带两个参数(整数,整数)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1,2)
        # 发射带两个参数(整数,字符串)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit (1,"abc")

如下例,可执行

from PyQt5.QtCore import QObject , pyqtSignal
class CustSignal(QObject):
    #声明无参数的信号
    signal1 = pyqtSignal()
    #声明带一个int类型参数的信号
    signal2 = pyqtSignal(int)
    #声明带int和str类型参数的信号
    signal3 = pyqtSignal(int,str)
    #声明带一个列表类型参数的信号
    signal4 = pyqtSignal(list)
    #声明带一个字典类型参数的信号
    signal5 = pyqtSignal(dict)
    #声明一个多重载版本的信号,包括带int和str类型参数的信号和带str类型参数的信号
    signal6 = pyqtSignal([int,str], [str])
    def __init__(self,parent=None):
        super(CustSignal,self).__init__(parent)
        #将信号连接到指定槽函数
        self.signal1.connect(self.signalCall1)
        self.signal2.connect(self.signalCall2)
        self.signal3.connect(self.signalCall3)
        self.signal4.connect(self.signalCall4)
        self.signal5.connect(self.signalCall5)
        self.signal6[int,str].connect(self.signalCall6)
        self.signal6[str].connect(self.signalCall6OverLoad)
        #发射信号
        self.signal1.emit()
        self.signal2.emit(1)
        self.signal3.emit(1,"text")
        self.signal4.emit([1,2,3,4])
        self.signal5.emit({"name":"wangwu","age":"25"})
        self.signal6[int,str].emit(1,"text")
        self.signal6[str].emit("text")
    def signalCall1(self):
        print("signal1 emit")
    def signalCall2(self,val):
        print("signal2 emit,value:",val)
    def signalCall3(self,val,text):
        print("signal3 emit,value:",val,text)
    def signalCall4(self,val):
        print("signal4 emit,value:",val)
    def signalCall5(self,val):
        print("signal5 emit,value:",val)
    def signalCall6(self,val,text):
        print("signal6 emit,value:",val,text)
    def signalCall6OverLoad(self,val):
        print("signal6 overload emit,value:",val)
if __name__ == '__main__':  
    custSignal = CustSignal()

在这里插入图片描述

在这里插入图片描述

有时在开发程序时经常会执行一些耗时的操作,这样就会导致界面卡顿,这也是多线程的应用范围之一——为了解决这个问题,我们可以创建多线程,使用主线程更新界面,使用子线程实时处理数据,最后将结果显示到界面上(子线程处理过程中,主线程还是可以更新界面)。
需要时再看

在这里插入图片描述
在这里插入图片描述

from PyQt5.QtCore import QObject , pyqtSignal

class SignalClass(QObject):
     # 声明无参数的信号
    signal1 = pyqtSignal()
    # 声明带一个int类型参数的信号
    signal2 = pyqtSignal(int)
    def __init__(self,parent=None):
        super(SignalClass,self).__init__(parent)
        # 将信号signal1连接到sin1Call和sin2Call这两个槽函数
        self.signal1.connect(self.sin1Call)
        self.signal1.connect(self.sin2Call)      #signal连接两个槽函数
        # 将信号signal2连接到信号signal1
        self.signal2.connect(self.signal1)      #连接不是对称的关系!!!
        # 发射信号
        self.signal1.emit()                     #输出两条信息,因为连接了两个槽函数
        self.signal2.emit(1)                   #signal2作为信号源连接了signal1,因此,触发signal2时会触发signal1,再次输出两条信息
        # 断开signal1、signal2信号与各槽函数的连接
        self.signal1.disconnect(self.sin1Call)
        self.signal1.disconnect(self.sin2Call)
        self.signal2.disconnect(self.signal1)
        # 将信号signal1和signal2连接到同一个槽函数sin1Call
        self.signal1.connect(self.sin1Call)
        self.signal2.connect(self.sin1Call)     #两个信号连接同一个槽函数
        # 再次发射信号
        self.signal1.emit()
        self.signal2.emit(1)
    def sin1Call(self):
        print("signal-1 emit")
    def sin2Call(self):
        print("signal-2 emit")
if __name__ == '__main__':  
    signal = SignalClass()

在这里插入图片描述

若是改 self.signal2.connect(self.signal1) 为 self.signal1.connect(self.signal2)
则报错
在这里插入图片描述

因此,连接不是对称的关系!!!参数作为槽(可以是函数或者是信号),调用者为signal。
槽为signal时,它接受的参数个数一定不能超过调用者的参数个数,不然就报错(sender跟receiver的argument不匹配)
再注意signal2发送了一个参数,但是它连接的槽函数sin2Call不接收参数,这也是可以的。只要不超过sender的参数个数就行。

signals and slots(**)

展示一个简单的例子吧
在这里插入图片描述
在这里插入图片描述

编辑信号与槽,slider的值改变时发送给display槽函数
在这里插入图片描述

这样就实现了,拖动slider时lcd实时展示值

reimplementing event handler(重定义事件方法)

前面的两小节就是通过重定义QWidget的事件方法来实现的
如点击窗口的X时不是立即关闭窗口(重定义closeEvent),右键单击控件时弹出context menu(重定义contextMenuEvent)

事件方法都属于QWidget
如下可见QWidget支持哪些事件
在这里插入图片描述在这里插入图片描述

这些方法有点像槽函数,它们都只接收一个参数,就是方法名字对应的事件
利用这些事件的属性来写代码,如closeEvent中利用了accept方法和ignore方法来接受/拒绝关闭,contextMenuEvent中利用了pos方法获取事件发生的坐标,下面keyPressEvent利用了事件中按了哪个按键

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication
class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Event handler')
        self.show()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_0:
            self.close()
def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())
if __name__ == '__main__':
main()

上面例子重定义方法 keyPressEvent 来实现按按键时的发生的事件,按0时就会关闭窗口
当然还可以重定义其他许多事件

event sender信号发送源

Sender方法是QObject类的,因此每个widget都有
返回的是该窗口内最后一次发送信号的widget,因此,同一个窗口内所有的widget的sender方法返回的都是同一个对象

import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication


class Example(QMainWindow):

    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        btn1 = QPushButton("Button 1", self)
        btn1.move(30, 50)
        btn2 = QPushButton("Button 2", self)
        btn2.move(150, 50)
        btn1.clicked.connect(self.buttonClicked)
        btn2.clicked.connect(self.buttonClicked)
        self.statusBar()

        self.setGeometry(300, 300, 450, 350)
        self.setWindowTitle('Event sender')
        self.show()
    def buttonClicked(self):
        sender = self.sender()
        self.statusBar().showMessage(sender.text() + ' was pressed')

def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())
if __name__ == '__main__':
    main()

在这里插入图片描述

点击按钮,使用sender方法来获知是哪个按钮触发了事件

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值