PyQt5信号与槽(三)高级玩法

三、PyQt5信号与槽高级玩法

1、高级自定义信号与槽

  • 自定义信号的一般流程:
    • 定义信号
    • 定义槽函数
    • 连接信号与槽函数
    • 发射信号
(1)定义信号
import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import pyqtSignal


class MyWidget(QWidget):
    # 无参信号
    Signal_NoParameters = pyqtSignal()
    # 带一个参数(整数)的信号
    Signal_OneParameter = pyqtSignal(int)
    # 带一个参数(整数或者字符串)的重载版本的信号
    Signal_OneParameter_Overload = pyqtSignal([int], [str])
    # 带两个参数(整数。字符串)的信号
    Signal_TwoParameters = pyqtSignal(int, str)
    # # 带两个参数([整数,整数]或者[整数,字符串])的重载版本的信号
    Signal_TwoParameters_Overload = pyqtSignal([int, int], [int, str])
(2)定义槽函数
  • 定义槽函数,它有多个不同的输入参数
    def setValue_NoParameters(self):
        """无参槽函数"""
        print("""无参槽函数""")

    def setValue_OneParameter(self, nIndex):
        """带一个参数(整数)的槽函数"""
        print("""带一个参数(整数)的槽函数""", nIndex)

    def setValue_OneParameter_String(self, snIndex):
        """带一个参数(字符串)的槽函数"""
        print("""带一个参数(字符串)的槽函数""", snIndex)

    def setValue_TwoParameters(self, x, y):
        """带两个参数(整数,整数)的槽函数"""
        print("""带两个参数(整数,整数)的槽函数""", x, y)

    def setValue_TwoParameters_String(self, x, szY):
        """带两个参数(整数,字符串)的槽函数"""
        print("""带两个参数(整数,字符串)的槽函数""", x, szY)

(3)连接信号与槽函数
		# 连接无参信号
        self.Signal_NoParameters.connect(self.setValue_NoParameters)

        # 连接带一个整数参数的信号
        self.Signal_OneParameter.connect(self.setValue_OneParameter)

        # 连接带一个整数参数,经过重载的信号
        self.Signal_OneParameter_Overload.connect(self.setValue_OneParameter_String)

        # 连接一个信号,有两个整数参数
        self.Signal_TwoParameters.connect(self.setValue_TwoParameters)

        # 连接带两个参数(整数,整数)的重载版本的信号
        self.Signal_TwoParameters_Overload[int, int].connect(self.setValue_TwoParameters)

        # 连接带两个参数(整数,字符串)的重载版本的信号
        self.Signal_TwoParameters_Overload[int, str].connect(self.setValue_TwoParameters_String)

(4)发射信号
    def mousePressEvent(self, event):
        # 发射无参信号
        self.Signal_NoParameters.emit()
        # 发射带一个参数(整数)的信号
        self.Signal_OneParameter.emit(1)
        # 发射带一个参数(整数)的重载版本的信号
        self.Signal_OneParameter_Overload.emit(1)
        # 发射带一个参数(字符串)的重载版本的信号
        self.Signal_OneParameter_Overload.emit('Abc')
        # 发射带两个参数(整数,字符串)的信号
        self.Signal_TwoParameters.emit(1, 'abc')
        # 发射带两个参数(整数,整数)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1, 2)
        # 发射带两个参数(整数,字符串)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1, 'abc')
(5)实例
  • 上面例子完整代码
# -*- coding:utf-8 -*-
"""
    # @Time:2022/12/11 0011 14:18
    # @Author:晚秋拾叶
    # @File:senior.py
    # @PyCharm之Python
"""
import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import pyqtSignal


