欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

荐 《PyQt5高级编程实战》自定义信号详解

程序员文章站 2023-01-25 16:15:57
自定义信号详解PyQt5中各个控件自带的信号已经能够让我们完成许多需求,但是如果想要更加个性化的功能,我们还得通过自定义信号来实现。在本节,笔者会详细介绍如何来自定义一个信号,并通过该方法来实现窗口间的通信以及线程间通信。如果对信号的基础用法还不是很了解的小伙伴,可以先去阅读下《快速掌握PyQt5》第二章 信号与槽。1. 创建自定义信号下面是一个简单的自定义信号使用例子:import sysfrom PyQt5.QtCore import pyqtSignalfr......

自定义信号详解

1. 创建自定义信号

2. 让自定义信号携带值

3. 自定义信号的重载版本

4. 窗口间通信

5. 线程间通信


PyQt5中各个控件自带的信号已经能够让我们完成许多需求,但是如果想要更加个性化的功能,我们还得通过自定义信号来实现。在本节,笔者会详细介绍如何来自定义一个信号,并通过该方法来实现窗口间的通信以及线程间通信。

 

如果对信号的基础用法还不是很了解的读者,可以先去阅读下《快速掌握PyQt5》第二章 信号与槽

 

1. 创建自定义信号

下面是一个简单的自定义信号使用例子:

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    my_signal = pyqtSignal()

    def __init__(self):
        super(Demo, self).__init__()
        self.my_signal.connect(self.signal_slot)

    def signal_slot(self):
        print('信号发射成功')
        
    def mouseDoubleClickEvent(self, event):
        self.my_signal.emit()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

首先我们实例化一个pyqtSignal对象,然后将其连接到signal_slot槽函数上。每当用户在窗口上双击时,我们就调用emit方法将自定义信号发射出去,这样槽函数就会执行,打印出“信号发射成功”字符串。

 

运行截图如下:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

 

2. 让自定义信号携带值

但是如果我们想知道鼠标双击点的横坐标呢?可以通过信号一并发送过来吗?当然可以,请看下面这个例子:

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    my_signal = pyqtSignal(int)             # 1

    def __init__(self):
        super(Demo, self).__init__()
        self.my_signal.connect(self.signal_slot)

    def signal_slot(self, x):               # 2
        print('信号发射成功')
        print(x)

    def mouseDoubleClickEvent(self, event):
        pos_x = event.pos().x()             # 3
        self.my_signal.emit(pos_x)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 首先在实例化pyqtSignal对象时传入一个int参数,表明我们这个信号会携带一个整型值,而这个值将会被槽函数接收。

2. 给槽函数添加一个接收参数x,并在函数内部打印该值。

3. 在获取到横坐标后,通过emit方法发射出去就行了。

 

运行截图如下:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

 

那如果我们想把纵坐标也一并发送过来呢?请看下面这个例子:

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    my_signal = pyqtSignal(int, int)             # 1

    def __init__(self):
        super(Demo, self).__init__()
        self.my_signal.connect(self.signal_slot)

    def signal_slot(self, x, y):                 # 2
        print('信号发射成功')
        print(x)
        print(y)

    def mouseDoubleClickEvent(self, event):
        pos_x = event.pos().x()                  # 3
        pos_y = event.pos().y()
        self.my_signal.emit(pos_x, pos_y)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 在实例化pyqtSignal时加多一个int参数,表明这个信号会一共会携带两个整型值。

2. 修改槽函数接收参数数量。

3. 首先获取x和y坐标值,然后通过emit方法一并发射出去。

 

运行截图如下:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

 

除了int类型,我们还可以让自定义信号携带其他类型的值,包括python语言所支持的值类型和PyQt5自定义的数据类型。:

类型 实例化
整型 pyqtSignal(int)
浮点型 pyqtSignal(float)
复数 pyqtSignal(complex)
字符串 pyqtSignal(str)
布尔型 pyqtSignal(bool)
列表 pyqtSignal(list)
元组 pyqtSignal(tuple)
字典 pyqtSignal(dict)
集合 pyqtSignal(set)
QSize pyqtSignal(QSize)
QPoint pyqtSignal(QPoint)
... ...

使用PyQt5自定义的数据类型时,必须先导入相应的模块。

比如要携带QSize类型值,那就必须先进行导入:from PyQt5.QtCore import QSize

 

我们拿QPoint来举个例子:

import sys
from PyQt5.QtCore import pyqtSignal, QPoint
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    my_signal = pyqtSignal(QPoint)

    def __init__(self):
        super(Demo, self).__init__()
        self.my_signal.connect(self.signal_slot)

    def signal_slot(self, pos):
        print('信号发射成功')
        print(pos)

    def mouseDoubleClickEvent(self, event):
        pos = event.pos()
        self.my_signal.emit(pos)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

运行截图如下:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

 

3. 自定义信号的重载版本

假如用户提出了一个这样的需求,并且规定要用到自定义信号:当鼠标按下时,打印出横坐标;当鼠标释放时,打印出横坐标和纵坐标。当然,我们可以在鼠标按下和释放事件中直接打印出相关信息,没必要使用自定义信号。

 

读者可能会想到创建两个自定义信号,一个在鼠标按下事件中发射,一个在鼠标释放事件中发射。例子如下:

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    press_signal = pyqtSignal(int)
    release_signal = pyqtSignal(tuple)

    def __init__(self):
        super(Demo, self).__init__()
        self.press_signal.connect(self.press_slot)
        self.release_signal.connect(self.release_slot)

    def press_slot(self, x):
        print(x)

    def release_slot(self, pos):
        print(pos)

    def mousePressEvent(self, event):
        x = event.pos().x()
        self.press_signal.emit(x)

    def mouseReleaseEvent(self, event):
        pos_x = event.pos().x()
        pos_y = event.pos().y()
        pos = (pos_x, pos_y)
        self.release_signal.emit(pos)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

 

但其实我们也可以只用一个自定义信号来实现,借助信号重载方式就可以了:

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    mouse_signal = pyqtSignal([int], [tuple])               # 1

    def __init__(self):
        super(Demo, self).__init__()
        self.mouse_signal[int].connect(self.press_slot)     # 2
        self.mouse_signal[tuple].connect(self.release_slot)

    def press_slot(self, x):
        print(x)

    def release_slot(self, pos):
        print(pos)

    def mousePressEvent(self, event):                       # 3
        x = event.pos().x()
        self.mouse_signal[int].emit(x)

    def mouseReleaseEvent(self, event):
        pos_x = event.pos().x()
        pos_y = event.pos().y()
        pos = (pos_x, pos_y)
        self.mouse_signal[tuple].emit(pos)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 实例化一个pyqtSignal对象,并将参数用中括号[]包住,每一个中括号代表了该信号的一种形式,也就是说mouse_signal一共有两种形式,既可以携带一个整型值,也可以一个元组。笔者这里再举个例子:

# mouse_siganl既可以携带一个整型值,也可以同时携带一个整型值和一个字符串
mouse_signal = pyqtSignal([int], [int, str])

# mouse_signal可以携带一个整型值或一个浮点型数据或一个元组
mouse_signal = pyqtSignal([int], [float], [tuple])

注意不要将信号重载概念跟一个信号会携带两个值的概念混淆起来:

# my_signal有两种形式,一种是可以携带一个整型值,另一种是可以携带一个元组
mouse_signal = pyqtSignal([int], [tuple])

# my_signal只有一种形式,它携带一个整型值和一个元组
mouse_signal = pyqtSignal(int, tuple)

2. 将信号与槽函数进行连接,注意这里一定要明确所连接信号的重载类型。

3. 同样,在发射信号时,也要写清楚重载类型。

如果在连接和发射时不写清楚重载类型的话,则默认使用第一种。也就是说self.mouse_signal.connect(self.press_slot)相当于self.mouse_signal[int].connect(self.press_slot)

 

运行截图如下:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

 

4. 窗口间通信

当项目变得稍微复杂起来时,窗口间的通信需求就会出现。我们来看下如何通过自定义信号来实现这一通信功能。

import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QTextBrowser, QLineEdit, QPushButton, QHBoxLayout


class Window1(QTextBrowser):                   # 1
    def __init__(self):
        super(Window1, self).__init__()

    def show_msg_slot(self, msg):
        self.append(msg)


class Window2(QWidget):                         # 2
    win2_signal = pyqtSignal(str)

    def __init__(self):
        super(Window2, self).__init__()
        self.line = QLineEdit()
        self.send_btn = QPushButton('发送')
        self.send_btn.clicked.connect(self.send_to_win1_slot)

        h_layout = QHBoxLayout()
        h_layout.addWidget(self.line)
        h_layout.addWidget(self.send_btn)
        self.setLayout(h_layout)

    def send_to_win1_slot(self):
        msg = self.line.text()
        self.win2_signal.emit(msg)


if __name__ == '__main__':                      # 3
    app = QApplication(sys.argv)

    win1 = Window1()
    win1.show()

    win2 = Window2()
    win2.show()
    win2.win2_signal.connect(win1.show_msg_slot)

    sys.exit(app.exec_())

1. 窗口一继承于QTextBrowser,类中有一个槽函数,它会将信号带过来的值添加到窗口上。

2. 窗口2实例化了一个自定义信号,该信号会携带一个字符串。每当用户点击发送按钮后,窗口二会将输入框中的文本随信号一同发射出去。

3. 在程序入口处,我们实例化了窗口一和窗口二,并将窗口二的信号同窗口一的槽函数连接起来。也就是说,每当用户在窗口二点击了发送按钮后,窗口一就会将输入框的文本显示到自己的界面上。

 

运行截图如下:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

 

有些读者可能会说这个不用自定义信号也可以实现。确实,请看下面这个例子:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QTextBrowser, QLineEdit, QPushButton, QHBoxLayout


class Window1(QTextBrowser):
    def __init__(self):
        super(Window1, self).__init__()


class Window2(QWidget):
    def __init__(self, win1):                   # 1
        super(Window2, self).__init__()
        self.win1 = win1

        self.line = QLineEdit()
        self.send_btn = QPushButton('发送')
        self.send_btn.clicked.connect(self.send_to_win1_slot)

        h_layout = QHBoxLayout()
        h_layout.addWidget(self.line)
        h_layout.addWidget(self.send_btn)
        self.setLayout(h_layout)

    def send_to_win1_slot(self):
        msg = self.line.text()
        self.win1.append(msg)                   # 2


if __name__ == '__main__':
    app = QApplication(sys.argv)

    win1 = Window1()
    win1.show()

    win2 = Window2(win1)                        # 3
    win2.show()

    sys.exit(app.exec_())

1. 给窗口二的构造函数添加一个参数用于接收窗口一的实例。

2. 在窗口二的槽函数中直接调用窗口一示例的append方法添加输入框文本。

3. 在程序入口处将窗口一实例传给窗口二。

 

看起来好像还更加简洁点,但其实这样做会增加了代码的耦合度。如果项目变得庞大复杂起来,那么这样做无非是给后期代码维护埋下隐患。所以,笔者还是强烈建议使用自定义信号的方式来进行通信。

 

5. 线程间通信

在用PyQt5开发可视化爬虫软件时,我们会将爬取操作放在一个子线程中执行,主线程负责界面更新。当子线程中的爬虫运行结束时,应当通知主线程更新下相应的界面信息,好让用户知道爬取情况。下面我们通过自定义信号来实现这一通信操作:

import sys
import random
from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtWidgets import QApplication, QWidget, QTextBrowser, QPushButton, QVBoxLayout


class ChildThread(QThread):
    child_signal = pyqtSignal(str)      # 1

    def __init__(self):
        super(ChildThread, self).__init__()

    def run(self):                      # 2
        result = str(random.randint(1, 10000))
        for _ in range(100000000):
            pass

        self.child_signal.emit(result)


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.browser = QTextBrowser()       # 3
        self.btn = QPushButton('开始爬取')
        self.btn.clicked.connect(self.start_thread_slot)

        v_layout = QVBoxLayout()
        v_layout.addWidget(self.browser)
        v_layout.addWidget(self.btn)
        self.setLayout(v_layout)

        self.child_thread = ChildThread()   # 4
        self.child_thread.child_signal.connect(self.child_thread_done_slot)

    def start_thread_slot(self):
        self.browser.clear()
        self.browser.append('爬虫开启')
        self.btn.setText('正在爬取')
        self.btn.setEnabled(False)
        self.child_thread.start()

    def child_thread_done_slot(self, msg):
        self.browser.append(msg)
        self.browser.append('爬取结束')
        self.btn.setText('开始爬取')
        self.btn.setEnabled(True)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 在子线程中实例化一个自定义信号对象,该信号会携带一个字符串。

2. 当子线程运行完毕,会将result这个随机值随信号一同发射出去。

3. 在主窗口中实例化一个QTextBrowser控件和一个按钮控件,前者用于显示子线程传递过来的信息,后者用于开启子线程。

4. 实例化子线程,并将子线程的自定义信号与child_thread_done_slot槽函数连接起来。每当子线程运行结束,child_signal就会被发射,而child_thread_done_slot槽函数也就会启动。槽函数中的代码相信读者都可以看懂,笔者这里就不再赘述。

 

运行截图如下:

开启线程:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

线程运行中:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

线程运行结束:

荐
                                                        《PyQt5高级编程实战》自定义信号详解

本文地址:https://blog.csdn.net/La_vie_est_belle/article/details/107573344