Events不只由用户操作app来生成,还有其他方式,如网络连接、定时器等。
Application对象使用exec_方法后,程序进入主循环,主循环捕捉事件并发送到目标对象
事件模型中有三个参与者,事件源、事件、事件目标
Event source 将处理事件的任务委托(delegate)给 event target
Slot 插槽
一个slot可以是一个python的可执行对象,当它所连接的signal发生时会执行
信号与槽2
Pyqt开发过程中,主要的代码也就是实现槽函数的地方。
但是!!!不只可以发送自定义的signal,还可以发送自定义的信号!
如下例,自定义信号与槽函数
from PyQt5.QtCore import QObject,pyqtSignal
#信号对象
class QTypeSignal(QObject):
#定义一个信号
sendmsg=pyqtSignal(str,str) #这里决定了发送信号的参数个数及类型!!!
def get(msg1,msg2 ):
print("QSlot get msg => " + msg1+' '+msg2) #这里是槽函数,任一个函数都可以成为槽函数,只要求参数对应发送源
if __name__ == '__main__':
#实例化信号对象
send=QTypeSignal()
print('_____-把信号绑定到槽函数上_)___')
send.sendmsg.connect(get) #指定槽函数为get
send.sendmsg.emit('第一参数','第二个参数')
print('_____-把信号与槽函数解绑_)___')
send.sendmsg.disconnect(get) #断开连接
send.sendmsg.emit('第一参数','第二个参数') #没有效果
可以看到,第一次执行完send.run()后执行了槽函数,输出了第二行字符串
断开后再执行send.run()就没效果了。
直接看代码感觉好像:定义信号对象好像没有什么意义。本质就是pyqtSignal对象调用emit方法进行发送,调用connect方法来绑定槽函数,使用disconnect来断开连接
但是下面代码
from PyQt5.QtCore import QObject,pyqtSignal
def get(msg1,msg2):
print("QSlot get msg => " + msg1+' '+msg2) #这里是槽函数,任一个函数都可以成为槽函数,只要求参数对应发送源
if __name__ == '__main__':
signal=pyqtSignal(str,str)
print('_____-把信号绑定到槽函数上_)___')
signal.connect(get) #指定槽函数为get
signal.emit('第一参数','第二个参数')
print('_____-把信号与槽函数解绑_)___')
signal.disconnect(get) #断开连接
signal.emit('第一参数','第二个参数') #再run就没有效果了
会报错
AttributeError: ‘PyQt5.QtCore.pyqtSignal’ object has no attribute ‘connect’
而调试发现前面的代码中的send.sendmsg是<class ‘PyQt5.QtCore.pyqtBoundSignal’>类型
查看文档可以看到pyqtBoundSignal拥有方法connect、disconnect、emit!而pyqtSignal并没有
查阅文档如下
Unbound and Bound Signals
A signal (specifically an unbound signal) is a class attribute. When a signal is referenced as an attribute of an instance of the class then PyQt5 automatically binds the instance to the signal in order to create a bound signal. This is the same mechanism that Python itself uses to create bound methods from class functions.
大意就是当signal对象作为一个类的属性时,会自动转换为Bound Signal,从而可以进行连接、发送等操作。
注意是类属性,不能是实例属性!!!
若是类定义改成如下,则仍然报相同的错误
class QTypeSignal(QObject):
#定义一个信号
def init(self):
QObject.init(self) #使用父类的初始化方法
self.sendmsg=pyqtSignal(str,str) #这里决定了发送信号的参数个数及类型!!!
另外一个要注意的地方就是必须要继承QObject类!否则又会报错如下
Objects created from a QObject can emit signals.
在GUI编程中,当改变一个控件的状态时(如单击了按钮),通常需要通知另一个控件,也就是实现了对象之间的通信。在早期的GUI编程中使用的是回调机制,在Qt中则使用一种新机制——信号与槽。
当事件或者状态发生改变时,就会发出信号。同时,信号会触发所有与这个事件(信号)相关的函数(槽)。信号与槽可以是多对多的关系。一个信号可以连接多个槽,一个槽也可以监听多个信号。
class MyWidget(QWidget): #这里继承的是QWidget,间接继承了QObject
# 无参数的信号
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])
一个类可以定义多个信号,要发送哪个信号,就emit哪个
class MyWidget(QWidget): #将信号与槽都定义在同一个类了
def setValue_NoParameters(self):
'''无参数的槽函数'''
pass
def setValue_OneParameter(self,x):
'''带一个参数(整数)的槽函数'''
pass
def setValue_OneParameter_String(self,x):
'''带一个参数(字符串)的槽函数'''
pass
def setValue_TwoParameters(self,x,y):
'''带两个参数(整数,整数)的槽函数'''
pass
def setValue_TwoParameters_String(self,x,y):
'''带两个参数(整数,字符串)槽函数'''
Pass
因为python函数不对参数进行限定,因此,调用作为槽函数时,根据发送的信息类型决定使用哪个槽函数
# 连接无参数的信号
widget.Signal_NoParameters.connect(self.setValue_NoParameters ) #使用self,这些代码应该是写在类定义里面,这里的代码都只是展示写法,没法执行。
# 连接带一个整数参数的信号
widget.Signal_OneParameter.connect(self.setValue_OneParameter)
# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[int].
connect(self.setValue_OneParameter)
# 连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[str].
connect(self.setValue_OneParameter_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_TwoParameters_String )
widget.show()
class MyWidget(QWidget):
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")
如下例,可执行
from PyQt5.QtCore import QObject , pyqtSignal
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.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)
#发射信号
self.signal1.emit()
self.signal2.emit(1)
self.signal3.emit(1,"text")
self.signal4.emit([1,2,3,4])
self.signal5.emit({"name":"wangwu","age":"25"})
self.signal6[int,str].emit(1,"text")
self.signal6[str].emit("text")
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()
有时在开发程序时经常会执行一些耗时的操作,这样就会导致界面卡顿,这也是多线程的应用范围之一——为了解决这个问题,我们可以创建多线程,使用主线程更新界面,使用子线程实时处理数据,最后将结果显示到界面上(子线程处理过程中,主线程还是可以更新界面)。
需要时再看
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)
# 将信号signal1连接到sin1Call和sin2Call这两个槽函数
self.signal1.connect(self.sin1Call)
self.signal1.connect(self.sin2Call) #signal连接两个槽函数
# 将信号signal2连接到信号signal1
self.signal2.connect(self.signal1) #连接不是对称的关系!!!
# 发射信号
self.signal1.emit() #输出两条信息,因为连接了两个槽函数
self.signal2.emit(1) #signal2作为信号源连接了signal1,因此,触发signal2时会触发signal1,再次输出两条信息
# 断开signal1、signal2信号与各槽函数的连接
self.signal1.disconnect(self.sin1Call)
self.signal1.disconnect(self.sin2Call)
self.signal2.disconnect(self.signal1)
# 将信号signal1和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()
若是改 self.signal2.connect(self.signal1) 为 self.signal1.connect(self.signal2)
则报错
因此,连接不是对称的关系!!!参数作为槽(可以是函数或者是信号),调用者为signal。
槽为signal时,它接受的参数个数一定不能超过调用者的参数个数,不然就报错(sender跟receiver的argument不匹配)
再注意signal2发送了一个参数,但是它连接的槽函数sin2Call不接收参数,这也是可以的。只要不超过sender的参数个数就行。
signals and slots(**)
展示一个简单的例子吧
编辑信号与槽,slider的值改变时发送给display槽函数
这样就实现了,拖动slider时lcd实时展示值
reimplementing event handler(重定义事件方法)
前面的两小节就是通过重定义QWidget的事件方法来实现的
如点击窗口的X时不是立即关闭窗口(重定义closeEvent),右键单击控件时弹出context menu(重定义contextMenuEvent)
事件方法都属于QWidget
如下可见QWidget支持哪些事件
这些方法有点像槽函数,它们都只接收一个参数,就是方法名字对应的事件
利用这些事件的属性来写代码,如closeEvent中利用了accept方法和ignore方法来接受/拒绝关闭,contextMenuEvent中利用了pos方法获取事件发生的坐标,下面keyPressEvent利用了事件中按了哪个按键
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Event handler')
self.show()
def keyPressEvent(self, e):
if e.key() == Qt.Key_0:
self.close()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
上面例子重定义方法 keyPressEvent 来实现按按键时的发生的事件,按0时就会关闭窗口
当然还可以重定义其他许多事件
event sender信号发送源
Sender方法是QObject类的,因此每个widget都有
返回的是该窗口内最后一次发送信号的widget,因此,同一个窗口内所有的widget的sender方法返回的都是同一个对象
import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn1 = QPushButton("Button 1", self)
btn1.move(30, 50)
btn2 = QPushButton("Button 2", self)
btn2.move(150, 50)
btn1.clicked.connect(self.buttonClicked)
btn2.clicked.connect(self.buttonClicked)
self.statusBar()
self.setGeometry(300, 300, 450, 350)
self.setWindowTitle('Event sender')
self.show()
def buttonClicked(self):
sender = self.sender()
self.statusBar().showMessage(sender.text() + ' was pressed')
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
点击按钮,使用sender方法来获知是哪个按钮触发了事件