PySide6从0开始学习的笔记(五) 信号与槽

信号与槽是 Qt 框架的核心机制,用于实现对象间的通信,是解耦界面组件交互、异步处理事件、实现前后端分离的关键。


一、核心概念

1. 信号(Signal)

  • 定义:对象在特定事件触发时发出的 "通知"(比如按钮被点击、输入框文本变化、自定义事件发生)。
  • 特性
    • 信号是QObject类的属性(QObject是pyside6的一个基础类),信号本身是由pyside6的 QtCore.Signal 类定义。信号的定义、存储、触发都依赖QObject类,脱离类的信号是无法定义和使用的,这点很重要。另外初学者常见的坑是将信号定义成了变量,同样造成信号无法使用。
    • 信号本身不包含逻辑,仅负责 "发送通知"。
    • 一个信号可以连接多个槽,一个槽也可以接收多个信号。

2. 槽(Slot)

  • 定义:接收信号并执行具体逻辑的函数 / 方法(可以是普通函数、类方法、lambda 表达式)。
  • 特性
    • 槽可以有参数(需与信号的参数类型匹配),也可以无参数。
    • 槽的执行时机由信号触发,而非主动调用(也可主动调用,没有别的影响)。
    • 通常将声明为@slot(),方便识别和特殊调用。

Slot的更多学习见:

https://blog.csdn.net/xulibo5828/article/details/155983164

3. 核心逻辑

  • 信号与槽通过 connect() 方法建立关联,当信号发出时,所有连接的槽会自动执行:

信号.emit(参数) → 触发所有连接的槽 → 槽函数执行


二、基础使用方法

1. 内置信号与槽(最常用)

Qt 内置组件(如 QPushButton、QLineEdit、QSlider)已预定义大量信号,直接连接即可使用。

示例 1:按钮点击触发槽函数
python 复制代码
import sys
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.num = 0

    def init_ui(self):
        # 设置窗口布局
        layout = QVBoxLayout()
        self.btn = QPushButton("点击我")
        layout.addWidget(self.btn)
        self.setLayout(layout)

        # 核心:连接信号与槽
        self.btn.clicked.connect(self.on_button_click)    # 当按钮被点击,按钮的内置clicked信号就会被发射,从而调用执行它绑定的自定义槽函数 on_button_click

    # 自定义槽函数
    def on_button_click(self):
        print("按钮被点击了!")
        self.num += 1
        self.btn.setText(f"已点击{self.num}次")

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

需要注意的是:一定不要把

some_signal.connect(some_slot),

写成:

some_signal.connect(some_slot())

比如上面代码,把连接语句写成:

python 复制代码
self.btn.clicked.connect(self.on_button_click())

就会报错。

就是说,信号与槽连接的本质是"传递槽函数引用"而不是连接执行槽函数后的返回值,这也是初学者常踩的坑。除非some_slot()的返回值是一个函数。

  • 另,如果槽函数只是执行临时的和简单的逻辑,也可以将信号连接到Lambda函数:
python 复制代码
# 核心:连接信号与槽
        self.btn.clicked.connect(lambda: self.btn.setText("已点击") )

示例 2:自定义信号(不带参数)
python 复制代码
import sys
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout

class MyWindow(QWidget):
    # 自定义信号:必须定义为类属性,可以指定参数类型(str, int 等)
    custom_signal = Signal()    # 定义一个不带类型参数的信号

    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()
        self.btn = QPushButton("发送自定义信号")
        self.label = QLabel("等待信号...")
        layout.addWidget(self.btn)
        layout.addWidget(self.label)
        self.setLayout(layout)


        # 1. 连接按钮点击信号到发送自定义信号的发射函数
        self.btn.clicked.connect(self.custom_signal.emit)
        # 2. 连接自定义信号到槽函数
        self.custom_signal.connect(lambda: self.label.setText("不带参数信号"))

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

关键说明:

  • clicked 是 QPushButton 的内置信号(点击事件)。
  • connect() 方法将信号与槽函数绑定。
  • 槽函数 on_button_click 无参数,匹配clicked 信号无参数版本(clicked 信号有两个版本,一个带有clicked(bool)参数,另一个没有参数)。
示例 3:自定义信号(带参数)
python 复制代码
import sys
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout

class MyWindow(QWidget):
    # 自定义信号:必须定义为类属性,指定参数类型(str, int 等)
    # 定义一个带 str 类型参数的信号
    custom_signal = Signal(str)

    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()
        self.btn = QPushButton("发送自定义信号")
        self.label = QLabel("等待信号...")
        layout.addWidget(self.btn)
        layout.addWidget(self.label)
        self.setLayout(layout)

        # 1. 连接自定义信号到槽函数
        self.custom_signal.connect(self.on_custom_signal)
        # 2. 连接按钮点击信号到发送自定义信号的函数
        self.btn.clicked.connect(self.send_custom_signal)

    def send_custom_signal(self):
        # 发送自定义信号,携带参数
        self.custom_signal.emit("自定义信号触发成功!")

    def on_custom_signal(self, msg):
        # 槽函数接收信号参数并处理
        self.label.setText(msg)

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

