(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_())
-
运行结果