【问题标题】:Custom pyqtSignal implementation自定义 pyqtSignal 实现
【发布时间】:2014-02-01 19:14:18
【问题描述】:

在 PyQt 中,您可以使用 QtCore.pyqtSignal() 创建自定义信号。

我尝试自己实现观察者模式来代替pyqtSignal,以规避它的一些限制(例如,不能动态创建)。

它在大多数情况下都有效,至少有一个区别。

这是我目前的实现

class Signal:   
    def __init__(self):
        self.__subscribers = []

    def emit(self, *args, **kwargs):
        for subs in self.__subscribers:
            subs(*args, **kwargs)

    def connect(self, func):
        self.__subscribers.append(func)  

    def disconnect(self, func):  
        try:  
            self.__subscribers.remove(func)  
        except ValueError:  
            print('Warning: function %s not removed from signal %s'%(func,self))

注意到的一件事是QObject.sender() 的工作方式有所不同。

我通常不会使用sender(),但如果它的工作方式不同,那么其他事情也可能如此。

对于常规的pyqtSignal 信号,发送者始终是信号链中最近的小部件。

在底部的示例中,您将看到两个对象,ObjectAObjectBObjectA转发来自ObjectB的信号,最终被Window接收。

对于pyqtSignalsender()接收到的对象是ObjectA,也就是转发来自ObjectB的信号。

使用上面的 Signal 类,接收到的对象是 ObjectB,链中的第一个对象。

这是为什么?

完整示例

# Using PyQt5 here although the same occurs with PyQt4

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

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

        object_a = ObjectA(self)
        object_a.signal.connect(self.listen)

        layout = QBoxLayout(QBoxLayout.TopToBottom, self)
        layout.addWidget(object_a)

    def listen(self):
        print(self.sender().__class__.__name__)


class ObjectA(QWidget):
    signal = Signal()
    # signal = pyqtSignal()
    def __init__(self, parent=None):
        super(ObjectA, self).__init__(parent)

        object_b = ObjectB()
        object_b.signal.connect(self.signal.emit)

        layout = QBoxLayout(QBoxLayout.TopToBottom, self)
        layout.addWidget(object_b)


class ObjectB(QPushButton):
    signal = Signal()
    # signal = pyqtSignal()
    def __init__(self, parent=None):
        super(ObjectB, self).__init__('Push me', parent)
        self.pressed.connect(self.signal.emit)

if __name__ == '__main__':
    import sys

    app = QApplication([])

    win = Window()
    win.show()

    sys.exit(app.exec_())

More reference

编辑:

抱歉,我应该提供一个用例。

以下是使用 pyqtSignals 的一些限制:

pyqt信号:

  1. ..仅适用于类属性
  2. ..不能在已经实例化的类中使用
  3. ..必须预先指定您希望发出的数据类型
  4. ..产生不支持关键字参数的信号和
  5. ..产生实例化后无法修改的信号

因此,我主要关心的是与基类一起使用它。

考虑以下内容。

列表类型容器小部件的 6 个不同小部件共享相同的界面,但外观和行为略有不同。基类提供基本变量和方法以及信号。

使用 pyqtSignal,您必须首先从至少 QObject 或 QWidget 继承您的基类。

问题在于,如果其中一个小部件也继承自 QPushButton,则它们都不能用作混合组件或多重继承。

class PinkListItem(QPushButton, Baseclass)

使用上面的 Signal 类,您可以创建没有任何先前继承的类(或只是对象)的基类,然后将它们用作任何派生子类的混合。

注意不要提出关于多重继承或混合是否好的问题,或者其他实现相同目标的方法。我也希望得到您对此的反馈,但也许不是这个地方。

我会更有兴趣在 Signal 类中添加位,使其与 pyqtSignal 产生的类似。

编辑 2:

刚刚注意到有人反对,所以这里有更多用例。

发出时的关键字参数。

signal.emit(5)

可以写成

signal.emit(velocity=5)

与 Builder 或任何类型的依赖注入一起使用

def create(type):
  w = MyWidget()
  w.my_signal = Signal()
  return w

松耦合

我同时使用 PyQt4 和 PyQt5。使用上面的 Signal 类,我可以为两者生成基类,而无需依赖任何一个。

【问题讨论】:

  • 为什么需要动态信号创建而不是带有自定义参数的静态信号,真的吗?
  • 我也很好奇这个用例。从表面上看,您的问题具有经典XY problem 的所有特征。
  • 我看不出您的任何代码实际上是如何与 Qt 信号/插槽交互的。我的意思是ObjectB.pressed.connect() 是,但之后的所有内容都不会与Qt 交互,它只是你的Signal 实现调用Python 函数(顺便说一下,如果你有QThreads,我认为它会完全中断)。因此,发送者对象是实际使用 qt 信号的对象,这对我来说并不奇怪。但是我一点也不知道如何让你的代码正常工作。
  • @three_pineapples:好点子,使用这种方法 QThread 似乎不起作用,喊出“QObject::setParent:无法设置父级,新父级在不同的线程中”。我很想听听你关于如何让它发挥作用的想法?
  • @MarcusOttosson 对不起,我不知道该怎么做。我什至不确定这是否可能。一个开始的地方可能是尝试模仿 qt-project.org/doc/qt-4.8/… 的行为,但即便如此,我也不确定你是否会在不修改 PyQt 源代码来做你想做的事情的情况下做出真正等效的东西。

标签: python qt pyqt pyqt4 pyqt5


【解决方案1】:

您可以使用继承自 pyqtWrapperType 的元类来执行此操作。在__new__ 内部,根据需要调用pyqtSignal() 并设置结果类的属性。

【讨论】:

  • 谢谢尼尔,下次我需要这样的东西时我会尝试一下,一旦我知道它是否有效就会接受。
猜你喜欢
  • 2014-10-14
  • 2011-06-04
  • 2010-10-29
  • 2011-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多