Pyqt6&Pyside6 信号与槽详解

信号与槽

要实现控件功能的联动和交互操作,比如点击按钮后执行某个动作。对按钮功能的定义,是通过信号(signal)与槽(slot)机制实现的。信号与槽是PySide6编程的基础,也是Qt的一大创新,有了信号与槽的编程机制,在PySide6中处理界面上各个控件的交互操作时变得更加直观和简单。

信号:信号是指从QObject类继承的控件(窗口、按钮、文本框、列表框等)在某个动作下或状态发生改变时发出的一个指令或一个信息,例如一个按钮被单击(clicked)、右击一个窗口(customContextMenuRequested)、一个输入框中文字的改变(textChanged)等,当这些控件的状态发生变化或者外界对控件进行输入时,让这些控件发出一个信息,来通知系统其某种状态发生了变化或者得到了外界的输入,以便让系统对外界的输入进行响应。

槽:槽是系统对控件发出的信号进行的响应,或者产生的动作,通常用函数来定义系统的响应或动作,因此也叫做“槽函数”。例如对于单击“计算”按钮,按钮发出被单击的信号,然后编写对应的函数,当控件发出信号时,就会自动执行与信号关联的函数。

信号与槽的关系可以是一对一,也可以是多对多,即一个信号可以关联多个槽函数,一个槽函数也可以接收多个信号。PySide6已经为控件编写了一些信号和槽函数,使用前需要将信号和槽函数进行连接,另外用户还可以自定义信号和自定义槽函数。

内置信号与内置槽的连接

PySide6对控件已经定义的信号和槽可以在Qt Designer中查看。启动Qt Designer创建一个MainWindow.ui文件,在窗口上拖放一个新的Push Button按钮,并将objectName改成btnClose,将text设置成“关闭”。然后单击工具栏上的“编辑信号/槽”按钮,进入信号和槽的编辑界面,按住Shift键的同时,用鼠标左键拖拽“关闭”按钮到窗口的空白区,这时会出现一个红色线和接地符号,松开鼠标,弹出“配置连接”对话框。

img

勾选“显示从QWidget继承的信号和槽”,这时对话框的左边列表框中显示按钮的所有已定义信号,右边列表框中显示窗口所有的槽函数。这里左边选择按钮的clicked()信号,右边选择窗口的close()函数,单击OK按钮,就建立了按钮的单击信号(clicked)和窗口的关闭(close)的连接。

img

按ESC键退出信号/槽编辑状态。另外一种建立信号和槽的方法是使用“信号/槽编辑器”。在Qt Designer的右下角的“信号/槽编辑器”上单击+按钮,双击发送者下的<发送者>,找到btnClose按钮,双击信号下的<信号>,找到clicked(),双击接收者下的<接收者>,找到Form,双击槽下的<槽>,找到close(),这样就建立了信号和槽的连接。如果要删除信号和槽的连接,应先选中信号槽,然后单击-按钮。

img

将ui文件编译成py文件,打开生成的py文件,可以发现在py中增加了一行新代码self.btnClose.clicked.connect(Form.close),用控件信号的connect()方法将信号和函数进行了连接,注意被连接的槽函数不需要带括号。

self.signalName.connect(receiver.slotName)

添加一个main.py,添加如下代码,运行main.py:

from PySide6.QtWidgets import QApplication, QMainWindow
from Ui_MainWindow import Ui_MainWindow
import sys


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.show()


app = QApplication(sys.argv)
w = MainWindow()
app.exec()

img

当pushButton被点击,窗口被关闭。

内置信号与自定义槽函数的连接

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

将ui文件编译成py文件之后,可以看到py文件中出现下面的语句:

QMetaObject.connectSlotsByName(MainWindow)

这行代码的作用是使用PySide6的元对象QMetaObject在窗口上搜索所有从QObject类继承的控件,将控件的信号自动与槽函数根据名称objectName进行匹配,这是自定义的槽函数只要满足下面的格式,就能实现内置信号与自定义槽函数的关联:

@Slot()
def on_objectName_signalName(self, signalParameter):
    函数语句

@Slot()修饰符用于指定随后的函数是槽函数;
def为函数定义关键词;
on为函数名的前缀,必须要有;
objectName是控件的objectName属性名称,例如前面的“pushButton”按钮时给按钮设置的属性名称是btnClose;
signalName是控件的信号名称,如按钮的clicked信号;
signalParameter是信号传递过来的参数,例如一个checkBox是否处于勾选状态,对checkBox可以定义def on_checkBox_toggled(self,checked)自动关联槽函数,自动接收来自checkBox的toggled(bool)的信号,其中checked是形参,表示toggled(bool)信号传递的状态。

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
from PySide6.QtCore import QSize, Slot, QMetaObject
import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(QSize(400, 400))

        self.btn = QPushButton("点击后查看控制台")
        self.btn.setObjectName("maxBtn")  # 2.设置这个控件的objectName
        # maxBtn.clicked.connect(self.on_maxBtn_Clicked) # 手动关联槽函数

        self.setCentralWidget(self.btn)

        QMetaObject.connectSlotsByName(self)
        # 1.使用PySide6的元对象QMetaObject在窗口上搜索所有从QObject类继承的控件,
        # 将控件的信号自动与槽函数根据名称objectName进行匹配
        # 是必须的,否则自动关联无法生效

    @Slot()  # 3.槽参数类型修饰符
    def on_maxBtn_clicked(self):  # 4.自动关联槽函数
        print("自定义槽函数")


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()