class MyWidget(QWidget):
    """------------------一大片信号定义--------------------"""
    # 无参信号
    Signal_NoParameters = pyqtSignal()
    # 带一个参数(整数)的信号
    Signal_OneParameter = pyqtSignal(int)
    # 带一个参数(整数或者字符串)的重载版本的信号
    Signal_OneParameter_Overload = pyqtSignal([int], [str])
    # 带两个参数(整数。字符串)的信号
    Signal_TwoParameters = pyqtSignal(int, str)
    # # 带两个参数([整数,整数]或者[整数,字符串])的重载版本的信号
    Signal_TwoParameters_Overload = pyqtSignal([int, int], [int, str])

    def __init__(self):
        super().__init__()
        
        """------------------一大片信号连接---------------------"""
        # 连接无参信号
        self.Signal_NoParameters.connect(self.setValue_NoParameters)

        # 连接带一个整数参数的信号
        self.Signal_OneParameter.connect(self.setValue_OneParameter)

        # 连接带一个整数参数,经过重载的信号
        self.Signal_OneParameter_Overload.connect(self.setValue_OneParameter_String)

        # 连接一个信号,有两个整数参数
        self.Signal_TwoParameters.connect(self.setValue_TwoParameters)

        # 连接带两个参数(整数,整数)的重载版本的信号
        self.Signal_TwoParameters_Overload[int, int].connect(self.setValue_TwoParameters)

        # 连接带两个参数(整数,字符串)的重载版本的信号
        self.Signal_TwoParameters_Overload[int, str].connect(self.setValue_TwoParameters_String)

    """------------------一大片槽函数---------------------"""
    def setValue_NoParameters(self):
        """无参槽函数"""
        print("""无参槽函数""")

    def setValue_OneParameter(self, nIndex):
        """带一个参数(整数)的槽函数"""
        print("""带一个参数(整数)的槽函数""", nIndex)

    def setValue_OneParameter_String(self, snIndex):
        """带一个参数(字符串)的槽函数"""
        print("""带一个参数(字符串)的槽函数""", snIndex)

    def setValue_TwoParameters(self, x, y):
        """带两个参数(整数,整数)的槽函数"""
        print("""带两个参数(整数,整数)的槽函数""", x, y)

    def setValue_TwoParameters_String(self, x, szY):
        """带两个参数(整数,字符串)的槽函数"""
        print("""带两个参数(整数,字符串)的槽函数""", x, szY)

    """------------------发射信号函数---------------------"""
    def mousePressEvent(self, event):
        # 发射无参信号
        self.Signal_NoParameters.emit()
        # 发射带一个参数(整数)的信号
        self.Signal_OneParameter.emit(1)
        # 发射带一个参数(整数)的重载版本的信号
        self.Signal_OneParameter_Overload.emit(1)
        # 发射带一个参数(字符串)的重载版本的信号
        self.Signal_OneParameter_Overload.emit('Abc')
        # 发射带两个参数(整数,字符串)的信号
        self.Signal_TwoParameters.emit(1, 'abc')
        # 发射带两个参数(整数,整数)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1, 2)
        # 发射带两个参数(整数,字符串)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1, 'abc')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    sys.exit(app.exec_())
  • 运行过程中,需要点击窗口空白处。

  • 运行结果
    在这里插入图片描述

  • 新建另一实例

# -*- coding:utf-8 -*-
"""
    # @Time:2022/12/11 0011 15:51
    # @Author:晚秋拾叶
    # @File:qt07_signalSlot02.py
    # @PyCharm之Python
"""
from PyQt5.QtCore import QObject, pyqtSignal


class CustSignal(QObject):
    """  1 定义信号-------------------------------"""
    # 声明一个无参数的信号
    signal1 = pyqtSignal()
    # 声明带一个int类型参数的信号
    signal2 = pyqtSignal(int)
    # 声明带一个int和str类型参数的信号
    signal3 = pyqtSignal(int, str)
    # 声明带一个列表类型参数的信号
    signal4 = pyqtSignal(list)
    # 声明带一个字典类型参数的信号
    signal5 = pyqtSignal(dict)
    # 声明一个多重载版本的信号,包括了一个带int和str类型参数的信号或着带str参数的信号
    signal6 = pyqtSignal([int, str], [str])

    def __init__(self, parent=None):
        super(CustSignal, self).__init__(parent)

        """  2 信号连槽-------------------------------"""
        # 信号连接到指定槽
        self.signal1.connect(self.signalCall1)
        self.signal2.connect(self.signalCall2)
        self.signal3.connect(self.signalCall3)
        self.signal4.connect(self.signalCall4)
        self.signal5.connect(self.signalCall5)
        self.signal6[int, str].connect(self.signalCall6)
        self.signal6[str].connect(self.signalCall6OverLoad)

        """  4 信号发送-------------------------------"""
        # 信号发射
        self.signal1.emit()
        self.signal2.emit(1)
        self.signal3.emit(1, "text")
        self.signal4.emit([1, 2, 3, 4])
        self.signal5.emit({"name": "晚秋拾叶", "age": "28"})
        self.signal6[int, str].emit(1, "text")
        self.signal6[str].emit("text")

    """  3 定义槽函数-------------------------------"""

    def signalCall1(self):
        print("signal1 emit")

    def signalCall2(self, val):
        print("signal2 emit,value:", val)

    def signalCall3(self, val, text):
        print("signal3 emit,value:", val, text)

    def signalCall4(self, val):
        print("signal4 emit,value:", val)

    def signalCall5(self, val):
        print("signal5 emit,value:", val)

    def signalCall6(self, val, text):
        print("signal6 emit,value:", val, text)

    def signalCall6OverLoad(self, val):
        print("signal6 overload emit,value:", val)


