PyQt5信号与槽(信号与槽的高级玩法)

信号与槽的高级玩法

高级自定义信号与槽

所谓高级自定义信号与槽,指的是我们可以以自己喜欢的方式定义信号与槽函

数,并传递参数。自定义信号的一般流程如下:

(1)定义信号。

(2)定义槽函数。

(3)连接信号与槽函数。

(4)发射信号。

定义信号

通过类成员变量定义信号对象。

python 复制代码
class MyWidget(QWidget):
    # 无参数的信号
    Signal_NoParameters=pyqtSignal()
    #带一个参数(整数)的信号
    Singal_OneParameter=pyqtSignal(int)
    #带一个参数(整数或者字符串)的重载版本的信号
    Singal_OneParameter_Overload=pyqtSignal([int],[str])
    #带两个参数(整数,字符串)的信号
    Signal_TwoParameters=pyqtSignal(int,str)
    #带两个参数([整数,整数])或者[整数,字符串])的重载版本的信号
    Signal_TwoParameter_Overlaod=pyqtSignal([int,int],[int,str])

定义槽函数

定义一个槽函数,它有多个不同的输入参数。

python 复制代码
class MyWidget(QWidget):
    def setValue_NoParameters(self):
        '''无参数的槽函数'''
        pass

    def setValue_OneParameter(self,nIndex):
        '''带一个参数(整数)的槽函数'''
        pass

    def setValue_OneParameter_String(self,szIndex):
        '''带一个参数(字符串)的槽函数'''
        pass

    def setValue_TwoParameters(self,x,y):
        '''带两个参数(整数,整数)的槽函数'''
        pass

    def setValue_TwoParameters_String(self,x,szy):
        '''带两参数(整数,字符串)槽函数'''
        pass

连接信号与槽函数

通过方法连接信号与槽函数或者可调用对象。

python 复制代码
app=QApplication(sys.argv)
widget=MyWidget()
#连接无参数的信号
widget.Signal_NoParameters.connect(self.setValue_NoParameters)

#连接带一个整数参数的信号
widget.Signal_OneParameter.connect(self.setValue_OneParameter)

#连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[int].connect(self.setValue_OneParameter_Overload)

#连接带一个整数参数,经过重载信号
widget.Signal_OneParameter_Overload[str].connect(self.setValue_OneParameter_strng)

#连接带两个参数(整数,整数)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,int].connect(self.setVValue_TwoParameters_Overload)

#连接带两个参数(整数,字符串)的重载版本的信号
widget.Signal_TwoParameters_Overload[int,str].connect(self.setValue_TwoParameters_String)
widget.show()

发射信号

通过emit方法发射信号,

python 复制代码
class MyWidget(QWidget):
    
    def mousePressEvent(self,event):
        #发射无参数的信号
        self.Signal_NoParameters.emit()
        #发射带一个参数(整数)的参数
        self.Singal_OneParameters.emit(1)
        # 发射带一个参数(整数)的重载版本的信号
        self.Sinal_OneParameter_Overload.emit(1)
        # 发射带一个参数(字符串)的重载版本的信号
        self.Signal_OneParameter_Overlaod.emit("abc")
        # 发射带两个参数(整数,字符串)的信号
        self.Signal_TwoParameters.emit(1,"abc")
        # 发射带两个参数(整数,整数)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1,2)
        # 发射带两个参数(整数,字符串)的重载版本的信号
        self.Signal_TwoParameters_Overload.emit(1,"abc")

实例

python 复制代码
import sys
from PyQt5.QtWidgets import QWidget,QPushButton,QApplication
class winform(QWidget):
    def __init__(self):
        super(winform,self).__init__()
        self.setGeometry(200,300,350,50)
        self.setWindowTitle("内置信号,自定义槽的例子")

        self.btn=QPushButton("按钮文本",self)
        self.btn.clicked.connect(self.changeBtnText)

    def changeBtnText(self):
        self.btn.setText("按钮内容和宽度改变了")
        self.btn.setStyleSheet("QPushButton{max-width:200px;min-width:200px}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    qb=winform()
    qb.show()
    sys.exit(app.exec_())

运行结果

使用自定义参数

在编程过程中,经常会遇到给槽函数传递自定义参数的情况,比如有一个

信号与槽函数的连接是

python 复制代码
button1.clicked.connect(show_page)

我们知道对于clicked信号来说,它是没有参数的:对于show-page函数来说,

希望它可以接收参数。希望show-page函数像如下这样:

python 复制代码
def show_page(self,name):
	print(name,"   点击啦")

于是就产生一个问题一一信号发出的参数个数为0,槽函数接收的参数个数为

1,由于0<1,这样运行起来一定会报错(原因是信号发出的参数个数一定要大于槽

函数接收的参数个数)。解决这个问题就是本节的重点:自定义参数的传递。

python 复制代码
from PyQt5.QtWidgets import QMainWindow, QPushButton, QWidget, QMessageBox, QApplication, QHBoxLayout
import sys

class WinForm(QMainWindow):
    def __init__(self,parent=None):
        super(WinForm,self).__init__(parent)
        self.setWindowTitle("信号和槽传递额外参数例子")
        button1=QPushButton('Button1')
        button2=QPushButton('Button2')

        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)
    win=WinForm()
    win.show()
    sys.exit(app.exec_())

