信号(signal)和槽(slot)是Qt的核心机制,也是在PyQt编程中对象之间进行通信的机制。在创建事件循环之后,通过建立信号和槽的连接就可以实现对象之间的通信。当信号发射(emit)时,连接的槽函数将会自动执行。
信号(signal)是在特定情况下被发射(emit)的一种通告。GUI程序设计的主要内容就是对界面上各组件发射的特定信号进行响应,只需要知道什么情况下发射了哪些信号,然后合理地去响应和处理这些信号就可以了。
槽(slot)实质上是一个函数,可以被直接调用,是对信号响应的函数。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数会被自动执行。
在Qt编程中,通过Qt信号和槽机制对鼠标或键盘在界面上的操作进行响应处理。例如,对鼠标单击按钮的执行处理信号的操作。
PyQt的窗口控件类中有很多内置信号,开发者也可以添加自定义信号。信号与槽具有如下特点:
本文仅描述主要的信号函数,具体详情可参照官方文档:Support for Signals and Slots — PyQt v5.15 Reference Guide
PyQt的内置信号是自动定义的。使用 PyQt5.QtCore.pyqtSignal()函数可以为QObject创建一个信号,使用pyqtSingnal()函数可以把信号定义为类的属性。pyqtSignal()函数信息如下图所以:
使用connect()函数可以把信号绑定到槽函数上。connect()函数信息如下图所示:
使用disconnect()函数可以解除信号与槽函数的绑定。disconnect()函数信息如下图所示:
使用emit()函数可以发射信号。emit()函数信息如下图所示:
信号与槽有三种使用方法,第一种是内置信号与槽的使用,第二种是自定义信号与槽的使用,第三种是装饰器的信号与槽的使用。由于第三种方法本质上是第一种方法的衍生,因此这里简要介绍前两种方法的使用。
Qt Designer中提供了一些最基础的信号和槽设置方法,在实际的项目开发中,信号和槽最佳的使用方式是Qt Designer和编程相结合,才能提高开发效率。
所谓内置信号与槽的使用,是指在发射信号时,使用窗口控件的函数,而不是自定义的函数。在信号与槽中,可以通过 QObject.signal.connect将一个QObject的信号连接到另一个QObject的槽函数。
可以在【编辑->Edit Signal/slot】中进行信号和槽设置。
进入信号槽编辑模式,可以直接在发射者(“Button"按钮)上按住鼠标左键不放,拖动到接收者(Form窗体)上,这样就建立起了连接,如下图所示:
接着会弹出“配置连接"对话框,如下图所示:
可以看到按钮控件会发射很多内置信号和槽,选择所需信号,然后单击“OK"按钮,就会生成对应的槽函数处理。
例如,我想实现单击按钮关闭窗口的效果,所以这里勾选“显示从QWidget继承的信号和槽"复选框。
在左侧按钮的信号栏里选择clicked()信号,在右侧的Form槽函数中选择close(),这意味着对按钮单击会发射clicked信号,这个信号会被Form窗体的槽函数close()捕捉到,并触发该窗体的close行为(也就是关闭该窗体)。
连接信号和槽成功后,会发现在Edit Signal/slot(编辑信号/槽)模式下,所创建的信号和槽关系的连线是红色的,如下图所示:
接着将UI界面转换为Python文件,这里我使用Eric 6编译,不再赘述,效果如下所示:
拓展学习:Python Qt GUI设计:将UI文件转换为Python文件的三种妙招(基础篇—2)
UI文件编译后代码如下所示:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(383, 276)
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setGeometry(QtCore.QRect(140, 120, 93, 28))
self.pushButton.setObjectName("pushButton")
self.retranslateUi(Form)
self.pushButton.clicked.connect(Form.close)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pushButton.setText(_translate("Form", "Button"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
代码中,通过connect函数连接按钮的clicked()信号和槽函数Form.close(),如下所示:
self.pushButton.clicked.connect(Form.close)
运行程序,按钮是信号发射者,当单击按钮之后会发射一个信号,通过这行代码程序内部的通信机制知道这个按钮的单击事件被连接到窗体的关闭事件上,然后通知接收者窗体,可以运行槽函数close(),实现窗口关闭。
自定义信号与槽是指在发射信号时,不使用窗口控件的函数,而是使用自定义的函数(简单地说,就是使用pyqtSignal类实例发射信号)。
之所以要使用自定义信号与槽,是因为通过内置函数发射信号有自身的缺陷,主要是以下三点:
在PyQt5编程中,自定义信号与槽的适用范围很灵活。例如,因为业务需求,在程序中的某个地方需要发射一个信号,传递多种数据类型(实际上就是传递参数),然后在槽函数中接收传递过来的数据,这样就可以非常灵活地实现一些业务逻辑。
自定义信号的一般流程如下:
使用pyqtSingnal()函数可以把信号定义为类的属性,示例代码如下所示:
#无参数的信号
signal1=pyqtSignal()
#带一个参数(整数)的信号
signal2=pyqtSignal(int)
#带两个参数(整数,字符串)的信号
signal3=pyqtSignal(int,str)
#带一个参数(列表)的信号
signal4=pyqtSignal(list)
#带一个参数(字典)的信号
signal5=pyqtSignal(dict)
#带(整数 字符串)或者(字符串)的信号
signal6=pyqtSignal([int,str],[str])
定义一个槽函数,它有多个不同的输入参输数,示例代码如下所示:
def signalCall1( self ):
print("signal1 emit")
def signalCall2( self,val ):
print('signal2 emit,value:',val)
def signalCall3( self,val,text ):
print('signall3 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 signalCall7( self,val ):
print('signal6 ovetload emit',val)
使用connect()函数可以把信号绑定到槽函数上,示例代码如下所示:
#信号与槽函数的链接
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.signalCall7)
使用emit()函数可以发射信号,示例代码如下所示:
#信号发射
self.signal1.emit()
self.signal2.emit(1)
self.signal3.emit(1,'第三个')
self.signal4.emit([1,2,3,4])
self.signal5.emit({"name":'JIA','age':'21'})
self.signal6[int,str].emit(1,"第六")
self.signal6[str].emit('第六')
将上述片段代码,整合,非常简单,各位可以看着理解。
from PyQt5.QtCore import QObject,pyqtSignal
class CusSignal(QObject):
#无参数的信号
signal1=pyqtSignal()
#带一个参数(整数)的信号
signal2=pyqtSignal(int)
#带两个参数(整数,字符串)的信号
signal3=pyqtSignal(int,str)
#带一个参数(列表)的信号
signal4=pyqtSignal(list)
#带一个参数(字典)的信号
signal5=pyqtSignal(dict)
#带(整数 字符串)或者(字符串)的信号
signal6=pyqtSignal([int,str],[str])
def __init__(self,parent=None):
super(CusSignal, self).__init__(parent)
#信号与槽函数的链接
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.signalCall7)
#信号发射
self.signal1.emit()
self.signal2.emit(1)
self.signal3.emit(1,'第三个')
self.signal4.emit([1,2,3,4])
self.signal5.emit({"name":'JIA','age':'21'})
self.signal6[int,str].emit(1,"第六")
self.signal6[str].emit('第六')
#槽函数
def signalCall1( self ):
print("signal1 emit")
def signalCall2( self,val ):
print('signal2 emit,value:',val)
def signalCall3( self,val,text ):
print('signall3 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 signalCall7( self,val ):
print('signal6 ovetload emit',val)
if __name__ == '__main__':
custSignal=CusSignal()
运行效果如下所示:
关于信号与槽的使用方法就讲到这了,在后面的实践中我们再细聊,感兴趣小伙伴可看看以下的拓展学习链接~
参考资料、拓展学习:
《Python QT GUI与数据可视化编程》作者:杨海玲责任编辑 (中国)王维波 栗宝
《PyQt5快速开发与实战》王硕