首先,一定要设置好控件的setObjectName,然后要添加QMetaObject.connectSlotsByName(self),否则无法实现自动关联。然后用@Slot修饰自定义的槽函数。运行程序,点击按钮,控制台会打印“自定义槽函数”。

img

2.重载型信号的处理

在Qt Designer中查询一个控件的信号时,会发现有些控件有多个名字相同但是参数不同的信号。例如对于按钮有clicked()和clicked(bool)两种信号,一种不需要传递参数的信号,另一种传递布尔型参数的信号。这种信号名称相同、参数不同的信号称为重载(overload)型信号。对于重载型信号定义自动关联槽函数时,需要在槽函数前加修饰符@Slot(type)声明是对哪个信号定义槽函数,其中type是信号传递的参数类型。例如如果对按钮的clicked(bool)信号定义自动关联槽函数,需要在槽函数前加入@Slot(bool)进行修饰;如果对按钮的clicked()信号定义自动关联槽函数,需要在槽函数前加入@Slot()进行修饰。需要注意的是,在使用@Slot(type)修饰符前,应提前用from PySide6.QtCore import Slot语句导入槽函数。

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

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

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
from PySide6.QtCore import QSize, Slot, QMetaObject
import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(QSize(400, 400))

        self.btn = QPushButton("点击后查看控制台")
        self.btn.clicked.connect(self.btn_click) # 手动关联槽函数

        self.setCentralWidget(self.btn)

    def btn_click(self): 
        print("自定义槽函数")


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()

同样的使用connect也能实现相同的功能。

自定义信号

只有继承自QObject的类才可以定义信号。自定义信号可以不带参数,也可以带参数,可以带1个参数,也可以带多个参数。参数类型是任意的,如整数(int)、浮点数(float)、布尔(bool)、字符串(str)、列表(list)、元组(tuple)和字典(dict)等。参数类型需要在定义信号时进行声明。自定义信号通常需要在类属性位置用Signal类来创建,使用Signal前需要用from PySide6.QtCore import Signal语句导入Signal类。

1. 自定义信号的定义方式

定义非重载型信号:

signalName = Signal(type1, type2, ...)

定义重载型信号:

signalName = Signal((type1,), (type2,), ...)

signalName为信号名称;
Signal()用于创建信号实例对象,type为信号发送时附带的数据类型,这里数据类型不是形参也不是实参,只是类型的声明,参数类型任意,需根据实际情况确定,()表示重载信号,如果重载信号不带参数,则只使用(),不用带type。注意重载型信信号传入的参数是类型名构成的元组。

信号有连接connect()、发送emit()和断开disconnect()属性。对于重载型信号,在进行连接、发送和断开时要使用signalName[type]形式操作。第一个信号可以直接使用signalName的形式。

下面是创建不同信号的示例:

from PySide6.QtCore import QObject, Signal


class signalDefinition(QObject):  # 只有继承自QObject的类才可以定义信号
    signal_none = Signal()  # 无参数的信号
    signal_int = Signal(int)  # 带整数参数的信号
    signal_float = Signal(float)  # 带浮点数参数的信号
    signal_str = Signal(str)  # 带字符串参数的信号
    signal_list = Signal(list)  # 带列表参数的信号
    signal_dict = Signal(dict)  # 带字典参数的信号
    signal_int_float_str = Signal(int, float, str)  # 带整数、浮点数、字符串参数的信号
    signal_r2 = Signal((int,), (str,))  # 创建重载型信号,相当于创建了2个信号
    signal_r3 = Signal(
        (
            int,
            str,
        ),
        (str,),
        (list,),
    )  # 创建重载型信号,相当于创建了3个信号
    signal_r = Signal((), (bool))  # 创建重载型信号,一个不带参数,另一个带布尔型参数

    def __init__(self, parent=None):
        super().__init__(parent)
        self.signal_none.connect(self.slot_none)  # 信号与槽的连接
        self.signal_list.connect(self.slot_list)
        self.signal_r2[int].connect(
            self.slot_signal_r2_1
        )  # overload型信号的第一个信号可以不指定类型
        self.signal_r2[str].connect(
            self.slot_signal_r2_2
        )  # overload型信号的第一个信号可以不指定类型

        self.signal_none.emit()  # 发送信号
        self.signal_list.emit([1, 2, 3, 4])
        self.signal_r2[int].emit(1000)
        self.signal_r2[str].emit("信号重载Str")

    def slot_none(self):  # 自定义槽函数
        print("slot_none emit")

    def slot_list(self, list):
        for i in list:
            print(i)

    def slot_signal_r2_1(self, v: int):
        print(v)

    def slot_signal_r2_2(self, s: str):
        print(s)


signalDefinition()

运行结果:

slot_none emit
1
2
3
4
1000
信号重载Str
posted @ 2024-05-12 21:14  Apostle浩  阅读(382)  评论(1编辑  收藏  举报