PyQt5 信号槽装饰器示例

PyQt5 signal-slot decorator example

我目前正在创建一个生成 pyqtSignal(int) 和 pyqtSlot(int) 的 class。难点在于创建一个发出特定值的信号。

假设我想生成类似于以下简单示例的内容:

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

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

出于特定原因,我想使用 pyqtSignal() 工厂生成我自己的信号,并获得了不错的文档here. The only problem however, is that the very simple example没有为如何发出特定信号提供坚实的基础。

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

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

这就是我想要的:

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

如你所见。我给小部件起了一个名字,因为我发现这很有用,而且我还尝试从 class 中实例化 QWidget 以简化代码。

这就是我希望 main class 生成第一个示例中的小部件的方式:

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

我的解决方案可能完全离谱且想得太多了,请随时更正我的代码,以便我的信号发出滑块的值。

Why use the pyqtSlot() decorator at all when removing pyqtSlot(int) has no effect on the code? Can you give an example of when it would be necessary?

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

Although PyQt4 allows any Python callable to be used as a slot when connecting signals, it is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it. PyQt4 provides the pyqtSlot() function decorator to do this.

Connecting a signal to a decorated Python method also has the advantage of reducing the amount of memory used and is slightly faster.

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

这永远行不通

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

一定是

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

Create a metaclass that allows for the implementation of many different types of QWidget subclasses.

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

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)

Have the metaclass produce its own signal that can be called from outside the class.

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

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