if __name__ == '__main__':
    custSignal = CustSignal()

  • 运行结果
    在这里插入图片描述
  • 代码分析
    • 首先,定义信号
    • 其次,绑定信号
    • 再次,定义槽函数
    • 最后,发送信号

2、使用自定义参数

  • 在 PyQt编程过程中,经常会遇到给槽函数传递自定义参数的情况,比如有一个信号与槽函数的连接是

    button1.clicked.connect(show_page)
    
  • 对于clicked信号,无参,对于show_page函数来说,希望它可以接收参数。

    def show_page(self , name):
        print(name , "点击了")
    
  • 于是就产生一个问题——信号发出的参数个数为0,槽函数接收的参数个数为1,由于0<1,这样运行起来一定会报错(原因是信号发出的参数个数定要大于槽函数接收的参数个数)。解决这个问题 : 自定义参数的传递。

  • 本节提供了两种解决方法,其中一种方法就是使用lambda表达式。例子如下:

# -*- coding:utf-8 -*-
"""
    # @Time:2022/12/11 0011 16:41
    # @Author:晚秋拾叶
    # @File:qt07_winSignalSlot04.py
    # @PyCharm之Python
"""
from PyQt5.QtWidgets import QMainWindow, QPushButton, QWidget, QMessageBox, QApplication, QHBoxLayout
import sys


class WinForm(QMainWindow):
    def __init__(self, parent=None):
        super(WinForm, self).__init__(parent)
        self.setWindowTitle("信号和槽传递额外参数例子")
        button1 = QPushButton('Button 1')
        button2 = QPushButton('Button 2')

        button1.clicked.connect(lambda: self.onButtonClick(1))
        button2.clicked.connect(lambda: self.onButtonClick(2))

        layout = QHBoxLayout()
        layout.addWidget(button1)
        layout.addWidget(button2)

        main_frame = QWidget()
        main_frame.setLayout(layout)
        self.setCentralWidget(main_frame)

    def onButtonClick(self, n):
        print('Button {0} 被按下了'.format(n))
        QMessageBox.information(self, "信息提示框", 'Button {0} clicked'.format(n))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = WinForm()
    form.show()
    sys.exit(app.exec_())

  • 代码分析
    • 单击按钮1,将弹出信息提示框,提示信息为“Button 1 clicked”。控制台信息为
      Button 1被按下了
    • 这里重点解释onButtonClick()函数是怎样处理从两个按钮传来的信号的。使用lambda表达式传递按钮数字给槽函数,当然也可以传递其他任何信息,甚至是按钮控件本身(假设槽函数打算把传递信号的按钮修改为不可用的话)。
    • 另一种解决方法是使用functools中的partial函数。例子如下:
from functools import partial

from PyQt5.QtWidgets import QMainWindow, QPushButton, QWidget, QMessageBox, QApplication, QHBoxLayout
import sys


class WinForm(QMainWindow):
    def __init__(self, parent=None):
        super(WinForm, self).__init__(parent)
        self.setWindowTitle("信号和槽传递额外参数例子使用partial函数")
        button1 = QPushButton('Button 1')
        button2 = QPushButton('Button 2')

        # button1.clicked.connect(lambda: self.onButtonClick(1))
        # button2.clicked.connect(lambda: self.onButtonClick(2))
        button1.clicked.connect(partial(self.onButtonClick,1))
        button2.clicked.connect(partial(self.onButtonClick,2))

        layout = QHBoxLayout()
        layout.addWidget(button1)
        layout.addWidget(button2)

        main_frame = QWidget()
        main_frame.setLayout(layout)
        self.setCentralWidget(main_frame)

    def onButtonClick(self, n):
        print('Button {0} 被按下了'.format(n))
        QMessageBox.information(self, "信息提示框", 'Button {0} clicked'.format(n))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    form = WinForm()
    form.show()
    sys.exit(app.exec_())

  • 运行结果
    • 上面两个例子的结果是相同的,如图所示
      在这里插入图片描述

3、装饰器信号与槽

  • 装饰器信号与槽,就是通过装饰器的方法来定义信号和槽函数。具体用法如下:

    @PyQt5.QtCore.pyqtSlot(参数)
    def on_发送者对象名称_发射信号名称(self , 参数):
        pass
    
  • 这种方法有效的前提是下面的函数已经执行

    QMetaObject.connectSlotsByName(QObject)
    
  • 发送者对象名称 → 使用setObjectName函数设置的名称。自定义槽函数的命名规则也可以看成 : on _使用setObjectName设置的名称_信号名称。具体例子如下:

# -*- coding:utf-8 -*-
"""
    # @Time:2022/12/11 0011 17:08
    # @Author:晚秋拾叶
    # @File:qt07_connSlotsByName.py
    # @PyCharm之Python
"""
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
import sys


class CustWidget(QWidget):
    def __init__(self, parent=None):
        super(CustWidget, self).__init__(parent)

        self.okButton = QPushButton("OK", self)
        # 使用setObjectName设置对象名称
        self.okButton.setObjectName("okButton")
        layout = QHBoxLayout()
        layout.addWidget(self.okButton)
        QtCore.QMetaObject.connectSlotsByName(self)

    @QtCore.pyqtSlot()
    def on_okButton_clicked(self):
        print("单击了ok按钮")


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

  • 结果如图
    在这里插入图片描述
  • 代码分析
    • 解释下面代码的意思

      QtCore.QMetaObject.connectSlotsByName(self)
      
    • 事实上,这是在PyQt5中根据信号名称自动连接到槽函数的核心代码。通过使用pyuic5命令生成的代码中会带有这么一行代码。

    • 这行代码用来将QObject中的子孙对象的某些信号按照其objectName连接到相应的槽函数。这句话读起来有些拗口,举例简单说明。

      • 假设代码QtCore.QMetaObject.connectSlotsByName(self)已经执行,则下面的代码:

        @QtCore.pyqtSlot()
            def on_okButton_clicked(self):
                print("单击了ok按钮")
        
      • 会被自动识别为下面的代码(函数去掉了on,因为on会受到connectSlotsByName的影响,加上on运行时会出现问题)

        def __init__(self, parent=None):
        	self.okButton.clicked.connect(self.okButton_clicked)
        def okButton_clicked(self):
        	print("单击了OK按钮")
        
      • 事实上,以下代码实现的效果是相同的。

        • 代码一
        @QtCore.pyqtSlot()
        def on_okButton_clicked(self):
        	print("单击了ok按钮")
        
        • 代码二
        	    self.okButton.clicked.connect(self.okButton_clicked)
        	def okButton_clciked(self):
        		print("单击了OK按键")
        

4、信号与槽的断开和连接

  • 有时候基于某些原因 ,想要临时或永久断开某个信号与槽的连接。看下面例子
# -*- coding:utf-8 -*-
"""
    # @Time:2022/12/11 0011 18:10
    # @Author:晚秋拾叶
    # @File:qt07_signalSlot03.py
    # @PyCharm之Python
"""
from PyQt5.QtCore import QObject, pyqtSignal


class SignalClass(QObject):
    # 声明一个无参数的信号
    signal1 = pyqtSignal()
    # 声明带一个int类型参数的信号
    signal2 = pyqtSignal(int)

    def __init__(self, parent=None):
        super(SignalClass, self).__init__(parent)

        # 信号sin1连接到sin1Call和sin2Call这两个槽
        self.signal1.connect(self.sin1Call)
        self.signal1.connect(self.sin2Call)
        # 信号sin2连接到信号sin1
        self.signal2.connect(self.signal1)

        self.signal2.emit(1)

        # 断开sin1、sin2信号与各槽的连接
        self.signal1.disconnect(self.sin1Call)
        self.signal1.disconnect(self.sin2Call)
        self.signal2.disconnect(self.signal1)

        # 信号sin1和sin2连接同一个槽sin1Call
        # 信号发射
        self.signal1.emit()
        self.signal1.connect(self.sin1Call)
        self.signal2.connect(self.sin1Call)

        # 信号再次发射
        self.signal1.emit()  # signal-1 emit
        self.signal2.emit(1)  # signal-1 emit

    def sin1Call(self):
        print("signal-1 emit")

    def sin2Call(self):
        print("signal-2 emit")


if __name__ == '__main__':
    signal = SignalClass()
  • 代码分析
    • 这段代码比较绕,尝试屏蔽一部分代码,再运行看效果。

5、Qt Designer神助攻:界面与业务逻辑的分离

  • Qt Designer工具的使用,在另一部分章节介绍。这个工具的使用可以更好的实现界面显示与业务逻辑的分离,节省我们写大量代码的时间。看下面例子。
  • 信号与槽如何和Qt Designer结合
    • 案例:
    • 通过一个模拟打印的界面来详细说明信号的使用,在打印时可以设置打印的份数、纸张类型,触发“打印”按钮后,将执行结果显示在右侧;通过QCheckBox (“全屏预览”复选框)来选择是否通过全屏模式进行预览,将执行结果显示在右侧。
  • 运行QtDesigner工具,新建一个模板名为“Widget”的简单窗口,命名为MainWinSignalSlog02.ui。通过将Widget Box区域的控件拖曳到窗口中,实现下图界面。
    • 其中,打印和预览功能需要编写信号和槽。
      在这里插入图片描述
  • 窗口控件简要说明
