06. 信号与槽
一、信号与槽
信号(signal)与 槽(slot)是 Qt 的核心机制,也是进行 PySide6 编程时,对象与对象之间通信的基础。在 PySide6 中,每一个 QObject 对象(包括各种窗口控件)都支持信号与槽机制。通过信号与槽的关联,就可以实现对象之间的通信。
信号 是指从 QObject 类继承的控件(窗口、按钮、文本框、列表框等)在某个动作下或状态发生改变时发出的一个指令或一个信息。槽 是系统对控件发出的信号进行的响应,或者产生的动作,通常用函数来定义系统的响应或动作。当信号发射时,连接的 槽函数(方法)将会自动执行。在 PySide6 中,信号与槽是通过对象的 signal.connect() 方法进行连接的。信号与槽的关系可以是一对一,也可以是多对多,即一个信号可以关联多个槽函数,一个槽函数也可以接收多个信号。
PySide6 中信号与槽的主要特点如下:
- 一个信号可以连接多个槽。
- 一个槽可以监听多个信号。
- 信号与信号之间可以互联。
- 信号与槽的连接可以跨线程。
- 信号与槽的连接即可以是同步的,也可以是异步的。
- 信号的参数可以是任何的 Python 类型。
我们可以在终端中使用 pip 安装 PySide6 模块。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载。
pip install pyside6 -i https://mirrors.aliyun.com/pypi/simple
国内常用的 pip 下载源列表:
- 阿里云 https://mirrors.aliyun.com/pypi/simple
- 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple
- 中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple
二、自动关联内置信号与自定义槽函数
在设计好 UI 界面之后,我们可以使用 PySide6 的元对象(QMetaObject)在窗口上搜索所有从 QObject 类继承的控件,将控件的信号自动与槽函数根据根据控件的 ObjectName 属性名进行匹配。
from PySide6.QtCore import QMetaObject
QMetaObject.connectSlotsByName(o: PySide6.QtCore.QObject, /) -> None
这时,自定义的槽函数必须具有如下格式,即可以实现信号与槽函数的自动关联。
from PySide6.QtCore import Slot
@Slot()
def on_objectName_signalName([signalParameter]):
pass
其中,@Slot() 修饰符用于 指定随后的函数是槽函数。on 为 自定义的槽函数的前缀,objectName 是 控件的 objectName 属性名称,singalName 是 控件的信号名称,signalParameter 是 信号传递过来的参数。
我们新建一个 ui.py 文件,用来存放 UI 代码。
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QCheckBox
class MyUi:
def setupUi(self, window:QWidget):
window.resize(800, 600) # 1.设置窗口对象大小
checkBox = QCheckBox(window) # 2.创建复选框控件
checkBox.setObjectName("checkBox") # 3.设置复选框控件的ObjectName属性
checkBox.setText("复选框") # 4.设置复选框控件的显示文本
checkBox.move(10, 10) # 5.设置复选框控件的位置
我们再新建一个 widget.py 文件,用来存放业务逻辑代码。
import sys
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtCore import QMetaObject, Slot
from ui import MyUi
class MyWidget(QWidget):
def __init__(self):
super().__init__() # 1.调用父类Qwidget类的__init__()方法
self.__ui = MyUi()
self.__ui.setupUi(self) # 2.初始化页面
QMetaObject.connectSlotsByName(self) # 3.自动通过控件的ObjectName属性关联信号和槽
@Slot()
def on_checkBox_clicked(self):
print("你点击了复选框")
if __name__ == "__main__":
app = QApplication(sys.argv) # 1.创建一个QApplication类的实例
window = MyWidget() # 2.创建一个窗口
window.show() # 3.显示窗口
sys.exit(app.exec()) # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
在 PySide6 中,有些控件有多个名字相同但是参数不同的信号。例如对于复选框控件有 clicked() 和 clicked(clicked:bool) 两种信号,一种不需要传递参数的信号,另一种传递布尔型参数的信号。这些信号名称相同、参数不同的信号称为重载型信号。
对于 重载信号定义自动关联槽函数 时,需要在槽函数前加修饰符 @Slot(type) 声明是对哪个信号定义槽函数,其中 type 是 信号传递的参数类型。
import sys
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtCore import QMetaObject, Slot
from ui import MyUi
class MyWidget(QWidget):
def __init__(self):
super().__init__() # 1.调用父类Qwidget类的__init__()方法
self.__ui = MyUi()
self.__ui.setupUi(self) # 2.初始化页面
QMetaObject.connectSlotsByName(self) # 3.自动通过控件的ObjectName属性关联信号和槽
@Slot(bool)
def on_checkBox_clicked(self, clicked:bool):
if clicked:
print("你勾选了复选框")
else:
print("你取消勾选了复选框")
if __name__ == "__main__":
app = QApplication(sys.argv) # 1.创建一个QApplication类的实例
window = MyWidget() # 2.创建一个窗口
window.show() # 3.显示窗口
sys.exit(app.exec()) # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
三、手动关联内置信号与自定义槽函数
我们可以使用 connect() 方法将控件信号手动连接到自定义的槽函数上。
触发信号的控件名.触发信号名.connect(接收信号的控件的槽函数名)
在信号和槽连接时,我们必须往
connect()方法中传入一个可调用对象,也就是传入函数名,不带括号。如果带了括号,就表示我们传入了函数的返回值。
我们修改 ui.py 文件的内容。
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QCheckBox
class MyUi:
def setupUi(self, window:QWidget):
window.resize(800, 600) # 1.设置窗口对象大小
self.checkBox = QCheckBox(window) # 2.创建复选框控件
self.checkBox.setObjectName("checkBox") # 3.设置复选框控件的ObjectName属性
self.checkBox.setText("复选框") # 4.设置复选框控件的显示文本
self.checkBox.move(10, 10) # 5.设置复选框控件的位置
我们修改 widget.py 文件的内容。
import sys
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtCore import Slot
from ui import MyUi
class MyWidget(QWidget):
def __init__(self):
super().__init__() # 1.调用父类Qwidget类的__init__()方法
self.__ui = MyUi()
self.__ui.setupUi(self) # 2.初始化页面
self.__ui.checkBox.clicked.connect(self.checkBox_clicked_slot) # 3.关联信号和槽
# 手动关联内置信号与自定义槽函数,可以不加@Slot装饰器
@Slot(bool)
def checkBox_clicked_slot(self, clicked:bool):
if clicked:
print("你勾选了复选框")
else:
print("你取消勾选了复选框")
if __name__ == "__main__":
app = QApplication(sys.argv) # 1.创建一个QApplication类的实例
window = MyWidget() # 2.创建一个窗口
window.show() # 3.显示窗口
sys.exit(app.exec()) # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
如果信号无法传值,而我们想要让它连接一个带参数的槽函数,这时我们可以使用 lambda 匿名函数。
我们修改 widget.py 文件的内容。
import sys
from PySide6.QtWidgets import QApplication, QWidget
from ui import MyUi
class MyWidget(QWidget):
def __init__(self):
super().__init__() # 1.调用父类Qwidget类的__init__()方法
self.__ui = MyUi()
self.__ui.setupUi(self) # 2.初始化页面
# 3.关联信号和槽
self.__ui.checkBox.clicked.connect(lambda clicked: self.checkBox_clicked_slot(clicked, self.__ui.checkBox.text()))
def checkBox_clicked_slot(self, clicked:bool, text:str):
if clicked:
print(f"你勾选了【{text}】复选框")
else:
print(f"你取消勾选了【{text}】复选框")
if __name__ == "__main__":
app = QApplication(sys.argv) # 1.创建一个QApplication类的实例
window = MyWidget() # 2.创建一个窗口
window.show() # 3.显示窗口
sys.exit(app.exec()) # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
四、自定义信号
除了可以用控件的内置信号外,还可以自定义信号。自定义信号 可以不带参数,也可以带参数,可以带 1 个参数,也可以带多个参数。参数类型是任意的,参数类型需要在定义信号时进行声明。自定义信号 通常需要在 类属性 位置用 Signal 类来创建,使用 Signal 前需要用 from PySide6.QtCore import Signal 语句导入 Signal 类。需要注意的是,只有继承自 QObject 的类才可以定义信号。
# 定义非重载型信号
signName = Signal(type1, type2, ...)
其中,signalName 为 信号名称;Signal() 用于 创建信号实例对象,type 为 信号发送时附带的数据类型,这里数据类型不是形参也不是实参,只是类型的声明,参数类型任意,需根据实际情况确定。
定义一个信号后,信号就有 连接 connect()、发送 emit() 和 断开 disconnect() 属性,需要注意的是,只有从 QObject 继承的类才可以定义信号。
from PySide6.QtCore import QObject, Signal
class SignalDefinition(QObject):
s1 = Signal() # 创建无参数的信号
s2 = Signal(int) # 创建带整数的信号
s3 = Signal(float) # 创建带浮点数的信号
s4 = Signal(str) # 创建带字符串的信号
s5 = Signal(int, float, str) # 创建带整数、浮点数和字符串的信号
s6 = Signal(list) # 创建带列表的信号
s7 = Signal(dict) # 创建带字典的信号
s8 = Signal((int,), (str,)) # 创建重载信号
s9 = Signal((int, str), (str,), (list,))
s10 = Signal((), (bool,))
def __init__(self, parent=None):
super().__init__(parent)
# 信号与槽的连接
self.s1.connect(self.slot1)
self.s2.connect(self.slot2)
self.s3.connect(self.slot3)
self.s4.connect(self.slot4)
self.s5.connect(self.slot5)
self.s6.connect(self.slot6)
self.s7.connect(self.slot7)
self.s8[int].connect(self.slot8)
self.s8[str].connect(self.slot8)
self.s9[int, str].connect(self.slot9_1)
self.s9[str].connect(self.slot9_2)
self.s9[list].connect(self.slot9_3)
self.s10.connect(self.slot10_1)
self.s10[bool].connect(self.slot10_2)
# 提交信号
self.s1.emit()
self.s2.emit(10)
self.s3.emit(11.11)
self.s4.emit("你好,世界!")
self.s5.emit(10, 11.11, "你好,世界!")
self.s6.emit([10, 11.11, "你好,世界!"])
self.s7.emit({"1": "Sakura", "2": "Shana"})
self.s8[int].emit(10)
self.s8[str].emit("Sakura")
self.s9[int, str].emit(10, "Sakura")
self.s9[str].emit("Mikoto")
self.s9[list].emit([10, 11.11, "你好,世界!"])
self.s10.emit()
self.s10[bool].emit(True)
def slot1(self):
print("s1 emit", end="\n\n")
def slot2(self, value:int):
print("s2 emit int: ", value, end="\n\n")
def slot3(self, value:float):
print("s3 emit float: ", value, end="\n\n")
def slot4(self, string:str):
print("s4 emit string: ", string, end="\n\n")
def slot5(self, value1:int, value2:float, string:str):
print("s5 emit many values: ", value1, value2, string, end="\n\n")
def slot6(self, list_value:list):
print("s6 emit list: ", list_value, end="\n\n")
def slot7(self, dict_value:dict):
print("s7 emit dict: ", dict_value, end="\n\n")
def slot8(self, value):
print("s8 parameter type: ", type(value))
print("s8 emit value: ", value, end="\n\n")
def slot9_1(self, value:int, string:str):
print("s9_1 emit int: ", value)
print("s9_1 emit string: ", string, end="\n\n")
def slot9_2(self, value:str):
print("s9_2 emit strint: ", value, end="\n\n")
def slot9_3(self, list_value:list):
print("s9_3 emit list: ", list_value, end="\n\n")
def slot10_1(self):
print("s10 emit", end="\n\n")
def slot10_2(self, flag:bool):
print("s10 emit flag: ", flag, end="\n\n")
if __name__ == "__main__":
signalTest = SignalDefinition()

浙公网安备 33010602011771号