84-信号和槽- 信号与槽的简介

信号与槽的简介

信号

基本介绍

GUI 之间的通信

在GUI编程中,经常涉及控件之间通信的情况,如控件 B 依赖于控件 A,当控件A的参数发生变化时,通常希望控件 B 能够立刻知道这个情况,并做出相应的变化,这就是控件之间的相互通信。

一般的 GUI 框架使用回调实现这种通信。回调是指向函数的指针,因此,如果希望某个 func 可以及时通知某个事件,则可以在 func 中调用回调,这个回调指向另一个函数的指针。

尽管确实存在使用此方法的成功框架,但回调可能不直观,并且在确保回调参数的类型正确性(type-correctness)方面可能会遇到问题。

信号/槽机制

Qt使用了一种代替回调技术的方法,即信号/槽机制。

信号/槽机制的基本原理是当特定事件发生时发出信号,并传递给槽函数。

Qt 的小部件有许多预定义的信号,可以将小部件子类化并添加自定义信号。槽是响应特定信号而调用的函数,Qt 的小部件有许多预定义的插槽,但通常会对小部件子类化并添加自己的槽,以方便灵活处理感兴趣的信号。

信号/槽机制是类型安全的,Qt 的信号/槽机制可以确保如果将信号连接到槽,那么槽将在正确的时间接收信号的参数并且进行调用。信号/槽机制可以接收任意数量的任意类型的参数。

信号/槽是 Qt中的核心机制,也是在 PySide/PyQt 编程中对象之间进行通信的机制从QObject 或其子类之一(如 QWidget)继承的所有类都可以包含信号与槽。

当对象更改状态时,它会根据需要发射信号,而这个信号会被绑定的槽函数捕捉并执行结果,这就是Qt 中的通信机制。信号只负责发射,不关心是否有槽函数接收。同样,槽函数只用来接收信号,不知道是否有链接到它的信号发射,这体现了 Qt 通信机制的灵活性和独立性。

信号/槽机制的特点

PySide/PyQt的窗口控件中有很多内置信号,也可以添加自定义信号。信号/槽机制具有如下特点。

  • 一个信号可以连接多个槽,在发射信号时,插槽将按照它们连接的顺序一个接一个地执行。
  • 一个信号可以连接另一个信号(在发射第一个信号时立即发射第二个信号)。
  • 信号的参数可以是任意 Python 类型。
  • 信号永远不能有返回类型。
  • 一个槽可以监听多个信号。
  • 信号/槽机制完全独立于任何 GUI事件循环。
  • 信号与槽的连接方式既可以是同步的,也可以是异步的
  • 信号与槽的连接可能会跨线程。
  • 信号可能会断开。
两种通信机制之间的差别

与回调相比,信号/槽机制稍微慢一些,因为它提供了更高的灵活性,但是在实际的应用程序中两种机制的差异微不足道。

一般来说,发送连接到某些插槽的信号比直接调用接收器的性能差 10 倍。

这是定位连接对象、安全代所有连接(即检查后续接收器在发射期间没有被破坏),以及以通用方式编组任何参数所需的开销。

但是,考虑到字符串、向量、列表操作、新建实例或删除实例等操作,信号/槽机制的开销只占完整函数调用的很小一部分。信号/槽机制的简单性和灵活性非常值得这部分开销,这些开销甚至不会被注意到。

创建信号

Qt 提供了很多内置信号,如 QPushButton 的 clicked 信号、toggled 信号等,这些信号系统已经定义好,可以满足绝大多数需求。如果需要其他信号,则可以自己定义信号。使用 QtCoreSignal0函数可以创建信号,也可以为 QObject 及其子类(包括 QWidget 等)创建信号。

from PySide6.QtCore import Signal

__init__(self,
         *types: type,
         name: Union[str,NoneType] = None,
         arguments: Union[str,NoneType] = None)-> None

使用Signal()函数可以创建一个或多个重载的未绑定的信号作为类的属性。信号必须在创建类时定义,不能在创建类以后作为类的属性动态添加进来。

  • types 表示定义信号时参数的类型;
  • name表示信号的名字,该项在默认情况下使用类的属性的名字。

信号可以传递多个参数,并指定信号传递参数的类型,参数类型是标准的 Python 数据类型(如字符串、日期、布尔类型、数字、列表、元组和字典)。

一般的创建方式如下这是一个可以传递4 种参数(str、int、list、dict)的信号:

from PySide6.QtCore import Signal
from PySide6.QtWidgets import QWidget


class winForm(QWidget):
    # Signal(*types: type,name: Union[str,NoneType] = None,arguments: Union[str,NoneType] = None)
    btnClickedsignal = Signal(str,int,list,dict)

注意:PySide 6和PyQt 6对信号与槽的命名稍有不同,PySide6命名为Signal与slot,而在PyQt6中对应为pyqtSignal与 pyqtslot,它们只是名字不同而已,使用方式没有区别。
因此,可以将PyQt6代码和 PySide6代码尽量统一起来,减少后面的麻烦,对PyQt6代码可以尝试做如下修改:
from PyQt6.QtCore import pygtsignal as Signal
from PyQt6.QtCore import pyqtslot as slot
当然,对PySide6代码的修改也是一样的。

自定义重载信号:

signal1 = QtCore.Signal()# 无参数的信号
signal2 = QtCore.Signal(int)# 带一个参数(整数)的信号
signal3 = QtCore.Signal(int,str)# 带两个参数(整数,字符串)的信号
signal4 = QtCore.Signal(list)# 带一个参数(列表)的信号
signal5 = QtCore.Signal(dict)# 带一个参数(字典)的信号
signal6 = QtCore.Signal((int,str,),(str,))# 带(整数 字符串)或者(字符串)的信号. 部分教程讲的是用方括号pyside6会有问题,单个参数的逗号按照官方示例最好也加上

例子:

import sys

from PySide6.QtCore import Signal
from PySide6.QtGui import Qt
from PySide6.QtWidgets import QPushButton,QApplication,QWidget


class Btn(QPushButton):
	"""自定义按钮类,重写事件以实现自定义信号"""

    # 自定义信号
    rightClicked = Signal((str,),(int,))# 每次只发送一个str或int类型的数据,应用于重载等情况
    leftClicked = Signal((str,),(int,str,))# 可以传出多个参数

    def mousePressEvent(self,e)-> None:
        super().mousePressEvent(e)
        if e.button()== Qt.MouseButton.RightButton:  # 当按下的键是鼠标右键时
            self.rightClicked.emit("str1")# 发送信号,传递参数
            self.rightClicked[str].emit("str2")# 发送信号,指明了传递的参数为str类型
            self.rightClicked[int].emit(8881)# 发送信号,指明了传递的参数为int类型

        if e.button()== Qt.MouseButton.LeftButton:
            self.leftClicked.emit("str3")
            self.leftClicked[int,str].emit(8882,"str4")
            self.leftClicked[int,str].emit(8883,"str5")# 指明传递参数的数量和对应类型


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("信号")
        self.resize(500,500)
        self.setup_ui()

    def setup_ui(self):
        btn = Btn("测试按钮",self)
        btn.move(100,100)

        btn.rightClicked.connect(lambda content: print(f"右键点击1 {content}:{type(content)}"))# 没有指明需要的参数类型,默认取第一种
        btn.rightClicked[str].connect(lambda content: print(f"右键点击2 {content}:{type(content)}"))# 指明参数类型str
        btn.rightClicked[int].connect(lambda content: print(f"右键点击3 {content}:{type(content)}"))# 指明参数类型int

        # 传递多个参数
        btn.leftClicked.connect(lambda c1: print(f"左键点击1 {c1}:{type(c1)}"))# 默认传递
        btn.leftClicked[int,str].connect(lambda c1,c2: print(f"左键点击2 {c1}:{type(c1)},{c2}:{type(c2)}"))# 指定类型int,str
        btn.leftClicked[str].connect(lambda c1: print(f"左键点击3 {c1}:{type(c1)}"))# 默认传递  # 指定类型str


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

"""
左键点击1 str3:<class 'str'>
左键点击3 str3:<class 'str'>
左键点击2 8882:<class 'int'>,str4:<class 'str'>
左键点击2 8883:<class 'int'>,str5:<class 'str'>
右键点击1 str1:<class 'str'>
右键点击2 str1:<class 'str'>
右键点击1 str2:<class 'str'>
右键点击2 str2:<class 'str'>
右键点击3 8881:<class 'int'>
"""

可以发现走默认参数往往会同时触发指定参数类型的槽,重载型参数 实际运用建议还是指定类型触发 不要直接用默认。

操作信号

  • 使用 connect()函数可以把信号绑定到槽函数上。signaName[type].connect() 可用于连接重载型信号
  • 使用 disconnect()函数可以解除信号与槽函数的绑定。使用signaName.disconnect()断开重载型信号连接
  • 使用emit()函数可以发射信号。
    • 当使用自定义信号时,不仅需要手动触发信号,还需要用emit())函数。
    • 使用内置信号会自动触发,不需要执行emit0函数。

重载型信号连接

查询控件的信号时,会发现有些控件有多个名字相同但是参数不同的信号。

例如对于按钮有clicked()和 clicked(bool)两种信号,一种不需要传递参数的信号,另一种传递布尔型参数的信号。

这种信号名称相同、参数不同的信号称为重载(overload)型信号。

对于重载型信号定义自动关联槽函数时,需要在槽函数前加修饰符@slot(type)声明是对哪个信号定义槽函数,其中type是信号传递的参数类型。

  • 如果对按钮的clicked(bool)信号定义自动关联槽函数,需要在槽函数前加入@slot(bool)进行修饰;
  • 如果对按钮的clicked()信号定义自动关联槽函数,需要在槽函数前加人@slot()进行修饰。

需要注意的是,在使用@slot(type)修饰符前,应提前用from PySide6.QtCore import slot语句导人槽函数。

定义一个信号后就有连接connect()、发送emit()、断开disconnect()属性

重载型信号断开

已连接的信号非重载类型的直接使用signaName.disconnect()断开连接即可

重载类型的使用signaName[type].disconnect()断开连接

手动关联内置信号的自定义槽函数

除了使用控件内置信号定义自动连接的槽函数外,还可以将控件内置信号手动连接到其他函数上,这时需要用到信号的 connect()方法。

btnCalculate.clicked.connect(self.method)语句将按钮的单击信号clickedmethod()函数进行连接

也可以在主程序中,在消息循环语句前用myWindow.ui.btnCalculate.clicked.connect(myWindow.method)语句进行消息与槽函数的连接

槽函数

槽函数用来接收信号并执行相应的操作。槽函数可以是任何函数,也包括 lambda 表达式,主要作用是执行一些与信号匹配的操作。

可灵活使用lambda表达式链接一些自定义的信号和槽函数,尤其是在槽函数要求的参数和实际传递不一致时

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

士别三日,当挖目相待

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值