信号与槽是 Qt 中的一种事件处理机制,用于实现对象之间的通信;例如,按钮被点击,产生一个"被点击"的信号,然后执行某个事先连接的槽函数;

基本概念

  • 信号(Signal):事件发生的通知(如"按钮被点了"、"窗口大小被调整了")
  • 槽(Slot):响应信号的函数(如"退出程序"、"保存文件")
  • 连接(Connect):把信号和槽配对起来

连接信号与槽

信号与槽
程序运行效果

连接信号与槽函数的示例代码如下:

import sys
from PySide6.QtCore import Slot
from PySide6.QtWidgets import (
    QApplication, QWidget, QTextEdit, QPushButton, QVBoxLayout, QHBoxLayout
)

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("信号与槽示例")

        # 创建控件
        self.input = QTextEdit()
        self.btn_clear = QPushButton("清空")
        self.btn_add = QPushButton("添加内容")
        self.btn_exit = QPushButton("退出")

        # 布局设置
        layout = QVBoxLayout()
        layout.addWidget(self.input)

        btn_layout = QHBoxLayout()
        btn_layout.addWidget(self.btn_clear)
        btn_layout.addWidget(self.btn_add)
        btn_layout.addWidget(self.btn_exit)

        layout.addLayout(btn_layout)
        self.setLayout(layout)

        # 连接信号和槽
        self.btn_clear.clicked.connect(self.input.clear)
        self.btn_add.clicked.connect(self.add_text)
        self.btn_exit.clicked.connect(QApplication.quit)

    @Slot()
    def add_text(self):
        self.input.append("hello wrold!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec())

这个示例中,按钮btn_clear用于清空输入框中的内容,按钮btn_add用于向输入框中追加内容,按钮btn_exit用于退出程序;

第 32 ~ 34 行中,用于连接按钮的信号与槽函数;其中clicked是按钮预定义(内置)的信号,表示按钮被点击,connect()用于连接信号与槽函数,self.input.clear()是输入框预定义的槽函数,self.add_text()为自定义的槽函数,自定义槽函数可使用装饰器@Slot()使其更安全;

如果不加 @Slot 会怎样?

PySide6 会把它当作普通 Python 函数,依然能被连接;

但加了 @Slot 可以:

  • 提升性能
  • 增加类型安全
  • 避免内存泄漏

connect() 函数

在此信号和接收器之间建立连接;

connect(receiver[, type=Qt.AutoConnection])
  • receiver:可调用类型,可以是槽函数或信号;
  • type:信号与槽之间可使用的连接类型;它决定了特定信号是立即传送到槽,还是排队等待稍后传送;
连接类型 描述
Qt.AutoConnection 默认,如果接收者位于发出信号的线程中,使用Qt.DirectConnection;否则使用Qt.QueuedConnection
Qt.DirectConnection 信号发出时,槽函数会立即被调用;槽函数在信号线程中执行。
Qt.QueuedConnection 当控制权返回到接收者线程的事件循环时,调用槽函数;槽函数在接收者的线程中执行。
Qt.BlockingQueuedConnection Qt.QueuedConnection类似,但信号线程会阻塞,直到槽函数返回为止;如果接收方位于信号线程中,则不得使用此连接,否则应用程序将死锁。

断开连接

及时断开不再需要的连接,防止内存泄漏;

#断开与clicked信号连接的指定槽
button.clicked.disconnect(function)
#断开与clicked信号连接的所有槽
button.clicked.disconnect()

一个信号连接多个槽

一个信号可以与多个槽函数进行连接;

槽函数也可被多个信号同时连接;

def log():
    print("[LOG] clicked.")

button.clicked.connect(log)
button.clicked.connect(lambda: print("Do something!"))

带参数的信号

某些内置信号携带参数,这对于在组件之间传递信息非常有用。

例如QLineEdit控件,内容改变时textChanged(str)信号携带一个参数,槽函数接收参数的方法如下:

        # TODO
        # 输入框
        self.input = QLineEdit(self)
        self.label = QLabel("当前输入内容:", self)

        # 信号连接槽函数
        self.input.textChanged.connect(self.on_text_changed)
        #self.input.textChanged.connect(self.label.setText)

    @Slot(str)
    def on_text_changed(self, text: str):
        # 槽函数:将当前输入的文本显示在标签上
        self.label.setText(f"当前输入内容:{text}")

自定义信号

PySide6 可使用Signal类来定义一个信号,示例如下:

from PySide6.QtCore import QObject, Signal, Slot

class A(QObject):
    message = Signal(str)

class B(QObject):
    
    @Slot(str)
    def print_message(self, msg):
        print("Received:", msg)

a = A()
b = B()
a.message.connect(b.print_message)
a.message.emit("Hello!")

代码中,message就是自定义带参数的信号,通过connect连接到槽函数print_message,并通过emit方法发送信号;

自定义多个参数的信号:

    message = Signal(str,int)
    ...
    @Slot(str,int)
    def print_message(self, msg, value):
        print("Received:",msg,value)
...
a.message.emit("hello",123)

使用 @Slot 重载签名

你可以为同一个函数使用多个@Slot重载签名:

class Receiver(QObject):
    @Slot(str)
    @Slot(int)
    def handle(self, value):
        print("收到:", value)

这表示handle可以接收str类型或int类型的参数,Qt 会根据信号的参数类型自动匹配合适的重载版本。