信号与槽的高级玩法
高级自定义信号与槽
所谓高级自定义信号与槽,指的是我们可以以自己喜欢的方式定义信号与槽函
数,并传递参数。自定义信号的一般流程如下:
(1)定义信号。
(2)定义槽函数。
(3)连接信号与槽函数。
(4)发射信号。
定义信号
通过类成员变量定义信号对象。
python
class MyWidget(QWidget):
# 无参数的信号
Signal_NoParameters=pyqtSignal()
#带一个参数(整数)的信号
Singal_OneParameter=pyqtSignal(int)
#带一个参数(整数或者字符串)的重载版本的信号
Singal_OneParameter_Overload=pyqtSignal([int],[str])
#带两个参数(整数,字符串)的信号
Signal_TwoParameters=pyqtSignal(int,str)
#带两个参数([整数,整数])或者[整数,字符串])的重载版本的信号
Signal_TwoParameter_Overlaod=pyqtSignal([int,int],[int,str])
定义槽函数
定义一个槽函数,它有多个不同的输入参数。
python
class MyWidget(QWidget):
def setValue_NoParameters(self):
'''无参数的槽函数'''
pass
def setValue_OneParameter(self,nIndex):
'''带一个参数(整数)的槽函数'''
pass
def setValue_OneParameter_String(self,szIndex):
'''带一个参数(字符串)的槽函数'''
pass
def setValue_TwoParameters(self,x,y):
'''带两个参数(整数,整数)的槽函数'''
pass
def setValue_TwoParameters_String(self,x,szy):
'''带两参数(整数,字符串)槽函数'''
pass
连接信号与槽函数
通过方法连接信号与槽函数或者可调用对象。
python
app=QApplication(sys.argv)
widget=MyWidget()
#连接无参数的信号
widget.Signal_NoParameters.connect(self.setValue_NoParameters)
#连接带一个整数参数的信号
widget.Signal_OneParameter.connect(self.setValue_OneParameter)
#连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[int].connect(self.setValue_OneParameter_Overload)
#连接带一个整数参数,经过重载信号
widget.Signal_OneParameter_Overload[str].connect(self.setValue_OneParameter_strng)
#连接带两个参数(整数,整数)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,int].connect(self.setVValue_TwoParameters_Overload)
#连接带两个参数(整数,字符串)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,str].connect(self.setValue_TwoParameters_String)
widget.show()
发射信号
通过emit方法发射信号,
python
class MyWidget(QWidget):
def mousePressEvent(self,event):
#发射无参数的信号
self.Signal_NoParameters.emit()
#发射带一个参数(整数)的参数
self.Singal_OneParameters.emit(1)
# 发射带一个参数(整数)的重载版本的信号
self.Sinal_OneParameter_Overload.emit(1)
# 发射带一个参数(字符串)的重载版本的信号
self.Signal_OneParameter_Overlaod.emit("abc")
# 发射带两个参数(整数,字符串)的信号
self.Signal_TwoParameters.emit(1,"abc")
# 发射带两个参数(整数,整数)的重载版本的信号
self.Signal_TwoParameters_Overload.emit(1,2)
# 发射带两个参数(整数,字符串)的重载版本的信号
self.Signal_TwoParameters_Overload.emit(1,"abc")
实例
python
import sys
from PyQt5.QtWidgets import QWidget,QPushButton,QApplication
class winform(QWidget):
def __init__(self):
super(winform,self).__init__()
self.setGeometry(200,300,350,50)
self.setWindowTitle("内置信号,自定义槽的例子")
self.btn=QPushButton("按钮文本",self)
self.btn.clicked.connect(self.changeBtnText)
def changeBtnText(self):
self.btn.setText("按钮内容和宽度改变了")
self.btn.setStyleSheet("QPushButton{max-width:200px;min-width:200px}")
if __name__ == '__main__':
app = QApplication(sys.argv)
qb=winform()
qb.show()
sys.exit(app.exec_())
运行结果

使用自定义参数
在编程过程中,经常会遇到给槽函数传递自定义参数的情况,比如有一个
信号与槽函数的连接是
python
button1.clicked.connect(show_page)
我们知道对于clicked信号来说,它是没有参数的:对于show-page函数来说,
希望它可以接收参数。希望show-page函数像如下这样:
python
def show_page(self,name):
print(name," 点击啦")
于是就产生一个问题一一信号发出的参数个数为0,槽函数接收的参数个数为
1,由于0<1,这样运行起来一定会报错(原因是信号发出的参数个数一定要大于槽
函数接收的参数个数)。解决这个问题就是本节的重点:自定义参数的传递。
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('Button1')
button2=QPushButton('Button2')
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)
win=WinForm()
win.show()
sys.exit(app.exec_())
运行结果

装饰器信号与槽
所谓装饰器信号与槽,就是通过装饰器的方法来定义信号和槽函数。具体的使
用方法如下:
python
@PyQt5.QtCore.pyqtSlot(参数)
def on_发送者对象名称_发射信号名称(self,参数):
pass
这种方法有效的前提是下面的函数已经执行:
python
QMetaObject.connectSlotsByName(QObject)
在上面代码中,"发送者对象名称"就是使用setObjectName函数设置的名称,
因此自定义槽函数的命名规则也可以看成:on+使用setObjectName设置的名称+
信号名称。接下来看具体的使用方法。
python
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication,QWidget,QHBoxLayout,QPushButton
import sys
class CustWidget(QWidget):
def __init__(self,parent=None):
super(CustWidget,self).__init__(parent)
self.okButton=QPushButton("OK",self)
#使用setObectName设置对象名称
self.okButton.setObjectName("okButton")
layout=QHBoxLayout()
layout.addWidget(self.okButton)
self.setLayout(layout)
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_())
运行效果

python
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication,QWidget,QHBoxLayout,QPushButton
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)
self.setLayout(layout)
QtCore.QMetaObject.connectSlotsByName(self)
self.okButton.clicked.connect(self.okButton_clicked)
def okButton_clicked(self):
print("点击了OK按钮")
if __name__ == '__main__':
app = QApplication(sys.argv)
win=CustWidget()
win.show()
sys.exit(app.exec_())
运行效果同上。
信号与槽的断开和连接
python
# -*- coding: utf-8 -*-
"""
【简介】
信号槽N对N连接、断开连接示例
"""
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.signal1.emit()
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.connect(self.sin1Call)
self.signal2.connect(self.sin1Call)
# 信号再次发射
self.signal1.emit()
self.signal2.emit(1)
def sin1Call(self):
print("signal-1 emit")
def sin2Call(self):
print("signal-2 emit")
if __name__ == '__main__':
signal = SignalClass()
QtDesigner神助攻:界面显示与业务逻辑的分离
前面给出的是手工输入代码的信号与槽的使用方法,因为采用这种方式介绍时
会更简单一些。如果采用Qt来介绍这些内容,那么任何一个简单的功能都
需要使用xxx.ui、xxx.PY、CalLxxx.py三个文件来实现,这样做内容会显得很乱。
在实战应用中,由于QtDesigner可以更好地实现界面显示与业务逻辑的分离,
所以能帮助我们解决大量的代码。如果能够使用QtDesigner自动创建一些信号与槽
机制,那就更好了。本节将通过一个实战性案例来介绍信号与槽是如何和QtDesigner
结合的。
本例要实现的功能是:通过一个模拟打印的界面来详细说明信号的使用,在打
印时可以设置打印的份数、纸张类型,触发"打印"按钮后,将执行结果显示在右
侧;通过QCheckBox("全屏预览"复选框)来选择是否通过全屏模式进行预览,
将执行结果显示在右侧。
这里对窗口中的控件进行简要说明,如表7-1所示。
控件类型 | 空间名称 | 作用 |
---|---|---|
QSpinBox | numberSpinBox | 显示打印的份数 |
QComboBox | styleCombo | 显示打印的纸张类型。纸张类型包括A3,A4和A5纸 |
QPushButton | printButton | 连按emitPrintSignal函数的绑定,触发自定义信号printSignal的发射 |
QCheckBox | previewStatus | 是否全屏预览 |
QPushButton | previewButton | 连接emitPreviewSignal函数的绑定:触发自定义信号previewSignal的发射 |
QLabel | resultLabel | 显示执行结果 |
from PyQt5.QtWidgets import QMainWindow,QHBoxLayout,QPushButton,QApplication,QWidget
import sys
class Winform(QMainWindow):
def __init__(self,parent=None):
super(Winform,self).__init__(parent)
self.setWindowTitle('控件中的信号槽通信')
self.button1=QPushButton('Button1')
self.button1.clicked.connect(self.onButtonClick)
layout = QHBoxLayout()
layout.addWidget(self.button1)
main_frame=QWidget()
main_frame.setLayout(layout)
self.setCentralWidget(main_frame)
def onButtonClick(self):
#sender 是发送信号的对象
sender=self.sender()
print(sender.text()+' 被按下了')
if __name__ == '__main__':
app=QApplication(sys.argv)
form=Winform()
form.show()
sys.exit(app.exec_())
运行结果

python
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(715, 225)
self.controlsGroup = QtWidgets.QGroupBox(Form)
self.controlsGroup.setGeometry(QtCore.QRect(10, 20, 451, 151))
self.controlsGroup.setObjectName("controlsGroup")
self.widget = QtWidgets.QWidget(self.controlsGroup)
self.widget.setGeometry(QtCore.QRect(10, 40, 411, 30))
self.widget.setObjectName("widget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.widget)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.numberSpinBox = QtWidgets.QSpinBox(self.widget)
self.numberSpinBox.setObjectName("numberSpinBox")
self.horizontalLayout.addWidget(self.numberSpinBox)
self.styleCombo = QtWidgets.QComboBox(self.widget)
self.styleCombo.setObjectName("styleCombo")
self.styleCombo.addItem("")
self.styleCombo.addItem("")
self.styleCombo.addItem("")
self.horizontalLayout.addWidget(self.styleCombo)
self.label_2 = QtWidgets.QLabel(self.widget)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.printButton = QtWidgets.QPushButton(self.widget)
self.printButton.setObjectName("printButton")
self.horizontalLayout.addWidget(self.printButton)
self.widget1 = QtWidgets.QWidget(self.controlsGroup)
self.widget1.setGeometry(QtCore.QRect(10, 100, 201, 30))
self.widget1.setObjectName("widget1")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget1)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.previewStatus = QtWidgets.QCheckBox(self.widget1)
self.previewStatus.setObjectName("previewStatus")
self.horizontalLayout_2.addWidget(self.previewStatus)
self.previewButton = QtWidgets.QPushButton(self.widget1)
self.previewButton.setObjectName("previewButton")
self.horizontalLayout_2.addWidget(self.previewButton)
self.resultGroup = QtWidgets.QGroupBox(Form)
self.resultGroup.setGeometry(QtCore.QRect(470, 20, 231, 151))
self.resultGroup.setObjectName("resultGroup")
self.resultLabel = QtWidgets.QLabel(self.resultGroup)
self.resultLabel.setGeometry(QtCore.QRect(20, 30, 191, 101))
self.resultLabel.setObjectName("resultLabel")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "打印控件"))
self.controlsGroup.setTitle(_translate("Form", "打印控制"))
self.label.setText(_translate("Form", "打印份数:"))
self.styleCombo.setItemText(0, _translate("Form", "A3"))
self.styleCombo.setItemText(1, _translate("Form", "A4"))
self.styleCombo.setItemText(2, _translate("Form", "A5"))
self.label_2.setText(_translate("Form", "纸张类型:"))
self.printButton.setText(_translate("Form", "打印"))
self.previewStatus.setText(_translate("Form", "全屏预览"))
self.previewButton.setText(_translate("Form", "预览"))
self.resultGroup.setTitle(_translate("Form", "操作结果"))
self.resultLabel.setText(_translate("Form", "<html><head/><body><p><br/></p></body></html>"))
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(str)
#声明一个多重载版本的信号,包括了一个带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)
self.printSignal.connect(self.printPaper)
self.previewSignal[str].connect(self.previewPaper)
self.previewSignal[int,str].connect(self.previewPaperWithArgs)
self.printButton.clicked.connect(self.emitPrintSignal)
self.previewButton.clicked.connect(self.emitPreviewSignal)
#发射预览信号
def emitPreviewSignal(self):
if self.previewStatus.isChecked()==True:
self.previewSignal[int,str].emit(1000,"Full Screen")
elif self.previewStatus.isChecked()==False:
self.previewSignal[str].emit("Preview")
#发射打印信号
def emitPrintSignal(self):
pList=[]
pList.append(self.numberSpinBox.value())
pList.append(self.styleCombo.currentText())
self.previewSignal.emit(pList)
def printPaper(self,list):
self.resultLabel.setText("打印:"+"份数:"+str(list[0])+"纸张:"+str(list[1]))
def previewPaperWithArgs(self,style,text):
self.resultLabel.setText(str(style)+text)
def previewPaper(self,text):
self.resultLabe.setText(text)
#重载点击键盘事件
def keyPressEvent(self, event):
if event.key()==Qt.Key_F1:
self.helpSignal.emit("help message")
#显示帮助消息
def showHelpMessage(self,message):
self.resultLabel.setText(message)
self.statusBar().showMessage(message)
if __name__ == "__main__":
app = QApplication(sys.argv)
win=MyMainWindow()
win.show()
sys.exit(app.exec_())
运行结果

注意 :
(1)自定义信号在__init__()函数之前定义。
(2)自定义信号可以传递如str、int、list、object、float、tuple、、dict等很多类
型的参数。
(3)注意signal和slot的调用逻辑,避免signal和slot之间出现死循环,比
如在slot方法中继续发射该信号。
多线程中信号与槽的使用
python
from PyQt5.QtWidgets import QApplication,QWidget
from PyQt5.QtCore import QThread,pyqtSignal
import sys
class Main(QWidget):
def __init__(self):
super(Main,self).__init__()
#创建一个线程实例并设置名称、变量、信号槽
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)
win=Main()
win.show()
sys.exit(app.exec_())
运行结果:
python
thread1==>5
thread1==>4
thread1==>3
thread1==>2
thread1==>1
有时在开发程序时经常会执行一些耗时的操作,这样就会导致界面卡顿,这也
是多线程的应用范围之一一一为了解决这个问题,我们可以创建多线程,使用主线
程更新界面,使用子线程实时处理数据,最后将结果显示到界面上。
本例中,定义了一个后台线程类BackendThread来模拟后台耗时操作,在这个
线程类中定义了信号update_date。使用BackendThread线程类在后台处理数据,每
秒发射一次自定义信号update_date。
在初始化窗口界面时,定义后台线程类BackendThread,并把线程类的信号
updatedate连接到槽函数handleDisplay()。这样后台线程每发射一次信号,就可以
把最新的时间值实时显示在前台窗口的QLineEdit文本对话框中。
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__':
app = QApplication(sys.argv)
window=Window()
window.show()
sys.exit(app.exec_())
运行结果
