PySide6/PyQT多线程之 信号与槽 / (Signal Slot)的高效利用

举报
frica01 发表于 2023/10/31 20:54:08 2023/10/31
【摘要】 在使用PySide6/PyQT时候,信号槽是一种事件处理方式,允许程序中的对象发送和接收信号,本文对信号和槽进行了详尽的介绍,信号和槽的使用也不过如此~

前言

PySide6/PyQT信号槽是一种事件处理方式,允许程序中的对象发送和接收信号。

PySide6/PyQT 精进的过程中,一定躲不开 信号和槽 这座大山,这是一个比较有意思的知识点:

  • 初接触的看不懂,觉得复杂;
  • 看得懂的又觉得简单。

简单和难另说,将 PySide6/PyQT信号和槽 讲的清楚,这个才是重点。




其实也挺简单,分三步走:

  1. 定义信号Signal
  2. 定义槽Slot
  3. 将信号和槽连接:Signal().connect.Slot()

本文给你捋清楚它的具体使用,让你轻松掌握这一知识点,跨过这座大山。


知识点📖📖

本文用到的几个PySide6的知识点及链接。

作用 链接
创建新线程 QThread
对象间通信的机制,允许对象发送和接收信号 Signal
用于响应Signal信号的方法 Slot

基础概念

PySide6/PyQT 中的信号和槽是一种对象间通信机制。

信号(Signal) 槽(Slot)
事件的发射者 对事件的响应
在对象中定义的方法,在特定事件发生时被触发 响应信号的函数,在信号发生时被调用

理解 信号和槽

用通俗易懂的话解释 PySide6/PyQT的信号和槽:

PySide6/PyQT信号和槽 机制简单地理解为一种 订阅机制

  • 信号充当了 发布者publisher)的角色,负责发布消息;
  • 槽扮演了 订阅者subscriber)的角色,用于接收消息并作出响应。
  • 槽可以订阅一个或多个信号,当信号发生时,槽会自动被调用执行。

信号 Signal

PySide6/PyQT中,可以使用 Signal 来定义信号。