运行结果

装饰器信号与槽

所谓装饰器信号与槽,就是通过装饰器的方法来定义信号和槽函数。具体的使

用方法如下:

python 复制代码
@PyQt5.QtCore.pyqtSlot(参数)
def on_发送者对象名称_发射信号名称(self,参数):
	pass

这种方法有效的前提是下面的函数已经执行:

python 复制代码
QMetaObject.connectSlotsByName(QObject)

在上面代码中,"发送者对象名称"就是使用setObjectName函数设置的名称,

因此自定义槽函数的命名规则也可以看成:on+使用setObjectName设置的名称+

信号名称。接下来看具体的使用方法。

python 复制代码
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication,QWidget,QHBoxLayout,QPushButton
import sys

class CustWidget(QWidget):

    def __init__(self,parent=None):
        super(CustWidget,self).__init__(parent)

        self.okButton=QPushButton("OK",self)
        #使用setObectName设置对象名称
        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)
    win=CustWidget()
    win.show()
    sys.exit(app.exec_())

运行效果

python 复制代码
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication,QWidget,QHBoxLayout,QPushButton
import sys

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)
        self.okButton.clicked.connect(self.okButton_clicked)

    def okButton_clicked(self):
        print("点击了OK按钮")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win=CustWidget()
    win.show()
    sys.exit(app.exec_())

运行效果同上。

信号与槽的断开和连接

python 复制代码
# -*- coding: utf-8 -*-

"""
    【简介】
    信号槽N对N连接、断开连接示例


"""

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)

        # 信号sin1连接到sin1Call和sin2Call这两个槽
        self.signal1.connect(self.sin1Call)
        self.signal1.connect(self.sin2Call)

        # 信号sin2连接到信号sin1
        self.signal2.connect(self.signal1)

        # 信号发射
        self.signal1.emit()
        self.signal2.emit(1)

        # 断开sin1、sin2信号与各槽的连接
        self.signal1.disconnect(self.sin1Call)
        self.signal1.disconnect(self.sin2Call)
        self.signal2.disconnect(self.signal1)

        # 信号sin1和sin2连接同一个槽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()

QtDesigner神助攻:界面显示与业务逻辑的分离

前面给出的是手工输入代码的信号与槽的使用方法,因为采用这种方式介绍时

会更简单一些。如果采用Qt来介绍这些内容,那么任何一个简单的功能都

需要使用xxx.ui、xxx.PY、CalLxxx.py三个文件来实现,这样做内容会显得很乱。

在实战应用中,由于QtDesigner可以更好地实现界面显示与业务逻辑的分离,

所以能帮助我们解决大量的代码。如果能够使用QtDesigner自动创建一些信号与槽

机制,那就更好了。本节将通过一个实战性案例来介绍信号与槽是如何和QtDesigner

结合的。

本例要实现的功能是:通过一个模拟打印的界面来详细说明信号的使用,在打

印时可以设置打印的份数、纸张类型,触发"打印"按钮后,将执行结果显示在右

侧;通过QCheckBox("全屏预览"复选框)来选择是否通过全屏模式进行预览,

将执行结果显示在右侧。

这里对窗口中的控件进行简要说明,如表7-1所示。

控件类型 空间名称 作用
QSpinBox numberSpinBox 显示打印的份数
QComboBox styleCombo 显示打印的纸张类型。纸张类型包括A3,A4和A5纸
QPushButton printButton 连按emitPrintSignal函数的绑定,触发自定义信号printSignal的发射
QCheckBox previewStatus 是否全屏预览
QPushButton previewButton 连接emitPreviewSignal函数的绑定:触发自定义信号previewSignal的发射
QLabel resultLabel 显示执行结果
复制代码
from PyQt5.QtWidgets import QMainWindow,QHBoxLayout,QPushButton,QApplication,QWidget
import sys

class Winform(QMainWindow):
    def __init__(self,parent=None):
        super(Winform,self).__init__(parent)
        self.setWindowTitle('控件中的信号槽通信')

        self.button1=QPushButton('Button1')

        self.button1.clicked.connect(self.onButtonClick)

        layout = QHBoxLayout()
        layout.addWidget(self.button1)

        main_frame=QWidget()
        main_frame.setLayout(layout)
        self.setCentralWidget(main_frame)

    def onButtonClick(self):
        #sender 是发送信号的对象
        sender=self.sender()
        print(sender.text()+'  被按下了')

