2.4 自定义信号的使用
2.4.1 信号与槽的一些特点和功能
在PyQt5中,信号与槽的使用有如下一些特点。
· 一个信号可以关联多个槽函数。
· 一个信号也可以关联其他信号。
· 信号的参数可以是任何Python数据类型。
· 一个槽函数可以和多个信号关联。
· 关联可以是直接的(同步)或排队的(异步)。
· 可以在不同线程之间建立关联。
· 信号与槽也可以断开关联。
2.3节的示例使用的信号都是类的内建信号,在自定义类中还可以自定义信号。使用自定义信号在程序的对象之间传递信息是非常方便的,例如在多窗体应用程序中,通过信号与槽在窗体之间传递数据。
使用PyQt5.QtCore.pyqtSignal()为一个类定义新的信号。要自定义信号,类必须是QObject类的子类。pyqtSignal()的句法是:
pyqtSignal(types[, name[, revision=0[, arguments=[]]]])
信号可以带有参数types,后面的参数都是一些可选项,基本不使用。
信号需要定义为类属性,这样定义的信号是未绑定(unbound)信号。当创建类的实例后,PyQt5会自动将类的实例与信号绑定,这样就生成了绑定的(bound)信号。这与Python语言从类的函数生成绑定的方法的机制是一样的。
一个绑定的信号(也就是类的实例对象的信号)具有connect()、disconnect()和emit()这3个函数,分别用于关联槽函数、断开与槽函数的关联、发射信号。
2.4.2 自定义信号使用示例
下面是示例Demo2_4目录下的程序human.py的完整代码,这个程序演示了自定义信号的使用,以及信号与槽的使用中一些功能的实现方法。
## 自定义信号与槽的演示 import sys from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal class Human(QObject): ##定义一个带str类型参数的信号 nameChanged = pyqtSignal(str) ## overload型信号有两种参数,一种是int,另一种是str ageChanged = pyqtSignal([int], [str]) def __init__(self, name='Mike', age=10, parent=None): super().__init__(parent) self.setAge(age) self.setName(name) def setAge(self, age): self.__age= age self.ageChanged.emit(self.__age) #发射int参数信号 if age<=18: ageInfo="你是 少年" elif (18< age <=35): ageInfo="你是 年轻人" elif (35< age <=55): ageInfo="你是 中年人" elif (55< age <=80): ageInfo="您是 老人" else: ageInfo="您是 寿星啊" self.ageChanged[str].emit(ageInfo) #发射str参数信号 def setName(self, name): self.__name = name self.nameChanged.emit(self.__name) class Responsor(QObject): @pyqtSlot(int) def do_ageChanged_int(self, age): print("你的年龄是:"+str(age)) @pyqtSlot(str) def do_ageChanged_str(self, ageInfo): print(ageInfo) ## @pyqtSlot(str) def do_nameChanged(self, name): print("Hello, "+name) if __name__ == "__main__": ##测试程序 print("**创建对象时**") boy=Human("Boy",16) resp=Responsor() boy.nameChanged.connect(resp.do_nameChanged) ## overload的信号,两个槽函数不能同名,关联时需要给信号加参数区分 boy.ageChanged.connect(resp.do_ageChanged_int) #默认参数,int型 boy.ageChanged[str].connect(resp.do_ageChanged_str) #str型参数 print("\n **建立关联后**") boy.setAge(35) #发射两个ageChanged信号 boy.setName("Jack") #发射nameChanged信号 boy.ageChanged[str].disconnect(resp.do_ageChanged_str) #断开关联 print("\n **断开ageChanged[str]的关联后**") boy.setAge(10) #发射两个ageChanged信号
(1)信号的定义
定义的类Human是从QObject继承而来的,它定义了两个信号,两个信号都需要定义为类的属性。nameChanged信号是带有一个str类型参数的信号,定义为:
nameChanged = pyqtSignal(str)
ageChanged信号是具有两种类型参数的overload型的信号,信号的参数类型可以是int,也可以是str。ageChanged信号定义为:
ageChanged = pyqtSignal([int], [str])
(2)信号的发射
通过信号的emit()函数发射信号。在类的某个状态发生变化,需要通知外部发生了这种变化时,发射相应的信号。如果信号关联了一个槽函数,就会执行槽函数,如果信号没有关联槽函数,就不会产生任何动作。
例如在Human.setName()函数中,当变量self.__name发生变化时发射nameChanged信号,并且传递参数,即
self.nameChanged.emit(self.__name)
变量self.__name作为信号的参数,关联的槽函数可以从参数中获得当前信号的名称,从而进行相应的处理。
Human.setAge()函数中发射了两次ageChanged信号,但是使用了不同的参数,分别是int型参数和str型参数,即
self.ageChanged.emit(self.__age) #int参数信号 self.ageChanged[str].emit(ageInfo) #str参数信号
(3)信号与槽的关联
另外定义的一个类Responsor也是从QObject继承而来的,它定义了三个函数,分别用于与Human类实例对象的信号建立关联。
因为信号ageChanged有两种参数类型,要与两种参数的ageChanged信号都建立关联,两个槽函数的名称必须不同,所以定义的两个槽函数名称分别是do_ageChanged_int和do_ageChanged_str。
需要在创建类的具体实例后再进行信号与槽的关联,所以,程序在测试部分先创建两个具体的对象。
boy=Human("Boy",16) resp=Responsor()
如果一个信号的名称是唯一的,即不是overload型信号,那么关联时无须列出信号的参数,例如,nameChanged信号的连接为:
boy.nameChanged.connect(resp.do_nameChanged)
对于overload型的信号,定义信号时的第一个位置的参数是默认参数。例如,ageChanged信号的定义是:
ageChanged = pyqtSignal([int], [str])
所以,ageChanged信号的默认参数就是int型。默认参数的信号关联无须标明参数类型,所以有:
boy.ageChanged.connect(resp.do_ageChanged_int) #默认参数,int型
但是,对于另外一个非默认参数,必须在信号关联时在信号中注明参数,即
boy.ageChanged[str].connect(resp.do_ageChanged_str) #str型参数
(4)@pyqtSlot修饰符的作用
在PyQt5中,任何一个函数都可以作为槽函数,但有时也需要使用@pyqtSlot修饰符说明函数的参数类型,以使信号与槽之间能正确关联。
@pyqtSlot()修饰符用于声明槽函数的参数类型,例如在2.3节的示例中,为了使函数on_chkBoxItalic_clicked(self, checked)与窗体上chkBoxItalic复选框的clicked(bool)信号自动建立关联,就使用了@pyqtSlot(bool)进行修饰。
在本例Responsor类的3个槽函数前的@pyqtSlot()修饰符都可以被注释掉,不影响程序的运行结果。因为overload型信号的两个槽函数名称不同,在建立关联时也指定了参数类型。
(5)断开信号与槽的关联
在程序中可以使用disconnect()函数断开信号与槽的关联,例如,程序中用下面的代码断开了一个关联。
boy.ageChanged[str].disconnect(resp.do_ageChanged_str) #断开关联
运行程序human.py,在Python Shell中显示如下的运行结果:
**创建对象时** **建立关联后** 你的年龄是:35 你是 年轻人 Hello, Jack **断开ageChanged[str]的关联后** 你的年龄是:10
从运行结果中可以看到:
· 创建对象时虽然也发射信号,但还未建立关联,所以无响应;
· 建立关联后,3个信号关联的槽函数都响应了;
· 断开关联后,断开关联的槽函数无响应了。
2.4.3 使用信号与槽的一些注意事项
通过这两节的示例讲解,信号与槽使用中涉及的一些用法基本都介绍了。信号与槽机制是非常好用的,特别是为GUI程序各对象之间的信息传递提供了很方便的处理方法,但是在PyQt5中使用信号与槽时也要注意以下问题。
(1)对于PyQt5中的类的内建overload型信号,一般只为其中一种信号编写槽函数。例如QCheckBox组件有clicked()和clicked(bool)两种信号,可以有针对性地只选择其中一种参数类型的信号编写槽函数。如果使用的overload型信号不是默认参数类型的信号,那么槽函数还需要使用@pyqtSlot()修饰符声明参数类型。
(2)在自定义信号时,尽量不要定义overload型信号。因为Python的某些类型转换为C++的类型时,对于C++来说可能是同一种类型的参数,例如,若定义一个overload型的信号:
valueChanged = pyqtSignal([dict], [list])
dict和list在Python中是不同的数据类型,但是转换为C++后可能就是相同的数据类型了,这可能会出现问题。