信号和槽用于对象之间的通信。
信号和槽机制是 Qt 的核心特性,可能也是与其他框架最不同的地方。Qt的元对象系统使信号和槽成为可能。
在 GUI 编程中,当我们更改一个 widget 时,我们通常希望通知另一个 widget。更普遍地说,我们希望任何类型的对象都能够相互通信。例如,如果用户单击关闭按钮,我们可能希望调用窗口的
close()函数。
其他框架使用回调实现这种通信,在 Qt 中,我们有回调技术的替代方法:
我们使用信号和槽,当特定事件发生时会发出信号。Qt 的widget有许多预定义的信号,但我们总是可以将widget子类化以向它们添加我们自己的信号。插槽是响应特定信号而调用的函数。Qt 的widget有许多预定义的插槽,但通常的做法是将widget子类化并添加您自己的插槽,以便您可以处理您感兴趣的信号。
详情应该去看官网文档,这里简单总结以下这些核心要点:
1. 信号和槽的使用依赖于QObject上下文,就是说必须在QObject或者其子类中以类成员变量的方式定义信号,其他地方定义的信号是无效的。信号与槽的连接和信号的发射就无所谓了,放在哪里都可以。
from PyQt5.QtCore import QObject, pyqtSignal
class ContextObject(QObject):
# 信号只能定义在这里
my_signal = pyqtSignal()
def my_solt():
print('i get the signal.')
if __name__ == "__main__":
context = ContextObject()
# 连接信号和槽
context.my_signal.connect(my_solt)
# 发射信息
context.my_signal.emit()
print('over')
"""
i get the signal.
over
"""
说明:
必须要实例化ContextObject,在代码中直接以ContextObject.my_signal.connect(my_solt)的方式连接信号和槽是无效的,因为这时没有上下文环境,my_signal还没有添加connect、emit等方法。
2. 信号的签名必须与槽的签名相匹配,槽的签名可以比信号的签名短,但不能比它长。使用emit发射信号时,参数的数量和类型必须与定义信号时的严格对应。
from PyQt5.QtCore import QObject, pyqtSignal
class ContextObject(QObject):
my_signal = pyqtSignal(str, int)
def my_solt1():
print('my_solt1')
def my_solt2(a):
print('my_solt2:', a)
def my_solt3(a, b):
print('my_solt3:', a, b)
def my_solt4(a, b, c):
print('my_solt4:', a, b, c)
if __name__ == "__main__":
context = ContextObject()
context.my_signal.connect(my_solt1) # my_solt1
context.my_signal.connect(my_solt2) # my_solt2: 2
context.my_signal.connect(my_solt3) # my_solt3: 2 3
context.my_signal.connect(my_solt4) # TypeError: my_solt4() missing 1 required positional argument: 'c'
context.my_signal.emit("2", 3)
context.my_signal.emit(2, 3) # TypeError: ContextObject.my_signal[str, int].emit(): argument 1 has unexpected type 'int'
print('over')
3. 一个信号可以连接任意多个槽,一个槽可以连接到任意多个信号上
from PyQt5.QtCore import QObject, pyqtSignal
class ContextObject(QObject):
my_signal1 = pyqtSignal(str, int)
my_signal2 = pyqtSignal(int)
def my_solt1():
print('my_solt1')
def my_solt2(a):
print('my_solt2:', a)
if __name__ == "__main__":
context = ContextObject()
context.my_signal1.connect(my_solt1)
context.my_signal1.connect(my_solt2)
context.my_signal2.connect(my_solt1)
context.my_signal2.connect(my_solt2)
print('my_signal1')
context.my_signal1.emit("2", 3)
print('my_signal2')
context.my_signal2.emit(5)
print('over')
"""
my_signal1
my_solt1
my_solt2: 2
my_signal2
my_solt1
my_solt2: 5
over
"""
4. 信号的发射与槽的调用是同步的,即只有所有槽都按顺序挨个调用完成之后,emit()之后的代码才会继续执行
5. 使用lambda表达式给无参数信号携带参数
class ContextQObject(QObject):
my_signal = pyqtSignal()
def func(num, count):
print(num, count)
if __name__ == '__main__':
my = ContextQObject()
a = 222
b = 333
my.my_signal.connect(lambda : func(a, b))
my.my_signal.emit()
输出:222 333
6. 使用functools下的partial给无参数信号携带参数
class ContextQObject(QObject):
my_signal = pyqtSignal()
def func(num, count):
print(num, count)
if __name__ == '__main__':
my = ContextQObject()
a = 222
b = 333
my.my_signal.connect(partial(func, a, b))
my.my_signal.emit()
输出:222 333
第5条和第6条一般用来扩展PyQt5的内置信号,因为很多内置信号都是未定义参数的,但有时候我们又有传输参数的需求。