if __name__ == '__main__':
    app=QApplication(sys.argv)
    form=Winform()
    form.show()
    sys.exit(app.exec_())

运行结果

python 复制代码
from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(715, 225)
        self.controlsGroup = QtWidgets.QGroupBox(Form)
        self.controlsGroup.setGeometry(QtCore.QRect(10, 20, 451, 151))
        self.controlsGroup.setObjectName("controlsGroup")
        self.widget = QtWidgets.QWidget(self.controlsGroup)
        self.widget.setGeometry(QtCore.QRect(10, 40, 411, 30))
        self.widget.setObjectName("widget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(self.widget)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.numberSpinBox = QtWidgets.QSpinBox(self.widget)
        self.numberSpinBox.setObjectName("numberSpinBox")
        self.horizontalLayout.addWidget(self.numberSpinBox)
        self.styleCombo = QtWidgets.QComboBox(self.widget)
        self.styleCombo.setObjectName("styleCombo")
        self.styleCombo.addItem("")
        self.styleCombo.addItem("")
        self.styleCombo.addItem("")
        self.horizontalLayout.addWidget(self.styleCombo)
        self.label_2 = QtWidgets.QLabel(self.widget)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout.addWidget(self.label_2)
        self.printButton = QtWidgets.QPushButton(self.widget)
        self.printButton.setObjectName("printButton")
        self.horizontalLayout.addWidget(self.printButton)
        self.widget1 = QtWidgets.QWidget(self.controlsGroup)
        self.widget1.setGeometry(QtCore.QRect(10, 100, 201, 30))
        self.widget1.setObjectName("widget1")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget1)
        self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.previewStatus = QtWidgets.QCheckBox(self.widget1)
        self.previewStatus.setObjectName("previewStatus")
        self.horizontalLayout_2.addWidget(self.previewStatus)
        self.previewButton = QtWidgets.QPushButton(self.widget1)
        self.previewButton.setObjectName("previewButton")
        self.horizontalLayout_2.addWidget(self.previewButton)
        self.resultGroup = QtWidgets.QGroupBox(Form)
        self.resultGroup.setGeometry(QtCore.QRect(470, 20, 231, 151))
        self.resultGroup.setObjectName("resultGroup")
        self.resultLabel = QtWidgets.QLabel(self.resultGroup)
        self.resultLabel.setGeometry(QtCore.QRect(20, 30, 191, 101))
        self.resultLabel.setObjectName("resultLabel")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "打印控件"))
        self.controlsGroup.setTitle(_translate("Form", "打印控制"))
        self.label.setText(_translate("Form", "打印份数:"))
        self.styleCombo.setItemText(0, _translate("Form", "A3"))
        self.styleCombo.setItemText(1, _translate("Form", "A4"))
        self.styleCombo.setItemText(2, _translate("Form", "A5"))
        self.label_2.setText(_translate("Form", "纸张类型:"))
        self.printButton.setText(_translate("Form", "打印"))
        self.previewStatus.setText(_translate("Form", "全屏预览"))
        self.previewButton.setText(_translate("Form", "预览"))
        self.resultGroup.setTitle(_translate("Form", "操作结果"))
        self.resultLabel.setText(_translate("Form", "<html><head/><body><p><br/></p></body></html>"))
python 复制代码
import sys
from PyQt5.QtWidgets import QApplication,QMainWindow
from MainWinSignalSlog02 import Ui_Form
from PyQt5.QtCore import pyqtSignal,Qt

class MyMainWindow(QMainWindow,Ui_Form):
    helpSignal=pyqtSignal(str)
    printSignal=pyqtSignal(str)
    #声明一个多重载版本的信号,包括了一个带int和str类型参数的信号,以及带str参数
    previewSignal=pyqtSignal([int,str],[str])

    def __init__(self,parent=None):
        super(MyMainWindow,self).__init__(parent)
        self.setupUi(self)
        self.initUI()

    def initUI(self):
        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)

    #发射预览信号
    def emitPreviewSignal(self):
        if self.previewStatus.isChecked()==True:
            self.previewSignal[int,str].emit(1000,"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.previewSignal.emit(pList)

    def printPaper(self,list):
        self.resultLabel.setText("打印:"+"份数:"+str(list[0])+"纸张:"+str(list[1]))

    def previewPaperWithArgs(self,style,text):
        self.resultLabel.setText(str(style)+text)

    def previewPaper(self,text):
        self.resultLabe.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)
    win=MyMainWindow()
    win.show()
    sys.exit(app.exec_())