信号 是一种特殊函数,可以在特定的情况下被 QObject 对象 发射(emit)

  • 下面代码定义了一个名为 my_signal 的信号,它接受一个整形参数(可以为空,也可以更换为其它类型
from PySide6.QtCore import (QThread, Signal)


class MyThread(QThread):
    my_signal = Signal(int)

槽 Slot

PySide6/PyQT中,可以使用 @Slot()装饰器 来定义

是普通的函数,它可以被信号调用。当一个信号被发射时,与之相连接的槽将被调用,并将信号的参数传递给槽。

  • 下面定义了一个名为 my_slot 的槽,它接受一个字符串参数,并打印接收到的数据
from PySide6.QtCore import (QThread, Slot)


class MyThread(QThread):
    
    @Slot(int)
    def my_slot(self, data):
        print("Received data: ", data)

值得注意是:

  • 这里的 Slot(int) 可以不写,但是写了可以提高代码可读性和执行效率。
  • 首先,标明这是一个槽函数;
  • 其次,在运行时动态地连接信号,在一定程度上提高代码的执行效率,因为在编译期间不需要生成额外的代码来连接信号和槽。

连接信号和槽

PySide6/PyQT 中,可以使用 connect() 方法将信号和槽相连接,如下所示:

  • 下面代码将sender对象的my_signal信号receiver对象的my_slot槽相连接。
  • sender对象发射my_signal信号时,receiver对象的my_slot槽将被调用。
sender = MyThread()
receiver = MyThread()

sender.my_signal.connect(receiver)

其实就是 使用 connect 将两者连接起来;


注意事项

重要的事情要强调三遍!



信号和槽只能在 QObject的子类里使用!!!

信号和槽只能在 QObject的子类里使用!!!

信号和槽只能在 QObject的子类里使用!!!


然而,在PySide6/PyQT 中,大部分常用的类都是继承了QObject类的,所以这个问题倒无需担心,只需知道有这一概念即可。


信号和槽必须是相同的参数类型。在定义信号和槽时,必须确保它们的参数类型和个数是相同的,否则无法正确连接和触发信号和槽。

信号和槽必须是相同的参数类型。在定义信号和槽时,必须确保它们的参数类型和个数是相同的,否则无法正确连接和触发信号和槽。

信号和槽必须是相同的参数类型。在定义信号和槽时,必须确保它们的参数类型和个数是相同的,否则无法正确连接和触发信号和槽。



常见异常

如果在非 QObject 的子类中使用 SignalSlot会遇到一些异常情况。这是因为在非 QObject 的子类中没有定义 Signalslot,也没有 QObject 提供的信号和槽管理机制。

在非 QObject 的子类中使用 SignalSlot,会遇到以下异常情况:

  • 定义 Signal 时会出现 NameError,因为 Signal是在 QObject 中定义的;
  • connect() 时会出现 AttributeError,因为 connect()QObject 的成员函数;
  • 在 信号emit() 时会出现 AttributeError,即属性错误,因为该类中没有该函数。

信号和槽 的基本使用

说完了 基础概念,现在上代码来检验学习成果了。

代码

# -*- coding: utf-8 -*-

import time


from PySide6.QtCore import (QThread, Signal, Slot, QSize)
from PySide6.QtWidgets import (QApplication, QPushButton, QLabel, QVBoxLayout, QWidget)


class MyThread(QThread):
    my_signal = Signal(int)
    finished_signal = Signal()

    def __init__(self, count):
        super().__init__()
        self.count: int = count

    def run(self):
        for idx in range(1, self.count + 1):
            time.sleep(1)
            # 任务进行时发出信号
            self.my_signal.emit(idx)
	    # 任务完成后发出信号
        self.finished_signal.emit()


class MainWindow(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.setup_ui()
        #
        self.button.clicked.connect(self.setup_thread)

    def setup_ui(self):
        self.setWindowTitle('demo')
        self.resize(QSize(250, 180))
        # 创建一个垂直布局
        layout = QVBoxLayout()
        # 创建一个标签
        self.label = QLabel('This is a label => ')
        layout.addWidget(self.label)
        # 创建一个按钮
        self.button = QPushButton('Send Request')
        layout.addWidget(self.button)
        # 将布局设置为主窗口的布局
        self.setLayout(layout)
        # 显示窗口
        self.show()

    def setup_thread(self):
        self.thread_ = MyThread(count=5)
        self.thread_.my_signal.connect(self.thread_progress)
        self.thread_.finished_signal.connect(self.thread_finished)
        self.thread_.start()

    @Slot(int)
    def thread_progress(self, item):
        self.label.setText('This is a label => ' + str(item))

    @Slot()
    def thread_finished(self):
        self.label.setText('This is a label finished.')


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()

MyThread

  • 继承了 QObject,创建了一个名为 MyThreadQThread 类;
  • 包含一个名为my_signalfinished_signal 的信号,它们分别在 任何进行时任务完成后 发射;
  • 注意 my_signal 是带参数的,而 finished_signal 不带参数,这个需要与 槽函数对应!!!
  • 循环执行 count 次数的操作,每次 通过 my_signal 发射,从而修改 应用程序中的 lable
  • 当循环完成后,通过 finished_signal 发射信号,修改 应用程序中的 label

MainWindow

  • 继承了 QWidget,实现了包含一个按钮和一个标签的简单窗口;
  • setup_thread 函数中,实例化了MyThread,并将实例化后的 my_signal 和 finished_signal 信号连接到 thread_progress 和 thread_finished 函数;
  • thread_progress 和 thread_finished为槽函数,当my_signal 和 finished_signal发出信号时,对应Slot(槽函数)将被调用,并修改label 的显示。

运行效果

后话

本次分享到此结束,
see you~🐱‍🏍🐱‍🏍

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。