控件类型控件名称作用
QGroupBox(组合布局器)controlsGroup布局整个窗口左侧的控件
QHBoxLayout(打印垂直布局器)controlGroup打印控制布局
QHBoxLayout(结果垂直布局器)resultGroup操作结果布局
QSpinBox(计数器)numberSpinBox显示打印的份数
QComboBox(下拉列表框)styleCombo显示打印的纸张类型,纸张类型包括A3、A4和A5
QPushButton(打印按钮)printButton连接emitPrintSignal函数的绑定,触发自定义信号printSignal的发射
QCheckBox(复选框)previewStatus是否全屏预览
QPushButton(预览按钮)previewButton连接emitPrintSignal函数的绑定,触发自定义信号previewSignal的发射
QLabel(显示标签)resultLabel显示执行结果
  • 打开Designer,建一个Form窗体,然后按照这个预览图,从左侧工具中拖曳各种布局和控件即可,其中QComboBox中的的选项,需要双击这个控件然后再添加。
    在这里插入图片描述

  • 将界面文件转换成Python文件。命令行如下:

     pyuic5.exe MainWinSignalSlog02.ui -o MainWinSignalSlog02.py
    
  • 生成的MainWinSignalSlog02.py文件,完整代码如下。

    # -*- coding: utf-8 -*-
    
    # Form implementation generated from reading ui file 'MainWinSignalSlog02.ui'
    #
    # Created by: PyQt5 UI code generator 5.15.4
    #
    # WARNING: Any manual changes made to this file will be lost when pyuic5 is
    # run again.  Do not edit this file unless you know what you are doing.
    
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Ui_Form(object):
        def setupUi(self, PrintWin):
    
            PrintWin.setObjectName("PrintWin")
            PrintWin.resize(794, 183)
    
            # “打印控制”组合布局设计
            self.controlGroup = QtWidgets.QGroupBox(PrintWin)
            self.controlGroup.setGeometry(QtCore.QRect(10, 10, 461, 161))
            self.controlGroup.setObjectName("controlGroup")
    
            #
            self.horizontalLayoutWidget = QtWidgets.QWidget(self.controlGroup)
            self.horizontalLayoutWidget.setGeometry(QtCore.QRect(9, 20, 431, 80))
            self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
    
            # 打印份数等控件所在的垂直布局
            self.HLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
            self.HLayout.setContentsMargins(0, 0, 0, 0)
            self.HLayout.setObjectName("HLayout")
    
            # “打印份数”标签
            self.label = QtWidgets.QLabel(self.horizontalLayoutWidget)
            self.label.setObjectName("label")
            self.HLayout.addWidget(self.label)
    
            # “计数器”标签
            self.numberSpinBox = QtWidgets.QSpinBox(self.horizontalLayoutWidget)
            self.numberSpinBox.setObjectName("numberSpinBox")
            self.HLayout.addWidget(self.numberSpinBox)
    
            # “纸张类型”标签
            self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget)
            self.label_2.setObjectName("label_2")
            self.HLayout.addWidget(self.label_2)
    
            # “下拉列表框”标签
            self.styleCombo = QtWidgets.QComboBox(self.horizontalLayoutWidget)
            self.styleCombo.setObjectName("styleCombo")
            self.styleCombo.addItem("")
            self.styleCombo.addItem("")
            self.styleCombo.addItem("")
            self.HLayout.addWidget(self.styleCombo)
    
            # ”打印“按键
            self.printButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
            self.printButton.setObjectName("printButton")
            self.HLayout.addWidget(self.printButton)
    
            #
            self.horizontalLayoutWidget_2 = QtWidgets.QWidget(self.controlGroup)
            self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(9, 110, 321, 41))
            self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
    
            # ”全屏预览“复选框所在的垂直布局器
            self.HLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
            self.HLayout_2.setContentsMargins(0, 0, 0, 0)
            self.HLayout_2.setObjectName("HLayout_2")
            # ”全屏预览“复选框
            self.previewStatus = QtWidgets.QCheckBox(self.horizontalLayoutWidget_2)
            self.previewStatus.setObjectName("previewStatus")
            self.HLayout_2.addWidget(self.previewStatus)
    
            # ”预览”按键
            self.previewButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2)
            self.previewButton.setObjectName("previewButton")
            self.HLayout_2.addWidget(self.previewButton)
    
            # “操作结果”组合布局设计
            self.resultGroup = QtWidgets.QGroupBox(PrintWin)
            self.resultGroup.setGeometry(QtCore.QRect(480, 10, 311, 161))
            self.resultGroup.setObjectName("resultGroup")
            self.resultLabel = QtWidgets.QLabel(self.resultGroup)
            self.resultLabel.setGeometry(QtCore.QRect(25, 63, 261, 21))
            self.resultLabel.setObjectName("resultLabel")
    
            # 调用再编译函数,对窗口进行展现
            self.retranslateUi(PrintWin)
            # 遇到了上节课的那种对PrintWin函数的绑定
            QtCore.QMetaObject.connectSlotsByName(PrintWin)
    
        def retranslateUi(self, PrintWin):
            _translate = QtCore.QCoreApplication.translate
            PrintWin.setWindowTitle(_translate("PrintWin", "打印窗口"))
            self.controlGroup.setTitle(_translate("PrintWin", "打印控制"))
            self.label.setText(_translate("PrintWin", "打印份数"))
            self.label_2.setText(_translate("PrintWin", "纸张类型"))
            self.styleCombo.setItemText(0, _translate("PrintWin", "A3"))
            self.styleCombo.setItemText(1, _translate("PrintWin", "A4"))
            self.styleCombo.setItemText(2, _translate("PrintWin", "A5"))
            self.printButton.setText(_translate("PrintWin", "打印"))
            self.previewStatus.setText(_translate("PrintWin", "全屏预览"))
            self.previewButton.setText(_translate("PrintWin", "预览"))
            self.resultGroup.setTitle(_translate("PrintWin", "操作结果"))
            self.resultLabel.setText(_translate("PrintWin", "<html><head/><body><p><br/></p></body></html>"))
    	
    		
    
  • 为了实现窗口显示和业务逻辑分离,再建一个调用对话窗口的文件CallMainWinSignalSlog02.py,代码如下:

    # -*- coding:utf-8 -*-
    """
        # @Time:2022/12/11 0011 21:06
        # @Author:晚秋拾叶
        # @File:callMainWinSignalSlog02.py
        # @PyCharm之Python
    """
    import sys
    from PyQt5.QtWidgets import QApplication, QMainWindow
    from MainWinSignalSlog02 import Ui_Form
    from PyQt5.QtCore import pyqtSignal, Qt
    
    
    class MyMainWindow(QMainWindow, Ui_Form):
        # 帮助信号和打印信号
        helpSignal = pyqtSignal(str)
        printSignal = pyqtSignal(list)
        # 预览信号,声明一个多重载版本的信号,包括了一个带int和str类型参数的信号,以及带str参数的信号
        previewSignal = pyqtSignal([int, str], [str])
    
        def __init__(self, parent=None):
            super(MyMainWindow, self).__init__(parent)
            self.setupUi(self)
            self.initUI()
    
        def initUI(self):
            self.helpSignal.connect(self.showHelpMessage)                       # 1 信号一,显示帮助消息
            self.printSignal.connect(self.printPaper)                           # 2 信号二,点击打印按键后显示的信息
            self.previewSignal[str].connect(self.previewPaper)                  # 3 信号三,多重载中的一个参数情况下的预览Preview
            self.previewSignal[int, str].connect(self.previewPaperWithArgs)     # 4 信号三,多重载中的多个参数情况下的全屏预览1080 Full Screen
            self.printButton.clicked.connect(self.emitPrintSignal)              # 5 打印按钮,发射打印信号
            self.previewButton.clicked.connect(self.emitPreviewSignal)          # 6 预览按钮,发射预览信号
    
        # 1 显示帮助消息
        def showHelpMessage(self, message):
            self.resultLabel.setText(message)
            self.statusBar().showMessage(message)
    
        # 2 点击打印按键后显示的信息
        def printPaper(self, listview):
            self.resultLabel.setText("打印: " + "份数:" + str(listview[0]) + " 纸张:" + str(listview[1]))
    
        # 3 多重载中的一个参数情况下的预览Preview
        def previewPaper(self, text):
            self.resultLabel.setText(text)
    
        # 4 多重载中的多个参数情况下的全屏预览1080 Full Screen
        def previewPaperWithArgs(self, style, text):
            self.resultLabel.setText(str(style) + text)
    
        # 5 发射打印信号
        def emitPrintSignal(self):
            pList = [self.numberSpinBox.value(), self.styleCombo.currentText()]
            self.printSignal.emit(pList)
    
        # 6 发射预览信号
        def emitPreviewSignal(self):
            if self.previewStatus.isChecked():
                self.previewSignal[int, str].emit(1080, " Full Screen")
            elif not self.previewStatus.isChecked():
                self.previewSignal[str].emit("Preview")
    
        # 0 重载点击键盘事件
        def keyPressEvent(self, event):
            if event.key() == Qt.Key_F1:
                self.helpSignal.emit("help message")
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        win = MyMainWindow()
        win.show()
        sys.exit(app.exec_())
    
    
  • 效果如图

    • 这是我按下了F1键后的效果图,出现help message的帮助字样。
      在这里插入图片描述
    • 这是我选择了打印份数和纸张类型后,再点打印的效果图。
      在这里插入图片描述
    • 窗体代码需要改动的地方,也可以直接在窗体改
      在这里插入图片描述
  • 代码分析

    • 这个例子非常好,既学习了信号与槽,还复习了Designer工具的使用。
    • 代码分离的好处,那就是所见即所得,每次改动用Designer太方便了,生成的界面图,保存后,用pyuic命令重新生成一次,很省事。
    • 本例通过pyqtSignal()定义了三个信号,即helpSignal、printSignal、previewSignal,参数类型分别为str、list和一个多重载版本的信号,其中previewSignal的参数包括一个带int和str类型参数的信号,以及带str类型参数的信号
      # 帮助信号和打印信号
      helpSignal = pyqtSignal(str)
      printSignal = pyqtSignal(list)
      # 预览信号,声明一个多重载版本的信号,包括了一个带int和str类型参数的信号,以及带str参数的信号
      previewSignal = pyqtSignal([int, str], [str])
      
    • 对于绑定信号与槽,这里着重说明多重载版本的信号绑定。因为previewSignal有两个版本,因此,在绑定时需要显式指定信号与槽的绑定关系。
      self.helpSignal.connect(self.showHelpMessage)
      self.printSignal.connect(self.printPaper)
      self.previewSignal[str].connect(self.previewPaper)
      self.previewSignal[int, str].connect(self.previewPaperWithArgs)
      
    • [str]参数的previewSignal信号绑定previewPaper(),[int,str]参数previewSignal信号绑定previewWithArgs(),多重载版本的信号发射时,要注意发射信号传递的参数类型和个数,在Qt参数的通信机制中,根据所传递信号的参数类型和个数,连接到不同的槽函数。
      def emitPreviewSignal(self):
          if self.previewStatus.isChecked():
              self.previewSignal[int, str].emit(1080, " Full Screen")
          elif not self.previewStatus.isChecked():
              self.previewSignal[str].emit("Preview")
      
    • 信号发射时可以传递Python数据类型的参数,本例中的printSignal信号可以传递list类型的参数pList。
      def emitPrintSignal(self):
          pList = [self.numberSpinBox.value(), self.styleCombo.currentText()]
          self.printSignal.emit(pList)
      
    • 通过复写keyPressEvent()方法,对F1键进行功能扩展。在Windows的大部分应用中,都会使用一些快捷键来快速完成某些特定的功能。这里通过复写keyPressEvent()方法模拟发射所需的信号,来完成对应的任务。
      def keyPressEvent(self, event):
         if event.key() == Qt.Key_F1:
             self.helpSignal.emit("help message")
      
  • 特别注意

    • (1)自定义信号在__init__()函数之前定义。
    • (2)自定义信号可以传递如str、int、list、object、float、tuple、dict等很多类型的参数。
    • (3)注意signal和slot的调用逻辑,避免 两者之间出现死循环,比如在slot方法中继续发射该信号。