运行结果

注意

(1)自定义信号在__init__()函数之前定义。

(2)自定义信号可以传递如str、int、list、object、float、tuple、、dict等很多类

型的参数。

(3)注意signal和slot的调用逻辑,避免signal和slot之间出现死循环,比

如在slot方法中继续发射该信号。

多线程中信号与槽的使用

python 复制代码
from PyQt5.QtWidgets import QApplication,QWidget
from PyQt5.QtCore import QThread,pyqtSignal
import sys

class Main(QWidget):
    def __init__(self):
        super(Main,self).__init__()

        #创建一个线程实例并设置名称、变量、信号槽
        self.thread=MyThread()
        self.thread.setIdentity("thread1")
        self.thread.sinOut.connect(self.outText)
        self.thread.setVal(6)

    def outText(self,text):
        print(text)

class MyThread(QThread):
    sinOut=pyqtSignal(str)

    def __init__(self,parent=None):
        super(MyThread,self).__init__(parent)
        self.identity=None

    def setIdentity(self,text):
        self.identity=text

    def setVal(self,val):
        self.times=int(val)
        #执行线程的run方法
        self.start()

    def run(self):
        while self.times>0 and self.identity:
            #发射信号
            self.sinOut.emit(self.identity+"==>"+str(self.times))
            self.times-=1

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win=Main()
    win.show()
    sys.exit(app.exec_())

运行结果:

python 复制代码
thread1==>5
thread1==>4
thread1==>3
thread1==>2
thread1==>1

有时在开发程序时经常会执行一些耗时的操作,这样就会导致界面卡顿,这也

是多线程的应用范围之一一一为了解决这个问题,我们可以创建多线程,使用主线

程更新界面,使用子线程实时处理数据,最后将结果显示到界面上。

本例中,定义了一个后台线程类BackendThread来模拟后台耗时操作,在这个

线程类中定义了信号update_date。使用BackendThread线程类在后台处理数据,每

秒发射一次自定义信号update_date。

在初始化窗口界面时,定义后台线程类BackendThread,并把线程类的信号

updatedate连接到槽函数handleDisplay()。这样后台线程每发射一次信号,就可以

把最新的时间值实时显示在前台窗口的QLineEdit文本对话框中。

复制代码
from PyQt5.QtCore import QThread,pyqtSignal,QDateTime
from PyQt5.QtWidgets import QApplication,QDialog,QLineEdit
import time
import sys

class BackendThread(QThread):
    #通过类成员对象定义信号对象
    update_date=pyqtSignal(str)

    #处理要做的业务逻辑
    def run(self):
        while True:
            data=QDateTime.currentDateTime()
            currTime=data.toString("yyyy-MM-dd hh:mm:ss")
            self.update_date.emit(str(currTime))
            time.sleep(1)

class Window(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        self.setWindowTitle('pyqt5界面实时更新例子')
        self.resize(400,100)
        self.input=QLineEdit(self)
        self.input.resize(400,100)
        self.initUI()

    def initUI(self):
        #创建线程
        self.backend=BackendThread()
        #连接信号
        self.backend.update_date.connect(self.handleDisplay)
        # 开始线程
        self.backend.start()

    #将当前时间输出到文本框
    def handleDisplay(self,data):
        self.input.setText(data)

if __name__=='__main__':
    app = QApplication(sys.argv)
    window=Window()
    window.show()
    sys.exit(app.exec_())

运行结果

相关推荐
java1234_小锋24 分钟前
【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博类别信息爬取
开发语言·python·flask
BTU_YC2 小时前
Neo4j Python 驱动库完整教程(带输入输出示例)
开发语言·python·neo4j
lishaoan772 小时前
用Python实现神经网络(四)
python·神经网络·多层神经网络
曾几何时`2 小时前
分别使用Cypher与python构建neo4j图谱
开发语言·python·机器学习
请站在我身后2 小时前
无声视频自动配音效,开源模型thinksound 和mmaudio复现
人工智能·深度学习·算法·计算机视觉·aigc
屁股割了还要学2 小时前
【C语言进阶】题目练习(2)
c语言·开发语言·c++·学习·算法·青少年编程
007php0073 小时前
使用LNMP一键安装包安装PHP、Nginx、Redis、Swoole、OPcache
java·开发语言·redis·python·nginx·php·swoole
三道杠卷胡3 小时前
【AI News | 20250717】每日AI进展
人工智能·python·语言模型·github·aigc
Mr_Xuhhh3 小时前
Qt窗口(2)-工具栏
java·c语言·开发语言·数据库·c++·qt·算法
bug和崩溃我都要3 小时前
QGIS二次开发环境搭建(qgis-3.28.6+qt5.15)
qt·qgis