信号与槽的高级玩法
高级自定义信号与槽
所谓高级自定义信号与槽,指的是我们可以以自己喜欢的方式定义信号与槽函
数,并传递参数。自定义信号的一般流程如下:
(1)定义信号。
(2)定义槽函数。
(3)连接信号与槽函数。
(4)发射信号。
定义信号
通过类成员变量定义信号对象。
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])
定义槽函数
定义一个槽函数,它有多个不同的输入参数。
class MyWidget(QWidget):def setValue_NoParameters(self):'''无参数的槽函数'''passdef setValue_OneParameter(self,nIndex):'''带一个参数(整数)的槽函数'''passdef setValue_OneParameter_String(self,szIndex):'''带一个参数(字符串)的槽函数'''passdef setValue_TwoParameters(self,x,y):'''带两个参数(整数,整数)的槽函数'''passdef setValue_TwoParameters_String(self,x,szy):'''带两参数(整数,字符串)槽函数'''pass
连接信号与槽函数
通过方法连接信号与槽函数或者可调用对象。
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方法发射信号,
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")
实例
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_())
运行结果
使用自定义参数
在编程过程中,经常会遇到给槽函数传递自定义参数的情况,比如有一个
信号与槽函数的连接是
button1.clicked.connect(show_page)
我们知道对于clicked信号来说,它是没有参数的:对于show-page函数来说,
希望它可以接收参数。希望show-page函数像如下这样:
def show_page(self,name):print(name," 点击啦")
于是就产生一个问题一一信号发出的参数个数为0,槽函数接收的参数个数为
1,由于0<1,这样运行起来一定会报错(原因是信号发出的参数个数一定要大于槽
函数接收的参数个数)。解决这个问题就是本节的重点:自定义参数的传递。
from PyQt5.QtWidgets import QMainWindow, QPushButton, QWidget, QMessageBox, QApplication, QHBoxLayout
import sysclass 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_())
运行结果
装饰器信号与槽
所谓装饰器信号与槽,就是通过装饰器的方法来定义信号和槽函数。具体的使
用方法如下:
@PyQt5.QtCore.pyqtSlot(参数)
def on_发送者对象名称_发射信号名称(self,参数):pass
这种方法有效的前提是下面的函数已经执行:
QMetaObject.connectSlotsByName(QObject)
在上面代码中,“发送者对象名称”就是使用setObjectName函数设置的名称,
因此自定义槽函数的命名规则也可以看成:on+使用setObjectName设置的名称+
信号名称。接下来看具体的使用方法。
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication,QWidget,QHBoxLayout,QPushButton
import sysclass 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_())
运行效果
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication,QWidget,QHBoxLayout,QPushButton
import sysclass 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_())
运行效果同上。
信号与槽的断开和连接
# -*- coding: utf-8 -*-"""【简介】信号槽N对N连接、断开连接示例"""from PyQt5.QtCore import QObject, pyqtSignalclass 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连接到信号sin1self.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连接同一个槽sin1Callself.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 sysclass 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_())
运行结果
from PyQt5 import QtCore, QtGui, QtWidgetsclass 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.translateForm.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>"))
import sys
from PyQt5.QtWidgets import QApplication,QMainWindow
from MainWinSignalSlog02 import Ui_Form
from PyQt5.QtCore import pyqtSignal,Qtclass 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方法中继续发射该信号。
多线程中信号与槽的使用
from PyQt5.QtWidgets import QApplication,QWidget
from PyQt5.QtCore import QThread,pyqtSignal
import sysclass 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=Nonedef setIdentity(self,text):self.identity=textdef 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-=1if __name__ == '__main__':app = QApplication(sys.argv)win=Main()win.show()sys.exit(app.exec_())
运行结果:
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 sysclass 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_())
运行结果