一、信号和槽的概念
Qt 的信号与槽(Signals and Slots)机制是一个用于对象间通信的核心特性。这个机制使得对象能以松散耦合的方式进行通信,从而提升了代码的模块化和可维护性。
1.1信号(Signal)
当特定条件满足或事件发生时,例如UI交互中的按钮被点击、选择触发事件、用户输入事件以及后台线程执行到触发点等等,对象会发出一个信号。信号本质上是一种特殊的函数声明,它不需要具体的实现,只负责通知外部此处有相应的信号产生了,告知大家。
1.2槽(Slot)
槽是用来接收信号的函数,既可以是普通函数,也可以是 Lambda 表达式。当一个信号被发射时,与其连接的槽函数就会被自动调用,从而完成相应的处理过程。
二、定义信号和槽
2.1 自定义信号的定义
除了标准UI控件所提供的信号外,我们可以自己定义所需的自定义信号。在PyQT/PySide中,信号的定义很简单,如下所示:
#PyQT信号的定义 #无参数信号 customSignal1 = pyqtSignal() #带一个整数参数的信号 customSignal2 = pyqtSignal(int) #带一个字符串参数的信号 customSignal3 = pyqtSignal(str) #带一个整数和一个字符串参数的信号 customSignal4 = pyqtSignal(int,str) #带一个列表参数的信号 customSignal5 = pyqtSignal(list) #带一个字典参数的信号 customSignal6 = pyqtSignal(dict) #带一个整数或者字符串的信号 customSignal7 = pyqtSignal([int],[str])
#PySide信号的定义 #无参数信号 customSignal1 = Signal() #带一个整数参数的信号 customSignal2 = Signal(int) #带一个字符串参数的信号 customSignal3 = Signal(str) #带一个整数和一个字符串参数的信号 customSignal4 = Signal(int,str) #带一个列表参数的信号 customSignal5 = Signal(list) #带一个字典参数的信号 customSignal6 = Signal(dict) #带一个整数或者字符串的信号 customSignal7 = Signal([int],[str])
信号定义中,参数的格式是不限制的。参数的类型可以是常规类型,也可以是自定义对象类型。以上列举了常规的定义方法。参数的数量、类型的不同组合,您可以根据实际需要定义。
2.2 槽的定义
槽就是一个普通的函数或lambda表达式。定义一个接收信号的槽的时候,只需要按照信号的定义,定义好形参即可。例如:定义customSignal6的槽,可以这样去编写:
def customSlot6(data:dict): pass
当然,如果槽是类中的成员函数,还需要加上self形参。例如:
def customSlot6(self,data:dict): pass
三、信号和槽的连接
3.1手动连接槽
手动连接槽可以通过connect函数进行连接,例如:将上述customSignal6信号连接到customSlot6槽上,如下所示:
[对象].customSignal6.connect(customSlot6)
3.2自动连接
自动连接,可以通过装饰器@pyqtSlot(PyQT装饰器)或者@Slot(PySide装饰器)来定义。例如:
#PyQT的定义方式 @pyqtSlot(dict) def on_[对象]_coustomSignal6(self,data:dict): pass
#PySide的定义方式 @Slot(dict) def on_[对象]_coustomSignal6(self,data:dict): pass
注意:函数的名称必须是“on_”开始,中间是对象的objectName,后面必须是对应的信号名称。
装饰器定义好以后还不够。要自动连接还必须调用:MetaObject.connectSlotsByName函数进行统一连接。connectSlotsByName函数传递的是当前需要绑定槽所在的对象。例如下面的例子:
class EmitObject(QObject): signal1 = pyqtSignal(int) .... class SlotObject(QObject): eo:EmitObject = None def __init__(self): self.eo = EmitObject() MetaObject.connectSlotsByName(self) #调用这一句,才会让对象的信号和槽进行自动连接 @pyqtSlot(int) def on_eo_signal1(self,data:int): pass
四、信号的发射
在对象内,需要发射信号的地方,调用信号的emit来发射信号。如下例所示:
class EmitObject(QObject): signal1 = pyqtSignal(int) def doSomething(self): .... self.signal1.emit(10) ....
五、信号和回调函数的区别
既然槽是一个函数,而信号发射的时候会自动去调用这个函数。这种方式就和我们常用的回调函数很类似,那他们的区别在哪里呢。
| 比对项目 | 回调函数 | 信号/槽 |
|---|---|---|
| 调用方式 | 直接调用形参名称并传入参数 | 使用信号的emit函数调用 |
| 触发数量 | 调用时只会调用单个回调函数的内容 | 通过绑定多个槽,一次emit触发多个槽执行 |
| 同步/异步 | 同步调用,阻塞调用位置知道回调函数执行完成 | 不会阻塞emit调用位置,属于异步操作 |
| 返回值 | 因为时同步调用,可以获取回调的返回值 | 不能够通过emit调用获取槽函数的返回值。 |
| 响应时间 | 同步实时相应 | 异步延迟不确定性 |
上述表格中,说到信号通过emit无法获取到返回值,那如何才能获取返回值呢?以下方法可以解决这个问题。
5.1 获取槽的返回值
from PySide2.QtCore import Slot,QMetaObject,QObject from dataclasses import dataclass # 自定义类封装返回结果 @dataclass class SlotResultObject: is_valid: bool message: str class MainTest(QObject): @Slot(str, result=SlotResultObject) #通过result定义返回内容 def get_bool_and_str_func(self, msg): return SlotResult(False, "return_message") if __name__ == "__main__": main_test = MainTest() result = QMetaObject.invokeMethod( #此处不是通过emit,而是通过invokeMethod调用 main_test, "get_bool_and_str_func", QtCore.Qt.DirectConnection, QtCore.Q_RETURN_ARG(SlotResultObject), QtCore.Q_ARG(str, "start2") ) print(result.is_valid, result.message)
当然,这种invokeMethod调用方式有些违背的信号和槽的逻辑。所以在此不建议大家使用。再非常特殊的情况下,可以参考以上的方式。
备案/许可证编号:

wishst