自定义信号的规则:

  • 信号必须定义在继承 QObject 的类中(PySide6 所有界面组件都继承 QObject)。
  • 定义格式:信号名 = Signal(参数类型1, 参数类型2, ...),支持的类型包括 int、str、float、bool、自定义类等。
  • 发送信号:self.信号名.emit(参数1, 参数2, ...),参数数量 / 类型必须与定义一致。
  • 如果信号有多个参数,槽函数在接收时可以选择接收一部分参数,或者是全部接收。

例子:

python 复制代码
import sys
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout

class MyWindow(QWidget):
    # 自定义信号:必须定义为类属性,指定参数类型(str, int 等)
    # 定义一个带 str 类型参数的信号
    custom_signal = Signal(int, int, str)

    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()
        self.btn = QPushButton("发送自定义信号")
        layout.addWidget(self.btn)
        self.setLayout(layout)

        # 1. 连接自定义信号到槽函数
        self.custom_signal.connect(self.on_custom_signal_1)   #  连接自定义信号到槽函数,一个信号可以连接到多个槽函数
        self.custom_signal.connect(self.on_custom_signal_2)
        self.custom_signal.connect(self.on_custom_signal_all)
        self.custom_signal.connect(self.on_custom_signal_none)
        # 2. 连接按钮点击信号到发送自定义信号的函数
        self.btn.clicked.connect(self.send_custom_signal)

    def send_custom_signal(self):
        # 发送自定义信号,携带参数
        self.custom_signal.emit(1, 2, "3")

    def on_custom_signal_1(self, one):    # 只接收信号的第一个参数
        # 槽函数接收信号参数并处理
        print(f"接收了信号参数:{one}")

    def on_custom_signal_2(self, one, two):    # 只接收信号的第一个和第二个参数
        # 槽函数接收信号参数并处理
        print(f"接收了信号参数:{one, two}")

    def on_custom_signal_all(self, *all):    # 接收信号的所有参数
        # 槽函数接收信号参数并处理        
        print(f"接收了信号参数:{all}")

    def on_custom_signal_none(self):    # 不接收信号的任何参数
        # 槽函数接收信号参数并处理
        print("未接收任何参数")



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

运行结果:

python 复制代码
接收了信号参数:1
接收了信号参数:(1, 2)
接收了信号参数:(1, 2, '3')
未接收任何参数

小结一下:

信号在发射时参数的数量和类型必须与定义一致,槽函数在接收时可以选择不接收任何参数或接收前几个参数或接收所有参数。


三、信号与槽的高级特性

1. 多信号连接同一槽
python 复制代码
import sys
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout

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

    def init_ui(self):
        layout = QVBoxLayout()
        self.btn1 = QPushButton("按钮1")
        self.btn1.setObjectName("btn1")
        self.btn2 = QPushButton("按钮2")
        self.btn2.setObjectName("btn2")
        self.label = QLabel("")
        layout.addWidget(self.btn1)
        layout.addWidget(self.btn2)
        layout.addWidget(self.label)
        self.setLayout(layout)

        # 1. 连接自定义信号到槽函数
        self.btn1.clicked.connect(self.btns_clicked)
        self.btn2.clicked.connect(self.btns_clicked)
    def btns_clicked(self):    # 接收信号的所有参数
        # 槽函数接收信号参数并处理
        self.label.setText(f"接收了{self.sender().text()}发送的信号")
        print(self.sender().objectName())


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec())
  • 知识点:槽函数在接收信号的时候是可以通过sender()函数获知发送者的身份的。
2. 一个信号连接多个槽
python 复制代码
import sys
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout

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

    def init_ui(self):
        layout = QVBoxLayout()
        self.btn1 = QPushButton("按钮1")
        self.label = QLabel("")
        layout.addWidget(self.btn1)
        layout.addWidget(self.label)
        self.setLayout(layout)

        # 1. 连接自定义信号到槽函数
        self.btn1.clicked.connect(self.slot1)
        self.btn1.clicked.connect(self.slot2)

    def slot1(self):
        # 槽函数接收信号参数并处理
        self.label.setText("按钮被点击1")

    def slot2(self):
        print("按钮被点击2")





if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec())
3. 跨线程信号与槽

PySide6 中主线程和子线程,通过信号与槽通信:

python 复制代码
import sys
import time
from PySide6.QtCore import QThread, Signal
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout

# 子线程类
class WorkThread(QThread):
    # 自定义信号:向主线程发送进度
    progress_signal = Signal(int)

    def run(self):
        # 耗时操作
        for i in range(1, 101):
            time.sleep(0.05)
            self.progress_signal.emit(i)  # 发送进度信号

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

    def init_ui(self):
        layout = QVBoxLayout()
        self.btn = QPushButton("开始耗时操作")
        self.label = QLabel("进度:0%")
        layout.addWidget(self.btn)
        layout.addWidget(self.label)
        self.setLayout(layout)

        self.btn.clicked.connect(self.start_work)

    def start_work(self):
        # 创建子线程
        self.thread = WorkThread()
        # 连接子线程信号到主线程槽
        self.thread.progress_signal.connect(self.update_progress)
        # 启动线程
        self.thread.start()
        # 禁用按钮防止重复点击
        self.btn.setEnabled(False)

    def update_progress(self, progress):
        self.label.setText(f"进度:{progress}%")
        if progress == 100:
            self.btn.setEnabled(True)
            self.label.setText("操作完成!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec())
4. 断开连接

使用 disconnect() 解除信号与槽的关联,适用于动态控制的场景:

python 复制代码
# 断开单个连接

self.btn.clicked.disconnect(self.on_click)

# 断开所有连接

self.btn.clicked.disconnect()

四、常见问题与注意事项

1. 信号定义位置错误

  • 错误:将信号定义在 init 方法内定义成了变量(信号必须是类属性,需要在 __init__之前定义)。
  • 正确:直接定义在类体中(如 class MyWindow(QWidget): custom_signal = Signal(str))。

2. 槽函数参数不匹配

  • 报错:TypeError: slot_function() takes 1 positional argument but 2 were given。
  • 解决:确保槽函数参数数量 ≤ 信号参数数量,且类型匹配。

3. 多次连接导致槽重复执行

  • 问题:重复调用 connect() 会导致一个信号触发多次槽。
  • 解决:
    1. 连接前先 disconnect():self.signal.disconnect()(清空所有连接)。
    2. 确保只连接一次(如在 init 中连接)。

4. 子线程对象被提前销毁

  • 问题:子线程对象作为局部变量被销毁,导致信号无法发送。
  • 解决:将子线程对象设为实例属性(如 self.thread = WorkThread())。

5. 循环引用导致内存泄漏

  • 问题:信号与槽的循环引用(如 A 连接 B 的信号,B 又连接 A 的信号)。
  • 解决:
    1. 使用 QtCore.QObject.destroyed 信号清理连接。
    2. 手动 disconnect() 不再需要的连接。

6. 使用QMetaObject.connectSlotsByName()自动连接槽函数,见:

https://blog.csdn.net/xulibo5828/article/details/155712173


信号与槽的总结

PySide6 信号与槽的核心价值是解耦对象通信,关键要点:

  1. 信号是 "通知",槽是 "处理逻辑",通过 connect() 绑定。
  2. 内置组件自带常用信号,自定义信号需继承 QObject 并通过 Signal 定义。
  3. 支持多信号连一槽、一信号连多槽,跨线程通信需通过信号(子线程不直接操作 UI)。
  4. 注意参数匹配、避免重复连接、防止对象提前销毁。

掌握信号与槽是 PySide6 开发的核心,无论是简单的按钮点击,还是复杂的异步任务处理,都依赖这一机制实现灵活的交互逻辑。


学习阶段总结

至此,我们已经掌握了pyside6的基础应用框架:

  • 在可显示的窗口内将容器类控件设置为中心部件,交互类和展示类控件通过布局管理器布置在容器类控件中,设置合适的尺寸策略以构建健壮、自适应、可维护的图形界面。
  • pyside6自带的主窗口部件QMainWindow,内置了完善的标题栏、工具栏和菜单系统。
  • 对象之间通过信号与槽通信,信号槽机制完美实现了对象间解耦、线程间解耦、前后端分离。信号与槽函数之间用connect()函数连接,信号使用emit()函数发射,信号发射后,调用连接的槽函数,以刷新显示和实现程序其他功能。
相关推荐
刘孬孬沉迷学习3 小时前
GTP协议
开发语言·学习·5g·php·信息与通信
不夜牛仔3 小时前
算法笔记19 - 图和通用结构 | 图的两种遍历 | 三种拓扑排序 | 两种最小生成树算法Kruskal, Prim | 最短路径算法Dijkstra
笔记·算法
淼淼7634 小时前
Qt工具栏+图页,图元支持粘贴复制,撤销,剪切,移动,删除
开发语言·c++·windows·qt
Kelvin_Ngan4 小时前
Qt包含QtCharts/QValueAxis时编译报错
开发语言·qt
炽烈小老头4 小时前
【每天学习一点算法2025/12/16】二叉树的最大深度
学习·算法
葱卤山猪4 小时前
【Qt】心跳检测与粘包处理:打造稳定可靠的TCP Socket通信
开发语言·数据库·qt
白云千载尽4 小时前
基础命令学习之ps 与 pkill 与 nohup 与 2>&1 &
服务器·学习·远程
世转神风-4 小时前
qt-lambda信号槽机制
开发语言·qt
Lv11770084 小时前
Visual Studio 中的字符串
ide·笔记·c#·visual studio