python GUI库图形界面开发之PyQt5信号与槽机制、自定义信号基础介绍
作者:追逐阳光的风 发布时间:2022-04-18 06:08:49
信号和槽机制是 QT 的核心机制,要精通 QT 编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性,也是 QT 区别于其它工具包的重要地方。
在linux、windows等 GUI 工具包中,GUI组件都会注册回调函数用于处理组件所触发的动作,通常是注册对应的函数的函数指针。在之前关于Button的文章中提到了信号与槽的机制的使用,通过该机制可以很好的将组件的信号(如button的clocked、toggled、pressed等)和处理该信号的槽关联起来。通过 信号与槽机制,能够让我们很简洁和快速的来完成相关的功能。
信号和槽是用来在对象间传递数据的方法:当一个特定事件发生的时候,signal会被emit出来,slot调用是用来响应相应的signal的。Qt中对象已经包含了许多预定义的 signal(基本组件都有各自特有的预定义的信号),根据使用的场景我们可以添加新的signal。Qt的对象中已经包含了许多预定义的槽函数,但我们也根据使用的场景添加新的槽函数。
信号
当对象的状态发生改变的时候,信号就由该对象发射 (emit) 出去。当一个信号被发射(emit)时候,与其关联的槽函数被立刻执行。其中该对象只负责发送信号,发射该信号的对象并不知道是那个对象在接收这个信号。这样保证了对象与对象之间的低耦合。
如果存在信号和多个槽函数相关联的时候,当信号被发射时,这些槽的执行顺序将会是随机的、不确定的。
槽
用于接受信号,而且槽只是普通的对象成员函数。当和槽连接的信号被发射时,槽会被调用。一个槽并不知道是否有任何信号与自己相连接。
信号和槽的绑定
通过调用 QObject 对象的 connect 函数来将某个对象的信号与另外一个对象的槽函数相关联,这样当发射者发射信号时,接收者的槽函数将被调用。该函数的定义如下::
connect(slot[, type=PyQt5.QtCore.Qt.AutoConnection[, no_receiver_check=False]])
Parameters:
slot – the slot to connect to, either a Python callable or another bound signal.
type – the type of the connection to make.
no_receiver_check – suppress the check that the underlying C++ receiver instance still exists and deliver the signal anyway.
当信号与槽没有必要继续保持关联时,我们可以使用 disconnect 函数来断开连接。其定义如下:
disconnect([slot])
Parameters: slot – the optional slot to disconnect from, either a Python callable or another bound signal. If it is omitted then all slots connected to the signal are disconnected.
信号和槽的特点
1、一个信号可以连接到多个槽;
当信号发出后,槽函数都会被调用,但是调用的顺序是随机的,不确定的。
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
QSlider数据的变化同时绑定在setValue()和display()两个槽上。
2、多个信号可以连接到同一个槽;
其中任何一个信号发出,槽函数都会被执行。
self.buttonOn.clicked.connect(self.showMessage)
self.buttonOff.clicked.connect(self.showMessage)
showMessage()同时绑定在两个button的clicked信号上
3、信号的参数可以是任何的Python类型;
如list,dict等python独有的类型。自定义信号的时候举例说明。
4、信号和槽的连接可以被移除;
比如断开某个特定信号的关联。
self.buttonOn.clicked.connect(self.showMessage)
5、信号可以和另外一个信号进行关联;
第一个信号发出后,第二个信号也同时发送。比如关闭系统的信号发出之后,同时会发出保存数据的信号。
代码示例:
关于信号和槽的式样代码如下:
#-*- coding:utf-8 -*-
'''
Signal & Slot
'''
__author__ = 'Tony Zhu'
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,QGridLayout,QLabel,QHBoxLayout, QGroupBox,
QVBoxLayout, QApplication,QProgressBar,QPushButton,QMessageBox)
class SignalSlot(QWidget):
def __init__(self):
super(SignalSlot,self).__init__()
self.initUI()
def initUI(self):
self.controlsGroup = QGroupBox("运行样本")
self.lcdNumber = QLCDNumber(self)
self.slider = QSlider(Qt.Horizontal, self)
self.pBar = QProgressBar(self)
vbox = QVBoxLayout()
vbox.addWidget(self.pBar)
vbox.addWidget(self.lcdNumber)
vbox.addWidget(self.slider)
self.controlsGroup.setLayout(vbox)
controlsLayout = QGridLayout()
self.label1 = QLabel("保存状态:")
self.saveLabel = QLabel()
self.label2 = QLabel("运行状态:")
self.runLabel = QLabel()
self.buttonSave = QPushButton("保存")
self.buttonRun = QPushButton("运行")
self.buttonStop = QPushButton("停止")
self.buttonDisconnect = QPushButton("解除关联")
self.buttonConnect = QPushButton("绑定关联")
controlsLayout.addWidget(self.label1,0,0)
controlsLayout.addWidget(self.saveLabel,0,1)
controlsLayout.addWidget(self.label2,1,0)
controlsLayout.addWidget(self.runLabel,1,1)
controlsLayout.addWidget(self.buttonSave,2,0)
controlsLayout.addWidget(self.buttonRun,2,1)
controlsLayout.addWidget(self.buttonStop,2,2)
controlsLayout.addWidget(self.buttonDisconnect,3,0)
controlsLayout.addWidget(self.buttonConnect,3,1)
layout = QHBoxLayout()
layout.addWidget(self.controlsGroup)
layout.addLayout(controlsLayout)
self.setLayout(layout)
self.buttonRun.clicked.connect(self.buttonSave.clicked)
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
self.buttonSave.clicked.connect(self.showMessage)
self.buttonRun.clicked.connect(self.showMessage)
self.buttonDisconnect.clicked.connect(self.unbindConnection)
self.buttonConnect.clicked.connect(self.bindConnection)
self.buttonStop.clicked.connect(self.stop)
self.setGeometry(300, 500, 500, 180)
self.setWindowTitle('信号和槽')
def showMessage(self):
if self.sender().text() == "保存":
self.saveLabel.setText("Saved")
elif self.sender().text() == "运行":
self.saveLabel.setText("Saved")
self.runLabel.setText("Running")
def unbindConnection(self):
self.slider.valueChanged.disconnect()
def bindConnection(self):
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
def stop(self):
self.saveLabel.setText("")
self.runLabel.setText("")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SignalSlot()
ex.show()
sys.exit(app.exec_())
程序运行的结果:
控件说明:
控件类型 | 控件名称 | 作用 |
---|---|---|
QLCDNumber | lcdNumber | 显示slider滑动之后的数据 |
QProgressBar | pBar | 显示slider滑动之后的数据(百分比数据) |
QSlider | slider | 滑动块调整数据 |
QPushButton | buttonSave | 在saveLabel显示保存的状态”Saved” |
QPushButton | buttonRun | 在runLabel显示运行的状态”Running” |
QPushButton | buttonDisconnect | 解除slider.valueChanged信号的绑定 |
QPushButton | buttonConnect | 连接slider.valueChanged信号的绑定 |
QPushButton | buttonStop | 清除saveLabel和runLabel的信息 |
示例说明:
程序样本运行的界面逻辑,先设定运行的程序样本数量,然后先保存后运行的逻辑状态。通过slider的滑动来改变progressBar和LCD的显示数据;“保存”按钮保存运行的样本;“运行”按钮运行程序样本;“解除关联”解除slider.valueChanged信号的绑定,此时slider的滑动,不会改变progressBar和LCD的显示
示例说明:
L22~30:
self.controlsGroup = QGroupBox("运行样本")
self.lcdNumber = QLCDNumber(self)
self.slider = QSlider(Qt.Horizontal, self)
self.pBar = QProgressBar(self)
vbox = QVBoxLayout()
vbox.addWidget(self.pBar)
vbox.addWidget(self.lcdNumber)
vbox.addWidget(self.slider)
self.controlsGroup.setLayout(vbox)
实例化一个QGroupBox,在其中添加QSlider,QProgressBar,QLCDNumber控件。
L32~41:
controlsLayout = QGridLayout()
self.label1 = QLabel("保存状态:")
.....
self.buttonDisconnect = QPushButton("解除关联")
self.buttonConnect = QPushButton("绑定关联")
实例化,界面中右半部分的控件。
L58~65:
self.buttonRun.clicked.connect(self.buttonSave.clicked)
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
self.buttonSave.clicked.connect(self.showMessage)
self.buttonRun.clicked.connect(self.showMessage)
self.buttonDisconnect.clicked.connect(self.unbindConnection)
self.buttonConnect.clicked.connect(self.bindConnection)
self.buttonStop.clicked.connect(self.stop)
signal和slot进行绑定。
1、一个信号绑定多个槽:
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
slider控件的valueChanged信号,同时与QProgressBar的setValue(),QLCDNumber的display()槽函数绑定,当valueChanged信号触发的时候,这两个槽函数均会被调用。
2、多个信号绑定到一个槽:
self.buttonSave.clicked.connect(self.showMessage)
self.buttonRun.clicked.connect(self.showMessage)
buttonSave和buttonRun这两个对象的clicked信号,同时绑定到showMessage()这个槽函数。无论哪一个信号被触发,showMessage()这个槽函数均会被调用。
3、信号和槽的连接可以被移除:
self.buttonDisconnect.clicked.connect(self.unbindConnection)
当buttonDisconnect信号触发之后,与其关联的槽函数unbindConnection()中就会执行disconnect()方法,如下:
def unbindConnection(self):
self.slider.valueChanged.disconnect()
其中执行disconnect()的时候可以指定解除与某个特定的slot槽的关联,比如self.slider.valueChanged.disconnect(self.pBar.setValue),此时解除和QProgressBar的setValue()的关联;或者不指定,在不指定slot的场景下这样将解除和这个信号所有关联的槽。
4、信号与信号的关联:
self.buttonRun.clicked.connect(self.buttonSave.clicked)
在示例说明中提到,在运行之前要对样本进行保存,所以为了保证运行的时候执行了保存的操作,所以将buttonRun.clicked信号和buttonSave.clicked信号关联起来。
示例中在没有执行“保存”(buttonSave)的时候,执行“运行”(buttonRun),此时由于两个对象的clicked信号已经关联,所以buttonSave的clicked同样会执行。
PyQt5自定义信号
PyQt5已经自动定义了很多QT内建的信号。但是在实际的使用中为了灵活使用信号与槽机制,我们可以根据需要自定义signal。可以使用pyqtSignal()方法定义新的信号,新的信号作为类的属性。
自定义signal说明:
pyqtSignal()方法原型(PyQt官网的定义):
PyQt5.QtCore.pyqtSignal(types[, name[, revision=0[, arguments=[]]]])
Create one or more overloaded unbound signals as a class attribute.
Parameters:
types – the types that define the C++ signature of the signal. Each type may be a Python type object or a string that is the name of a C++ type. Alternatively each may be a sequence of type arguments. In this case each sequence defines the signature of a different signal overload. The first overload will be the default.
name – the name of the signal. If it is omitted then the name of the class attribute is used. This may only be given as a keyword argument.
revision – the revision of the signal that is exported to QML. This may only be given as a keyword argument.
arguments – the sequence of the names of the signal's arguments that is exported to QML. This may only be given as a keyword argument.
Return type: an unbound signal
新的信号应该定义在QObject的子类中。新的信号必须作为定义类的一部分,不允许将信号作为类的属性在类定义之后通过动态的方式进行添加。通过这种方式新的信号才能自动的添加到QMetaObject类中。这就意味这新定义的信号将会出现在Qt Designer,并且可以通过QMetaObject API实现内省。
通过下面的例子,了解一下关于signal的定义:
from PyQt5.QtCore import QObject, pyqtSignal
class NewSignal(QObject):
# 定义了一个“closed”信号,该信号没有参数据
closed= pyqtSignal()
# 定义了一个"range_changed"信号,该信号有两个int类型的参数
range_changed = pyqtSignal(int, int, name='rangeChanged')
自定义信号的发射,通过emit()方法类实现,具体参见该函数的原型:
emit(*args)
Parameters: args – the optional sequence of arguments to pass to any connected slots.
通过下面的例子,了解一下关于emit()的使用:
from PyQt5.QtCore import QObject, pyqtSignal
class NewSignal(QObject):
# 一个valueChanged的信号,该信号没有参数.
valueChanged = pyqtSignal()
def connect_and_emit_valueChanged(self):
# 绑定信号和槽函数
self.valueChanged.connect(self.handle_valueChanged)
# 发射信号.
self.trigger.emit()
def handle_valueChanged(self):
print("trigger signal received")
示例说明:
自定义信号的一般流程如下:
定义信号
定义槽函数
绑定信号和槽
发射信号
通过代码示例来了解一下信号的自定义过程:
#-*- coding:utf-8 -*-
'''
defined Signal
'''
__author__ = 'Tony Zhu'
import sys
from PyQt5.QtCore import pyqtSignal, QObject, Qt, pyqtSlot
from PyQt5.QtWidgets import QWidget, QApplication, QGroupBox, QPushButton, QLabel, QCheckBox, QSpinBox, QHBoxLayout, QComboBox, QGridLayout
class SignalEmit(QWidget):
helpSignal = pyqtSignal(str)
printSignal = pyqtSignal(list)
#声明一个多重载版本的信号,包括了一个带int和str类型参数的信号,以及带str参数的信号
previewSignal = pyqtSignal([int,str],[str])
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.creatContorls("打印控制:")
self.creatResult("操作结果:")
layout = QHBoxLayout()
layout.addWidget(self.controlsGroup)
layout.addWidget(self.resultGroup)
self.setLayout(layout)
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)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('defined signal')
self.show()
def creatContorls(self,title):
self.controlsGroup = QGroupBox(title)
self.printButton = QPushButton("打印")
self.previewButton = QPushButton("预览")
numberLabel = QLabel("打印份数:")
pageLabel = QLabel("纸张类型:")
self.previewStatus = QCheckBox("全屏预览")
self.numberSpinBox = QSpinBox()
self.numberSpinBox.setRange(1, 100)
self.styleCombo = QComboBox(self)
self.styleCombo.addItem("A4")
self.styleCombo.addItem("A5")
controlsLayout = QGridLayout()
controlsLayout.addWidget(numberLabel, 0, 0)
controlsLayout.addWidget(self.numberSpinBox, 0, 1)
controlsLayout.addWidget(pageLabel, 0, 2)
controlsLayout.addWidget(self.styleCombo, 0, 3)
controlsLayout.addWidget(self.printButton, 0, 4)
controlsLayout.addWidget(self.previewStatus, 3, 0)
controlsLayout.addWidget(self.previewButton, 3, 1)
self.controlsGroup.setLayout(controlsLayout)
def creatResult(self,title):
self.resultGroup = QGroupBox(title)
self.resultLabel = QLabel("")
layout = QHBoxLayout()
layout.addWidget(self.resultLabel)
self.resultGroup.setLayout(layout)
def emitPreviewSignal(self):
if self.previewStatus.isChecked() == True:
self.previewSignal[int,str].emit(1080," 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.printSignal.emit(pList)
def printPaper(self,list):
self.resultLabel.setText("Print: "+"份数:"+ str(list[0]) +" 纸张:"+str(list[1]))
def previewPaperWithArgs(self,style,text):
self.resultLabel.setText(str(style)+text)
def previewPaper(self,text):
self.resultLabel.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)
dispatch = SignalEmit()
sys.exit(app.exec_())
运行该函数之后的效果如下:
示例说明:
通过一个模拟打印的界面来详细说明一下关于信号的自定义,在打印的时候可以设定打印的分数,纸张类型,触发“打印”按钮之后,将执行结果显示到右侧;通过全屏预览QCheckBox来选择是否通过全屏模式进行预览,将执行结果显示到右侧。
通过点击F1快捷键,可以显示helpMessage信息。
代码分析:
L12~15:
helpSignal = pyqtSignal(str)
printSignal = pyqtSignal(list)
#声明一个多重载版本的信号,包括了一个带int和str类型参数的信号,以及带str参数的信号
previewSignal = pyqtSignal([int,str],[str])
通过pyqtSignal()定义了三个信号,helpSignal ,printSignal ,previewSignal 。其中:
helpSignal 为str参数类型的信号;
printSignal 为list参数类型的信号;
previewSignal为一个多重载版本的信号,包括了一个带int和str类型参数的信号,以及str类行的参数。
L31~36:
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)
绑定信号和槽;着重说明一下多重载版本的信号的绑定,previewSignal有两个版本previewSignal(str),previewSignal(int,str)。由于存在两个版本,从因此在绑定的时候需要显式的指定信号和槽的绑定关系。
具体如下:
self.previewSignal[str].connect(self.previewPaper)
self.previewSignal[int,str].connect(self.previewPaperWithArgs)
其中[str]参数的previewSignal信号绑定previewPaper();[int,str]的previewSignal信号绑定previewPaperWithArgs()
L72~76:
def emitPreviewSignal(self):
if self.previewStatus.isChecked() == True:
self.previewSignal[int,str].emit(1080," Full Screen")
elif self.previewStatus.isChecked() == False:
self.previewSignal[str].emit("Preview")
多重载版本的信号的发射也需要制定对应发射的版本,类似同信号的版定。
L78~82:
def emitPrintSignal(self):
pList = []
pList.append(self.numberSpinBox.value ())
pList.append(self.styleCombo.currentText())
self.printSignal.emit(pList)
如代码中所示,在信号发射的时候可以传递python数据类型的参数,在本例中传递list类型的参数pList.
L93~96:
def keyPressEvent(self, event):
if event.key() == Qt.Key_F1:
self.helpSignal.emit("help message")
通过复写keyPressEvent()方法,将F1快捷键进行功能的拓展。在windows的大部分应用,我们都会使用一些快捷键来快速的完成某些特定的功能。比如F1键,会快速调出帮助界面。那我们就可以复写keyPressEvent()方法来模拟发送所需的信号,来完成我们的对应任务.
注意事项:
自定义的信号在init()函数之前定义;
自定义型号可以传递,str、int、list、object、float、tuple、dict等很多类型的参数;
注意signal和slot的调用逻辑,避免signal和slot之间出现死循环。如在slot方法中继续发射该信号;
来源:https://blog.csdn.net/zhulove86/article/details/52530214
猜你喜欢
- 具体代码如下所示:<%@ page language="java" import="com.studen
- 概述用爬虫时,大部分网站都有一定的反爬措施,有些网站会限制每个 IP 的访问速度或访问次数,超出了它的限制你的 IP 就会被封掉。对于访问速
- Ajax 技术改变了大型商业 Web 应用程序的外观,但是许多较小的 Web 站点都不具备足够的资源重新构建完整的用户界面(UI)。Ajax
- 本文实例讲述了Python日期时间Time模块。分享给大家供大家参考,具体如下:关于时间和日期模块python程序能用很多方式处理日期和时间
- 前言相信每位家长都有所体会,因为要在孩子出生后两周内起个名字(需要办理出生证明了),估计很多人都像我一样,刚开始是很慌乱的,虽然感觉汉字非常
- 推导式comprehensions(又称解析式),是Python的一种独有特性。推导式是可以从一个数据序列构建另一个新的数据序列(的一种结构
- 在本教程中,你会学到如何把HTML的列表项(li元素)转换成下图的“便签墙”。该效果分5步实现。内核为webkit的Safari和Chrom
- HTTP状态码 摘要说明成功2××  
- 先简单说一下MP3的ID3 标记,因为主要是操作这个玩意MP3最开始的时候没有我们今天看到的那样,有歌手、年代,专集等等信息只有一些简单的参
- 密码算法程序设计实践选的SHA-1。在写的过程中遇到一丢丢关于python移位的问题,记录一下。SHA-1其中第一步需要填充消息。简单阐述一
- 每点击一次按钮,弹出一个对话框(子窗口),同时开启一个子线程来执行任务并更新对话框内容,关闭对话框则关闭对应子线程1. 建立一个简单的主界面
- 最近有在使用屏幕录制软件录制桌面,在用的过程中突发奇想,使用python能不能做屏幕录制工具,也锻炼下自己的动手能力。接下准备写使用pyth
- 一、线性回归的理论1)线性回归的基本概念线性回归是一种有监督的学习算法,它介绍的自变量的和因变量的之间的线性的相关关系,分为一元线性回归和多
- 前言当多线程访问同一个公共资源时,如果涉及到修改该公共资源的操作就可能会出现由于数据不同步导致的线程安全问题。一般情况下我们可以通过给公共资
- 1.SQL Server 2005中的存储过程并发问题问:我在SQL Server2005中遇到了并发问题。我持有车票的公共汽车上有一些空闲
- 如果你正从你的用户那里收集信息, 没有比网页表单更简单和直接的办法了。一份有良好设计的表单可以提供有价值的信息, 相反, 他们有可能把用户吓
- ASP 能快速执行你的 * 页,但你还可以通过紧缩代码和数据库连接以使它们执行更快。这是一篇关于怎样精简代码和Asp 特征以获得最快执行速度
- PS: 传送门——自定义Colorbars教程自定义colorbar(draw colorbar w
- SQL Server对上亿的表进行排序或者上亿的表之间进行join,会导致系统失去响应。◆1.我确实做了一个很大的查询,涉及的数据表有两亿条
- 网站能切换几套CSS风格早已不是什么新鲜事了。大家也都知道怎么去弄。早上发现一个有意思得站点 http://www.leemunroe.co