6、多线程中信号与槽的使用

  • 最简单的多线程使用方法是利用QTread函数,下例展现了QTread函数和信号/槽的简单结合方法。

    # -*- coding:utf-8 -*-
    """
        # @Time:2022/12/12 0012 0:01
        # @Author:晚秋拾叶
        # @File:qt07_signalSlot04.py
        # @PyCharm之Python
    """
    from PyQt5.QtWidgets import QApplication, QWidget
    from PyQt5.QtCore import QThread, pyqtSignal
    import sys
    
    
    class Main(QWidget):
        def __init__(self, parent=None):
            super(Main, self).__init__(parent)
    
            # 创建一个线程实例并设置名称、变量、信号槽
            self.thread = MyThread()
            self.thread.setIdentity("thread1")
            self.thread.sinOut.connect(self.outText)
            self.thread.setVal(6)
    
        def outText(self, text):
            print(text)
    
    
    class MyThread(QThread):
        sinOut = pyqtSignal(str)
    
        def __init__(self, parent=None):
            super(MyThread, self).__init__(parent)
            self.identity = None
    
        def setIdentity(self, text):
            self.identity = text
    
        def setVal(self, val):
            self.times = int(val)
            # 执行线程的run方法
            self.start()
    
        def run(self):
            while self.times > 0 and self.identity:
                # 发射信号
                self.sinOut.emit(self.identity + "==>" + str(self.times))
                self.times -= 1
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = Main()
        main.show()
        sys.exit(app.exec_())
    
  • 运行结果
    在这里插入图片描述

  • 有时在开发程序时经常会执行一些耗时的操作,这样就会导致界面卡顿,这也是多线程的应用范围之一——为了解决这个问题,我们可以创建多线程,使用主线程更新界面,使用子线程实时处理数据,最后将结果显示到界面上。

  • 本例中,定义了一个后台线程类 BackendThread 来模拟后台耗时操作,在这个线程类中定义了信号update_date。使用BackendThread 线程类在后台处理数据,每秒发射一次自定义信号 update date。

  • 在初始化窗口界面时,定义后台线程类 BackendThread,并把线程类的信号 update_date 连接到槽函数 handleDisplayO,这样后台线程每发射一次信号,就可以把最新的时间值实时显示在前台窗口的 QLineEdit 文本对话框中。

  • 本例文件名为PyQt5/Chapter07/qt07_signalSIotThreaadpy,其完整代码如下:

    # -*- coding:utf-8 -*-
    """
        # @Time:2022/12/12 0012 0:10
        # @Author:晚秋拾叶
        # @File:qt07_signalSlotThread.py
        # @PyCharm之Python
    """
    from PyQt5.QtCore import QThread, pyqtSignal, QDateTime
    from PyQt5.QtWidgets import QApplication, QDialog, QLineEdit
    import time
    import sys
    
    
    class BackendThread(QThread):
        # 通过类成员对象定义信号对象
        update_date = pyqtSignal(str)
    
        # 处理要做的业务逻辑
        def run(self):
            while True:
                data = QDateTime.currentDateTime()
                currTime = data.toString("yyyy-MM-dd hh:mm:ss")
                self.update_date.emit(str(currTime))
                time.sleep(1)
    
    
    class Window(QDialog):
        def __init__(self):
            QDialog.__init__(self)
            self.setWindowTitle('pyqt5界面实时更新例子')
            self.resize(400, 100)
            self.input = QLineEdit(self)
            self.input.resize(400, 100)
            self.initUI()
    
        def initUI(self):
            # 创建线程
            self.backend = BackendThread()
            # 连接信号
            self.backend.update_date.connect(self.handleDisplay)
            # 开始线程
            self.backend.start()
    
        # 将当前时间输出到文本框
        def handleDisplay(self, data):
            self.input.setText(data)
    
    
    if __name__ == '__main__':
        from pyqt5_plugins.examples.exampleqmlitem import QtCore
    
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
        app = QApplication(sys.argv)
        win = Window()
        win.show()
        sys.exit(app.exec_())
    
    
  • 运行结果
    在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
