文章目录
- 一、Qt 的信号(signals)和槽(slots)
- 二、信号槽实现
- 1. 示例1:无参数信号
- 2. 示例2:带参数信号
- 三、信号槽的断开与重连
- 四、控件之间的通信
- 五、自定义信号
- 1. 自定义信号(无参数)
- 2. 自定义信号(带参数)
一、Qt 的信号(signals)和槽(slots)
在Qt中,不同的控件如果需要通信,通常通过信号和槽来实现的。比如,当一个按钮控件被按下的时候,会发送一个信号,此时我们就可以把这个信号和一个函数连接起来,这样,当按钮按下的时候,这个函数就被执行了,这个函数也被称为槽函数。Qt已经设计好了一系列的信号供程序员直接使用,比如按键的按下,单击(即按下又松开),双击,比如标题栏名字的改变,对象被销毁等等都会发送一个信号,只要我们将这些信号连接一个函数,就可以让我们的程序对这些事件作出预期的响应。注意到,Qt 的底层负责事件的派送,已经槽函数的调用。
信号和槽有以下几个知识点:
- 信号可以连接信号
- 一个槽可以监听多个信号
- 一个信号可以连接多个槽
- 信号槽连接以后,可以重复的断开和连接
- 发射的信号可以带参数
注:本文使用的是pyQt5,在信号槽代码与pyQt4不同。
二、信号槽实现
1. 示例1:无参数信号
以下程序捕获一个对象销毁的信号,并打印出来。
import sys
from PyQt5.QtCore import QObject
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QIcon
class Example(QWidget):
def __init__(self):
super().__init__()
self.init()
# 0.运行一次QObject_Test()
self.QObject_Test()
# QObject_Create 创建一个 QObject 对象
def QObject_Test(self):
# 1.创建一个零时变量
obj = QObject()
# 2.定义个槽函数
def destroy_slot(self):
print('obj 被释放了');
# 3.连接槽函数
obj.destroyed.connect(destroy_slot)
def init(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('信号和槽实验')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
程序首先定一个了函数 == self.QObject_Test()== 并运行一次,这就使得在该函数内被定义的对象随着函数的运行而被创建,又随着函数的结束被销毁,在Qt中,一个对象被销毁,会发送一个信号,所以我们使用 obj.destroyed.connect(destroy_slot) 来将obj被销毁时发出的型号与destroy_slot()这个槽函数连接起来,程序运行以后,可以在控制台看到“obj 被释放了”的信息:
2. 示例2:带参数信号
示例1我们只是捕获了一个信号,但是并没有使用到参数。正如前文提及,当窗口的标题被改变的时候,也会发送一个信号,此时我们通常关心,这个窗口被改变成什么名字了?以下程序演示了如何捕捉到窗口的标题改动时间,并获取到新的窗口名称。
import sys
from PyQt5.QtWidgets import QApplication, QWidget
class Example(QWidget):
def __init__(self):
super().__init__()
self.init()
# 定义槽函数
def new_title_print(self,title):
print("现在的标题是: " + title)
def init(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle("原标题")
self.windowTitleChanged.connect(self.new_title_print) #连接窗口标题改变信号到槽函数 new_title_print()
self.setWindowTitle("新的标题!")
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
其运行结果如下:
可以看到,窗口标题为:新的标题。相关的事件信息也被打印出来,程序之所以可以打印出新标题,是因为标题改变以后,发送的信号带有标题信息,有了参数,我们可以更加灵活的设计程序。
三、信号槽的断开与重连
上文的程序示例了如何连接信号,但是,信号并不是连接以后,就不再断开的。考虑一种情况,如果我们需要设计一个给窗口标题自动加入前缀的槽函数,那么我们首先将窗口标题改变的型号连接到一个槽函数,接着在槽函数里将标题加上前缀,但是如果单纯这么实现,将会无限地修改窗口标题,因为每改一次,就触发一次修改标题,每修改一次标题,又触发一次修改标题,如此循环,直至程序错误退出。所以我们再修改标题前,先断开信号和槽的连接,当然,修改以后再连接回来即可。以下程序示例了这个功能的正确实现:
import sys
from PyQt5.QtWidgets import QApplication, QWidget
class Example(QWidget):
def __init__(self):
super().__init__()
self.init()
# 创建一个加入前缀的信号槽
def add_prefix(self,title):
# 1. 先断开
self.windowTitleChanged.disconnect(self.add_prefix)
# 2. 修改标题
self.setWindowTitle('prefix ' + title)
print("now titile is " + title)
# 3. 再次连接
self.windowTitleChanged.connect(self.add_prefix)
def init(self):
self.setGeometry(300, 300, 500, 300)
self.setWindowTitle('信号和槽实验 NO.0')
# 4. 连接信号槽
self.windowTitleChanged.connect(self.add_prefix)
self.setWindowTitle('信号和槽实验 NO.1')
self.setWindowTitle('信号和槽实验 NO.2')
self.setWindowTitle('信号和槽实验 NO.3')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
程序运行结果如下;
程序可以正常运行,这是因为槽函数在修改标题的时候,先断开了信号,使得本身修改的时候不受理标题修改的型号,切记在修改以后,重新连接好信号和槽。
四、控件之间的通信
上诉示例了3个程序,它们都是控件内部的交互,它们的连接也在类的内部。如果我们设计一个按钮,按下可以改变QWidght控件的背景颜色,则需要不同控件之间的通信,以下演示了该功能的实现:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
class Example(QWidget):
def __init__(self):
super().__init__()
self.init()
def init(self):
self.setGeometry(300, 300, 200, 200)
self.setWindowTitle("信号和槽")
#1. 创建 wid 控件,并设置背景颜色等
wid = QWidget(self);
wid.setStyleSheet("background:red;")
wid.resize(50,50)
wid.move(75,40)
#2. 创建 pushButton 控件
btn = QPushButton("改变颜色",self)
btn.move(65,110)
#3. 定义槽函数
def setColorBlue():
wid.setStyleSheet("background:Blue;")
def setColorRed():
wid.setStyleSheet("background:Red;")
#4. 连接信号槽
btn.pressed.connect(setColorBlue) # 按下连接槽函数为:设置背景为蓝色
btn.released.connect(setColorRed) # 松开连接槽函数为:设置背景会红色
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
其演示效果如下:
可以看到,当我的鼠标按下的时候,widget变成蓝色,松开后又变成了红色。注意到,这个连接是在他们父类“层次”代码区间实现的。
五、自定义信号
上文的几个示例中,我们都只是自定义的槽函数,信号是由Qt给我没提供的。事实上,我们可以自己建立一个信号。接下来示例如何自定义一个不带参数的信号以及带参数的信号。
1. 自定义信号(无参数)
import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QWidget
#注:这里信号是必须封装为类的
class Communicate(QObject):
#1. 定义一个无参数信号
printSignal = pyqtSignal()
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#2. 创建信号
self.c = Communicate()
#3. 连接信号
self.c.printSignal.connect(self.print_val)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('发送信号')
self.show()
#4. 实现槽函数
def print_val(self):
print('QWidget 被按下了')
def mousePressEvent(self, event):
#5. 发射信号
self.c.printSignal.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
其运行结果如下:
注:这里,鼠标的左键,右键,甚至滑轮的点击事件,都可以触发打印。
至此,我们多接触了两个函数:
- pyqtSignal() :用来创建一个信号
- emit():用来发射一个信号
出现emit()函数是因为在自定义信号中,我们要告知系统何时发送该信号,而系统自带的信号它本身就知道何时被发送。
2. 自定义信号(带参数)
import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QWidget
class Communicate(QObject):
#1. 在 pyqtSingal 后输入 int
# 创建一个带 int 参数的信号
printSignal = pyqtSignal(int)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.c = Communicate()
self.c.printSignal.connect(self.print_val)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('发送信号')
self.show()
#2. 定义槽函数,第二个参数为val,用来记录信号发送的值
def print_val(self,val):
#3. 打印信号的值
print('para = ',val)
def mousePressEvent(self, event):
#2. 发送信号,并写入参数的值为:123
self.c.printSignal.emit(123)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
其运行结果如下: