PyQt5信号槽装饰器示例

9
我现在正在创建一个类,它会产生一个pyqtSignal(int)和一个pyqtSlot(int)。难点在于创建一个可以发射特定值的信号。
假设我想要制作一个类似于以下简单示例的东西:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

    def printLabel(self, str):
        print(str)

    @pyqtSlot(int)
    def on_sld_valueChanged(self, value):
        self.lcd.display(value)
        self.printLabel(value)

    def initUI(self):

        self.lcd = QLCDNumber(self)
        self.sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(self.lcd)
        vbox.addWidget(self.sld)

        self.setLayout(vbox)
        self.sld.valueChanged.connect(self.on_sld_valueChanged)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        self.show()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我对以上代码的第一个问题是:

  • 为什么要使用pyqtSlot()装饰器,当移除pyqtSlot(int)对代码没有影响时?
  • 你能举个例子说明什么情况下它是必须的吗?

出于特定原因,我想使用pyqtSignal()工厂来生成自己的信号,并在这里获得了良好的文档(点击)。然而,唯一的问题是,非常简单的示例并没有为如何触发特定信号提供坚实的基础。

这就是我试图做的,但却感到迷茫:

  1. 创建一个元类,允许实现多种不同类型的QWidget子类。
  2. 使元类产生自己的信号,可以从类外调用。

我的目标是:

from PyQt5.QtWidgets import QPushButton, QWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QSlider

def template(Q_Type, name: str, *args):
    class MyWidget(Q_Type):

        def __init__(self) -> None:
            super().__init__(*args)
            self._name = name

        def setSignal(self,type):
            self.signal = pyqtSignal(type)

        def callSignal(self):
            pass

    return MyWidget

正如您所看到的,我给小部件命名,因为我发现这很有用,并且我也尝试从类中实例化QWidget以简化代码。

以下是我希望一个主类从第一个示例中生成小部件的方式:

import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

    def printLabel(self, str):
        print(str)

    @pyqtSlot(int)
    def sld_valChanged(self, value):
        self.lcd.display(value)
        self.printLabel(value)

    def initUI(self):

        #instantiate the QWidgets through template class
        self.lcd = template(QLCDNumber,'lcd_display')
        self.sld = template(QSlider, 'slider', Qt.Horizontal)

        #create signal
        #self.sld.setSignal(int)

        vbox = QVBoxLayout()
        vbox.addWidget(self.lcd)
        vbox.addWidget(self.sld)

        self.setLayout(vbox)

        #connect signal - this won't send the value of the slider
        #self.sld.signal.connect(self.sld_valChanged)
        self.sld.valueChanged.connect(self.sld_valChanged)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        self.show()

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这里只有两个问题需要解决:
  1. 模板元类设置不正确,我无法在元类中实例化QWidget。Q_Type告诉我它的类型是PyQt5.QtCore.pyqtWrapperType,而应该是PyQt5.QtWidgets.QSlider。
  2. 即使我通过模板类创建了新信号,我如何让QSlider发送我想要发送的更改值?我相信这就是emit()发挥作用的地方,但我对其有限的了解。如果我希望信号将值35传递给槽函数,我知道可以进行某种函数调用self.sld.emit(35)。问题变成了我应该在哪里实现这个函数?
我可能完全偏离了轨道并过度思考解决方案,请随意纠正我的代码,以便我可以使我的信号发出滑块的值。

1
使用@pyqtSignal()已被弃用,请仅使用@pyqtSlot()。查看文档以了解如何按预期方式使用它。不要重复造轮子。(如果你问我,@pyqtSlot()应该被命名为@pyQtSlot()以保持与PyQt的命名方案一致。) - Boštjan Mejak
1个回答

12

当连接信号时,PyQt4允许使用任何Python可调用对象作为槽函数,但有时需要明确标记Python方法为Qt槽函数并为其提供C++签名。PyQt4提供了pyqtSlot()函数装饰器来实现此功能。

将信号连接到装饰的Python方法还可以减少内存使用量并略微提高速度。

正如在您的以前的问题中所述。

此外,正如在您的以前的问题中所述,您不能将信号创建为实例变量,它们必须是类属性

这样永远不会起作用。

def setSignal(self,type):
    self.signal = pyqtSignal(type)

必须是这样的

class MyWidget(QWidget):
    signal = pyqtSignal(int)

创建一个元类,允许实现许多不同类型的QWidget子类。

无法为QObjects定义元类,因为它们已经使用自己的元类(这就是信号魔法可以工作的原因)。但是,您仍然可以使用type函数创建QObject子类工厂。

def factory(QClass, cls_name, signal_type):
    return type(cls_name, (QClass,), {'signal': pyqtSignal(signal_type)})

MySlider = factory(QSlider, 'MySlider', int)
self.sld = MySlider(Qt.Horizontal)
self.sld.signal.connect(self.on_signal)

让元类生成可以从类外部调用的信号。

简而言之,不要这样做。您不应该从类外部发出信号。上面的工厂示例并不是很有用,因为您没有在那些类上定义自定义方法来发出这些信号。通常,这是您使用自定义信号的方式。

class MyWidget(QWidget):
    colorChanged = pyqtSignal()

    def setColor(self, color):
        self._color = color
        self.colorChanged.emit()


class ParentWidget(QWidget):

    def __init__(self):
        ...
        self.my_widget = MyWidget(self)
        self.my_widget.colorChanged.connect(self.on_colorChanged)
        # This will cause the colorChanged signal to be emitted, calling on_colorChanged
        self.my_widget.setColor(Qt.blue)

    def on_colorChanged(self):
        # do stuff

我的问题是:如何组织代码,特别是当涉及到许多小部件时。简单地将大量的小部件放在一个主类中似乎很杂乱无章。 - Max
一个典型的应用程序将有数十个小部件,全部包含在单个窗口或对话框中。这并不会显得杂乱无章。看看官方的Qt和PySide示例应用程序,它们会给你一个关于应用程序如何组织的想法。 - Brendan Abel
假设我想将颜色更改记录到文件中。(我想记录每个小部件的每个信号并调用外部框架)。我知道我可以在setColor内部调用日志记录函数,但是要从其他框架调用函数,我需要让colorChangedcolor传递给on_colorChanged - Max
我对你的示例类进行了编辑,以展示我想要做的事情。 - Max
谢谢,它帮了我 :) - Lucky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接