网络编程
位置:首页>> 网络编程>> Python编程>> python GUI库图形界面开发之PyQt5信号与槽机制、自定义信号基础介绍

python GUI库图形界面开发之PyQt5信号与槽机制、自定义信号基础介绍

作者:追逐阳光的风  发布时间:2022-04-18 06:08:49 

标签:python,PyQt5

信号和槽机制是 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_())

程序运行的结果:

python GUI库图形界面开发之PyQt5信号与槽机制、自定义信号基础介绍

控件说明:

控件类型控件名称作用
QLCDNumberlcdNumber显示slider滑动之后的数据
QProgressBarpBar显示slider滑动之后的数据(百分比数据)
QSliderslider滑动块调整数据
QPushButtonbuttonSave在saveLabel显示保存的状态”Saved”
QPushButtonbuttonRun在runLabel显示运行的状态”Running”
QPushButtonbuttonDisconnect解除slider.valueChanged信号的绑定
QPushButtonbuttonConnect连接slider.valueChanged信号的绑定
QPushButtonbuttonStop清除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")

示例说明:

自定义信号的一般流程如下:

  1. 定义信号

  2. 定义槽函数

  3. 绑定信号和槽

  4. 发射信号

通过代码示例来了解一下信号的自定义过程:


#-*- 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_())

运行该函数之后的效果如下:

python GUI库图形界面开发之PyQt5信号与槽机制、自定义信号基础介绍

示例说明:

通过一个模拟打印的界面来详细说明一下关于信号的自定义,在打印的时候可以设定打印的分数,纸张类型,触发“打印”按钮之后,将执行结果显示到右侧;通过全屏预览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

0
投稿

猜你喜欢

手机版 网络编程 asp之家 www.aspxhome.com