PyQt5信号与槽一
信号与槽介绍
信号(Signal)和槽(Slot)是Qt中的核心机制,也是在PyQt编程中对象之间进行通信的机制。在Qt中,每一个QObject对象和PyQt中所有继承自QWidget的控件(这些都是QObject的子对象)都支持信号和槽机制。当信号发射时,连接的槽函数将会自动执行。在PyQt5中信号与槽通过object.signal.connect()方法连接
PyQt的窗口控件类中有很多内置信号,开发者也可以添加自定义信号,信号与槽有如下特点:
- 一个信号可以连接多个槽
- 一个信号可以连接另外一个信号
- 信号参数可以是任何python类型
- 一个槽可以监听多个信号
- 信号与槽的连接方式可以是同步连接,也可以是异步连接
- 信号与槽的连接可能会跨线程
- 信号可能会断开
在GUI编程中,当改变一个控件的状态时(如单击了按钮),通常需要通知另一个控件,也就是实现了对象之间的通信。在早期的GUI编程中使用的是回调机制,在Qt中则使用一种新机制–信号与槽。在编写一个类时,需要先定义类的信号与槽,在类中与槽进行连接,实现对象之间的数据传输。
定义信号
PyQt的内置信号时自动定义的。使用PyQt5.QtCore。培养前途Signal(0函数可以为QObject创建一个信号,使用pyqtSingal()函数可以把信号定义为类的属性。
1、为QObject对象创建信号
使用pyqtSignal()函数创建一个或多个重载的未绑定的信号作为类的属性,信号只能载QObject的子类中定义。
信号必须在类创建时定义,不能在类创建后作为类的属性动态添加进来。types参数表述定义信号时参数的类型,name参数表示信号的名字,该项缺省时使用类的属性名字。
使用pyqtSignal()函数创建信号时,信号可以传递多个参数,并指定信号传递参数的类型,参数类型时标准的Python数据类型(字符串、日期、布尔类型、数字、列表、元组和字典)
2、为控件创建信号
自定义控件WinForm创建一个btnClickedSignal信号
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidget import QMainWindow
class Winform(QMainWindow):
btnClickedSignal = pyqtSignal()
操作信号
使用connect()函数可以把信号绑定到槽函数上,使用disconnect()函数可以解除信号与槽函数的绑定,使用emit()函数可以发射信号。
1、 内置信号与槽的使用
所谓内置信号与槽的作用,是指在发射信号时,使用窗口控件的函数,而不是自定义的函数。在信号与槽中,可以通过QObject.signal.connet将一个QObject的信号连接到另一个QObject的槽函数。
from PyQt5.QtWidgets import *
import sys
app = QApplication([])
widget = QWidget()
def showMsg():
QMessageBox.information(widget, '信息提示框', "Ok,弹出测试信息")
btn = QPushButton("点击测试", widget)
btn.clicked.connect(showMsg)
widget.show()
sys.exit(app.exec_())
运行脚本
点击”测试“按钮
这个例子将一个按钮对象的内置clicked信号连接到自定义的槽函数showMsg(),也可以说showMsg()函数响应了一个按钮单击事件。
2、自定义信号与槽的使用
所谓自定义信号与槽的使用,是指在发射信号时,不使用窗口控件的函数,而是使用自定义的函数(简单地说,就是使用pyqtSignal类实例发射信号)。之所以要使用自定义信号与槽,是因为通过内置函数发射信号有自身的缺陷。首先,内置函数只包含一些常用的信号,有些信号的发射找不到对应的内置函数;其次,只有在特定情况下(如按钮的点击事件)才能发射这种信号;最后,内置函数传递的参数是特定的,不可以自定义。使用自定义的信号则没有这些缺陷。
在PyQt5编程中,自定义信号与槽的适用范围很灵活,比如因为业务需求,在程序中的某个地方需要发射一个信号,传递多种数据类型(实际上就是传递参数),然后在槽函数中接收传递过来的数据,这样就可以非常灵活的实现一些业务逻辑。
在QyQt5编程中,信号与槽有多种写法,以下是Python风格的写法。
from PyQt5.QtCore import *
class QTypeSignal(QObject):
# 定义一个信号
sendmsg = pyqtSignal(object)
def __init__(self):
super(QTypeSignal, self).__init__()
def run(self):
self.sendmsg.emit('Hello Pyqt5')
class QTypeSlot(QObject):
def __init__(self):
super(QTypeSlot, self).__init__()
# 槽对象中的槽函数
def get(self, msg):
print('QSlot get msg =>' + msg)
if __name__ == '__main__':
send = QTypeSignal()
slot = QTypeSlot()
# 1
print("---把信号绑定到槽函数上---")
send.sendmsg.connect(slot.get)
send.run()
# 2
print("---把信号与槽函数断开---")
send.sendmsg.disconnect(slot.get)
send.run()
运行脚本,输出
传递两个参数:
from PyQt5.QtCore import *
class QTypeSignal(QObject):
# 定义一个信号
sendmsg = pyqtSignal(str, str)
def __init__(self):
super(QTypeSignal, self).__init__()
def run(self):
self.sendmsg.emit('第一个参数', "第二个参数")
class QTypeSlot(QObject):
def __init__(self):
super(QTypeSlot, self).__init__()
# 槽对象中的槽函数
def get(self, msg1, msg2):
print('QSlot get msg =>' + msg1 + " " + msg2)
if __name__ == '__main__':
send = QTypeSignal()
slot = QTypeSlot()
# 1
print("---把信号绑定到槽函数上---")
send.sendmsg.connect(slot.get)
send.run()
# 2
print("---把信号与槽函数断开---")
send.sendmsg.disconnect(slot.get)
send.run()
运行脚本
信号与槽再细分
内置信号和槽函数
演示的那几按钮时关闭窗口,使用内置的信号和槽函数
from PyQt5.QtWidgets import *
import sys
class Winform(QWidget):
def __init__(self, parent=None):
super(Winform, self).__init__(parent)
self.setWindowTitle('内置信号/槽')
self.resize(330, 50)
btn = QPushButton('关闭', self)
btn.clicked.connect(self.close)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Winform()
win.show()
sys.exit(app.exec_())
运行脚本
在上面代码中,单击按钮时触发按钮内置的信号(clicked),绑定窗口(QWidget)内置的槽函数(self.close)
内置信号和自定义槽函数
from PyQt5.QtWidgets import *
import sys
class Winform(QWidget):
def __init__(self, parent=None):
super(Winform, self).__init__(parent)
self.setWindowTitle('内置信号和自定义槽函数')
self.resize(330, 50)
btn = QPushButton('关闭', self)
btn.clicked.connect(self.btn_close)
def btn_close(self):
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Winform()
win.show()
sys.exit(app.exec_())
运行脚本
自定义信号和内置槽函数
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSignal
import sys
class Winform(QWidget):
# 自定义信息,不带参数
button_clicked_signal = pyqtSignal()
def __init__(self, parent=None):
super(Winform, self).__init__(parent)
self.setWindowTitle('自定义信号和内置槽函数')
self.resize(330, 50)
btn = QPushButton('关闭', self)
# 连接信号与槽函数
btn.clicked.connect(self.btn_close)
# 接收信号,连接到槽函数
self.button_clicked_signal.connect(self.close)
def btn_close(self):
# 发送自定义信号,无参数
self.button_clicked_signal.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Winform()
win.show()
sys.exit(app.exec_())
自定义信号和槽函数
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSignal
import sys
class Winform(QWidget):
# 自定义信号,不带参数
button_clicked_signal = pyqtSignal()
def __init__(self, parent=None):
super(Winform, self).__init__(parent)
self.setWindowTitle('自定义信号和槽函数')
self.resize(330, 50)
btn = QPushButton('关闭', self)
# 连接信号与槽函数
btn.clicked.connect(self.btn_clicked)
# 接收信号,连接到槽函数
self.button_clicked_signal.connect(self.btn_close)
def btn_clicked(self):
self.button_clicked_signal.emit()
def btn_close(self):
# 发送自定义信号,无参数
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Winform()
win.show()
sys.exit(app.exec_())
信号与槽的高级玩法
高级自定义信号与槽
所谓高级自定义信号与槽,指的是我们可以以自己喜欢的方式定义信号与槽函数,并传递参数。自定义信号的一般流程如下:
(1)定义信号
(2)定义槽函数
(3)连接信号与槽函数
(4)发射信号
1、定义信号
通过类成员变量定义信号对象
class MyWidget(QWidget):
# 无参数的信号
Singal_NoParameter = pyqtSignal()
# 带一个参数(整数)
Singal_OneParameter = pyqtSignal(int)
# 带一个参数(整数或字符串)的重载版本的信号
Signal_OneParameter_Overload = pyqtSignal((int),[str])
# 带量参数(整数,字符串)的信号
Signal_TwoParameters = pyqtSignal(int, str)
# 带两个参数([整数,整数]或者[整数,字符串])的重载版本的信息
Signal_TwoParameters_Overload = pyqtSignal([int,int]),[int,str])
2、定义槽函数
定义一个槽函数,它有多个不同的输入参数
class MyWidget(QWidget):
def setValue_Noparameters(self):
pass
def setValue_OneParameter(self,nIndex):
"带一个参数(整数)的槽函数"
pass
def setValue_TwoParameters(self,x,y):
"带两个参数(整数,整数)的槽函数"
pass
def setValue_TwoParameters_String(self,x,szY)
"带两个参数(整数,字符串)槽函数"
pass
3、连接信号与槽函数
通过connect方法连接信号与槽函数或者可调用对象
app = QApplication(sys.argv)
widget = MyWidget()
# 连接无参数的信号
widget.Signal_NoParameters.connect(self.setValue_NoParameters)
# 连接带一个参数的信号
widget.Signal_OneParameters.connect(self.setValue_OneParameters)
# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameters_OverLoad[int].connect(self.setValue_OneParameters)
# 连接一个字符串参数,经过重载的信号
widget.Signal_NoParameters[str].connect(self.setValue_OneParameters_String)
# 连接一个信号,它有两个整数参数
widget.Signal_TwoParameters.connect(self.setValue_TwoParameters)
# 连接两个参数(整数,整数)的重载版本信号
widget.Signal_TwoParameters_Overload[int,int].connect(self.setValue_TwoParameters)
# 连接两个参数(整数,字符串)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,str].connect(self.setValue_OneParameters_String)
widget.show()
4、发射信号
通过emit方法发射信号
class MyWidget(QWidget):
def mousePressEvent(self, event):
# 发射无参数信号
self.Signal_NoParameters.emit()
# 发射一个带参数(整数)的信号
self.Signal_OneParemters.emit(1)
# 发射带一个参数(整数)的重载版本的信号
self.Signal_OneParameter_Overeload.emit(1)
# 发射带一个参数(字符串)的重载版本信号
self.Signal_OnePParameter_Overload.emit("abc")
# 发射带量参数(整数,字符串)的信号
self.Signal_TwoParameters.emit(1,"abc")
# 发射带两个参数(整数,整数)的重载版本的信号
self.Signal_TwoParameters_Overload.emit(1,2)
# 发射带两个参数(整数,字符串)的重载版本的信号
self.Signal_TwoParameters_Overload.emit(1,"abc")
5、实例
import sys
from PyQt5.QtCore import *
class CustSignal(QObject):
# 声明无参数的信号
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)
# 将信号连接到指定槽函数
self.signal1.connect(self.signalCall)
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)
# 发射信号
self.signal1.emit()
self.signal2.emit(1)
self.signal3.emit(1, "text")
self.signal4.emit([1,2,3,4])
self.signal5.emit({"name":"lisi",'age':18})
self.signal6[int,str].emit(1,'text')
self.signal6[str].emit('text')
def signalCall(self):
print("signal 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()
运行脚本
使用自定义参数
在PyQt编程过程中,经常会遇到给槽函数传递自定义参数的情况,比如有一个信号与槽函数的连接是
button.clicked.connect(show_page)
我们知道对于clicked信号来说,它是没有参数的,对于show_page函数来说,希望它可以接收参数。希望show_page函数如下:
def show_page(self,name):
print(name,"点击了")
于是就产生一个问题,信号发出的参数个数为0,槽函数接收的参数个数为1,由于0<1,这样运行起来一定会报错。
方法一:
使用lambda表达式
import sys
from PyQt5.QtWidgets import *
class WinForm(QMainWindow):
def __init__(self, parent=None):
super(WinForm, self).__init__(parent)
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_())
运行脚本
点击按钮
装饰器信号与槽
所谓装饰器信号与槽,就是通过装饰器的方法来定义信号合槽函数。
@PyQt5.QtCore.pyqtSlot(参数)
def on_发送者对象名称_发射信号名称(self,参数):
pass
这种方法有效的前提是羡慕的函数已经执行
QMetaObject.connectSlotsByName(QObject)
在上面代码中,“发送者对象名称”就是使用setObjectName函数设置的名称,因此自定义槽函数的命名规则也可以看成:on+使用setObjectName设置的名称+信号名称。
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
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)
@QtCore.pyqtSlot()
def on_okButton_clicked(self):
print("单击了OK按钮")
if __name__ == '__main__':
app = QApplication(sys.argv)
form = CustWidget()
form.show()
sys.exit(app.exec_())
运行脚本
点击ok按钮,控制台会输出
注意:
若def on_okButton_clicked(self):中okButton是别的名称,则不会有打印信息
代码解析:
QMetaObject.connectSlotByName(QObject)
它是在PyQt5中根据信号名称自动连接到参函数的核心代码。这行代码用来将QObject中的子孙对象的某些信号按照其objectName连接到相应的槽函数。
信号与槽的断开和连接
有时候基于某些原因,想要临时或永久断开某个信号与槽的连接。
from PyQt5.QtCore import *
class SignalClass(QObject):
# 声明无参数的信号
signal1 = pyqtSignal()
# 声明带一个int类型参数的信号
signal2 = pyqtSignal(int)
def __init__(self, parent=None):
super(SignalClass, self).__init__(parent)
# 将信号signal1连接到sin1Call和sin2Call这两个槽函数
self.signal1.connect(self.sin1Call)
self.signal1.connect(self.sin2Call)
# 将信号signal2连接到signal1
self.signal2.connect(self.signal1)
# 发射信号
self.signal1.emit()
self.signal2.emit(1)
# 断开signal1、signal2信号与各槽函数连接
self.signal1.disconnect(self.sin1Call)
self.signal1.disconnect(self.sin2Call)
self.signal2.disconnect(self.signal1)
# 将信号signall和signal2连接到同意给槽函数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()
运行脚本