【问题标题】:PyQt5 signal-slot decorator examplePyQt5 信号槽装饰器示例
【发布时间】:2016-07-27 11:57:03
【问题描述】:

我目前正在创建一个生成 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(int) 对代码没有影响时,为什么还要使用 pyqtSlot() 装饰器?
  • 您能否举例说明何时需要这样做?

出于特定原因,我想使用 pyqtSignal() 工厂生成我自己的信号,并获得了不错的文档here。然而,唯一的问题是,simple example 并没有为如何发出特定信号提供坚实的基础。

这是我正在尝试做但发现自己迷路的事情:

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

这里只有2个问题需要解决:

  1. 模板元类设置不正确,我无法从元类中实例化 QWidget。 Q_Type 告诉我它的类型是 PyQt5.QtCore.pyqtWrapperType,而它应该是 PyQt5.QtWidgets.QSlider
  2. 即使我正在通过模板类创建一个 newSignal,我如何让QSlider 发送我希望它发送的更改值。我相信这就是emit() 发挥作用的地方,但对它的用途还没有足够的了解。 我知道如果我希望信号将值 35 传递给 slot 函数,我可以进行某种函数调用 self.sld.emit(35)。问题不是如何实现,而是我应该在哪里实现这个功能?

我可能完全不合时宜并且过度考虑了解决方案,请随时更正我的代码,以便我可以让我的信号发出滑块的值。

【问题讨论】:

  • 不推荐使用@pyqtSignal()。仅使用 @pyqtSlot()。查看文档如何按预期方式使用它。不要重新发明轮子。 (如果你问我,@pyqtSlot() 应该命名为 @pyQtSlot() 以与 PyQt 的命名方案保持一致。)

标签: python qt pyqt decorator signals-slots


【解决方案1】:

为什么在删除 pyqtSlot(int) 时完全使用 pyqtSlot() 装饰器 对代码没有影响?你能举个例子说明什么时候 有必要吗?

已在您的previous question 中解决,但我会再次重申。

虽然 PyQt4 允许任何 Python 可调用对象在以下情况下用作插槽 连接信号,有时需要明确标记 Python 方法作为 Qt 插槽并为 它。 PyQt4 提供了 pyqtSlot() 函数装饰器来做到这一点。

将信号连接到修饰的 Python 方法也有 减少使用的内存量的优点,并且稍微 更快。

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

这永远行不通

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

【讨论】:

  • 然后我的问题是:如何组织代码,尤其是在处理许多小部件时。简单地将大量小部件放在一个主类中似乎很草率。
  • 一个典型的应用程序会有几十个小部件,所有小部件都包含在一个窗口或对话框中。这没有什么马虎的。看看官方的 Qt 和 PySide 示例应用程序。它们会让您了解应用程序的组织方式。
  • 假设我想将颜色变化记录到一个文件中。 (我想做的是记录每个小部件的每个信号并调用外部框架)。我知道我可以因此在setColor 中调用日志记录函数,但要从其他框架调用函数,我需要让colorChangedcolor 传递给on_colorChanged
  • 我对您的示例类进行了编辑以显示我正在尝试做的事情。
  • tnx,它有帮助:)
猜你喜欢
  • 2016-07-17
  • 2019-01-24
  • 2019-04-15
  • 1970-01-01
  • 1970-01-01
  • 2013-10-08
  • 2019-04-20
  • 1970-01-01
  • 2016-07-25
相关资源
最近更新 更多