PyQt5中,信号是用于对象间通信的机制。当一个对象的状态发生改变时,它会发出一个信号,其他对象可以通过连接该信号来接收该信号并做出响应。是一个函数,当一个信号被发出时,它会被调用执行。 在信号的连接过程中,可以传递参数。当信号发出时,它可以附带一些数据,这些数据可以在函数中进行处理。传递参数的方式有多种,以下是其中的两种方式: 1. 使用lambda表达式: ```python import sys from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton class Signal(QObject): signal = pyqtSignal(int) class Slot(QObject): def __init__(self): super().__init__() def handle(self, num): print('slot received:', num) if __name__ == '__main__': app = QApplication(sys.argv) signal = Signal() slot = Slot() signal.signal.connect(lambda num: slot.handle(num)) signal.signal.emit(1) sys.exit(app.exec_()) ``` 2. 使用functools.partial: ```python import sys from functools import partial from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton class Signal(QObject): signal = pyqtSignal(int) class Slot(QObject): def __init__(self): super().__init__() def handle(self, num, text): print('slot received:', num, text) if __name__ == '__main__': app = QApplication(sys.argv) signal = Signal() slot = Slot() signal.signal.connect(partial(slot.handle, text='hello')) signal.signal.emit(1) sys.exit(app.exec_()) ``` 以上两种方式都可以在函数中接收到附带的参数。如果你还有其他相关问题,请随时问我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

山哥ol

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

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

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

打赏作者

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

抵扣说明:

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

余额充值