PyQt5,信号槽的应用

0.前言

信号槽机制是Qt框架的核心之一,相比直接使用回调函数,信号和槽虽然效率低一点,但更易于代码解耦,并且信号槽是线程安全的。具体的实现可以参照Qt文档及源码,其实就是通过一个链表来存储信号槽,信号emit的时候就去调用槽函数,PyQt5中任意可调用对象都可以作为槽函数。本文主要总结信号槽的应用,后续再继续补充。

PyQt信号槽具有以下功能:

  • 一个信号可以连接多个槽函数;
  • 信号也可以连接另一个信号;
  • 信号参数可以是任意Python类型;
  • 槽函数可以连接多个信号;
  • 连接可以是同步或异步的;
  • 可以跨线程;
  • 可以自由断开信号槽连接。

1.使用预定义的信号

Qt提供的组件提供了许多预定义的信号,如点击按钮、TCP有新的数据等。PyQt中,信号(特别是未绑定的信号)是类属性,当信号被引用为该类实例的属性时,PyQt5会自动将该实例绑定到该信号,以创建绑定信号。这与Python本身用于从类函数创建绑定方法的机制相同。一个绑定信号具有connect()连接、disconnect()断开连接、emit()发送信号等方法。信号也可以重载,即参数列表不同。

通过文档可以查看一个组件有哪些预定义信号(如果PyQt文档没找到可以看看C++Qt的文档,除了类型区别,基本上是差不多的 https://doc.qt.io/qt-5.12/classes.html )。使用也很简单,一个connect()函数即可,不过要注意信号和槽的参数列表要匹配,槽的参数个数可以更少,但是对应位置顺序的类型要匹配。

下面的代码使用了两个组件,按钮点击的信号,数字框的值改变信号(有重载)。

import sys
from PyQt5.QtCore import pyqtSignal,pyqtSlot
from PyQt5.QtWidgets import (
    QApplication,QWidget,QPushButton,QSpinBox,QVBoxLayout)

class MyWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self.btn=QPushButton("btn",self)
        self.spinBox=QSpinBox(self)
        self.spinBox.setRange(0,100)
        self.spinBox.setValue(50)

        vlayout=QVBoxLayout(self)
        vlayout.addWidget(self.btn)
        vlayout.addWidget(self.spinBox)
        vlayout.addStretch()

        #通过connect函数连接信号槽,disconnect函数断开连接,emit函数发送信号
        #lambda是可调用对象,这里作为槽函数
        self.btn.clicked.connect(lambda :print("btn clicked"))

        #使用重载的信号可以用[type]来指定
        self.spinBox.valueChanged[int].connect(lambda value:print("lambda:"+str(value)))
        self.spinBox.valueChanged[str].connect(self.theValueChanged)

    #指定函数为槽函数,这在Qt4是常用的方式,到了Qt5开始支持直接用可调用对象
    @pyqtSlot(str)
    def theValueChanged(self,txt):
        print("slot:"+txt)

if __name__=="__main__":
    app=QApplication(sys.argv)

    w=MyWidget()
    w.resize(300,400)
    w.show()

    sys.exit(app.exec_())

2.自定义信号槽

可以使用 pyqtSignal 将新信号定义为类属性。

PyQt5.QtCore.pyqtSignal(types[, name[, revision=0[, arguments=[]]]])

type:定义信号的C ++签名的类型。每个类型都可以是Python类型对象,也可以是C ++类型名称的字符串。或者,每个参数可以是一系列类型参数。在这种情况下,每个序列定义了不同信号重载的特征。默认信号为参数序列第一个,使用时默认信号不用特殊处理。

name:信号名称。如果省略,则使用类属性的名称(经测试,使用了name,类属性名和那么名都可以用)。只能作为关键字参数给出。

revision:导出到QML的信号的修订(机翻)。只能作为关键字参数给出。

arguments:导出到QML的信号参数名称的顺序。只能作为关键字参数给出。

返回类型为未绑定的信号。

新信号只能在QObject的子类中定义 (大部分Qt类都派生自QObject)。它们必须是类定义的一部分,并且在定义了类之后不能动态添加为类属性。

尽管PyQt5允许在连接信号时将任何可调用的Python用作槽,但有时有必要将Python方法显式标记为Qt槽函数并为其提供C ++签名。PyQt5提供了 pyqtSlot() 函数装饰器来执行此操作,将信号连接到经过修饰的Python方法具有减少使用的内存量的优点,并且速度稍快。

PyQt5.QtCore.pyqtSlot(types[, name[, result[, revision=0]]])

type: 定义槽函数C ++签名的类型。每个类型都可以是Python类型对象,也可以是C ++类型名称的字符串。

name:C ++将看到的槽函数名称。如果省略,将使用要修饰的Python方法的名称。只能作为关键字参数给出。

revision:导出到QML的插槽的修订。只能作为关键字参数给出。

result:结果的类型,可以是Python类型的对象或指定C ++类型的字符串。只能作为关键字参数给出。

import sys
from PyQt5.QtCore import pyqtSignal,pyqtSlot
from PyQt5.QtWidgets import (
    QApplication,QWidget,QPushButton,QVBoxLayout)

class MyWidget(QWidget):

    #最简单的信号定义
    aClick=pyqtSignal()

    #指定信号名
    bClick=pyqtSignal(int,name="bIsClick")

    #重载信号
    cClick=pyqtSignal([int],["QString"])

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

        self.btnA=QPushButton("A",self)
        self.btnB=QPushButton("B",self)
        self.btnC=QPushButton("C",self)
        self.btnD=QPushButton("D",self)

        vlayout=QVBoxLayout(self)
        vlayout.addWidget(self.btnA)
        vlayout.addWidget(self.btnB)
        vlayout.addWidget(self.btnC)
        vlayout.addWidget(self.btnD)
        vlayout.addStretch()

        #通过按钮关联信号发送
        #1-信号关联信号
        self.btnA.clicked.connect(self.aClick)
        #2-发送信号,带参数
        self.btnB.clicked.connect(lambda:self.bClick.emit(100))
        #3-重载信号的第一个为默认信号,不用加[]
        self.btnC.clicked.connect(lambda:self.cClick.emit(1992))
        #4-发送重载信号
        self.btnD.clicked.connect(lambda:self.cClick[str].emit("gongjianbo"))

        #关联信号槽
        self.aClick.connect(lambda:print("a click"))
        self.bIsClick.connect(lambda value:print("b click"+str(value)))
        self.cClick.connect(self.theCSlot)
        self.cClick[str].connect(self.theDSlot)

    #指定函数为槽函数,这在Qt4是常用的方式,到了Qt5开始支持直接用可调用对象
    @pyqtSlot(int)
    def theCSlot(self,value):
        print("c click:"+str(value))

    def theDSlot(self,text):
        print("d click:"+text)

if __name__=="__main__":
    app=QApplication(sys.argv)

    w=MyWidget()
    w.resize(300,400)
    w.show()

    sys.exit(app.exec_())

3.使用关键字参数连接信号槽

创建对象时,也可以通过传递一个插槽作为与信号名称相对应的关键字参数来传递信号,或者使用该pyqtConfigure()方法来连接信号 。例如,以下三个片段是等效的:

act = QAction("Action", self)
act.triggered.connect(self.on_triggered)

act = QAction("Action", self, triggered=self.on_triggered)

act = QAction("Action", self)
act.pyqtConfigure(triggered=self.on_triggered)

4.通过名称连接信号槽

PyQt5支持pyuic5生成的Python代码connectSlotsByName()最常用的功能,以自动将信号连接到符合简单命名约定的插槽。但是,在类重载Qt信号的地方(即,具有相同名称但具有不同参数的Qt信号),PyQt5需要附加信息才能自动连接正确的信号。

  • 设置objectName
  • 调用connectSlotsByName
  • 使用pyqtSlot装饰器 
  • 特殊的函数命名on_[发送者对象名称]_[发送信号名称]

(测试的时候遇到个问题是,类自身emit信号,但是却不能触发slot) 

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)

    @QtCore.pyqtSlot()    
    def on_okButton_clicked(self):
        print( "单击了OK按钮")

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

遇到重载的信号,需要这样写:

@pyqtSlot(int, name='on_spinbox_valueChanged')
def spinbox_int_value(self, i):
    pass

@pyqtSlot(str, name='on_spinbox_valueChanged')
def spinbox_qstring_value(self, s):
    pass

5.参考

Qt文档:https://doc.qt.io/qt-5.12/signalsandslots.html

PyQt文档:https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html

博客:https://blog.csdn.net/broadview2006/article/details/80132757

发布了106 篇原创文章 · 获赞 31 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/gongjianbo1992/article/details/104400410