【问题标题】:Listen for new signal connections on a pyqtBoundSignal监听 pyqtBoundSignal 上的新信号连接
【发布时间】:2019-08-27 20:22:24
【问题描述】:

我有一个 PyQt5 应用程序,它具有可选功能(比如说一个按钮),只有在明确告知应用程序“打开此选项”时才会显示该功能。调用此按钮时,应用程序会进行一些计算,然后发出带有结果的信号。

我希望程序在有人连接到信号时显示功能,而不是显式传递构造函数选项。

工作示例

class ExampleApp(QWidget):
    do_something = pyqtSignal(str)

    def __init__(self, something_enabled: bool = False):
        super().__init__()

        self.button = QPushButton("Do something")
        self.button.pressed.connect(self.do_work)

        self.button.setVisible(something_enabled)

        self.setLayout(QVBoxLayout())
        self.layout().addWidget(self.button)

        self.show()

    def do_work(self):
        self.do_something.emit("Something!")

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    window = ExampleApp(something_enabled=True)
    window.do_something.connect(lambda result: print(result))
    app.exec()

我在文档中找不到任何可用选项,也找不到此功能的实现。在我自己的代码中,我尝试直接更改连接函数,但我得到一个只读的 AttributeError。

包装函数

self.original_connect = self.do_something.connect

def new_connect(slot, type=None, no_receiver_check=False):
    print("New connection!")
    self.original_connect(slot, type, no_receiver_check)

self.do_something.connect = new_connect

属性错误

AttributeError: 'PyQt5.QtCore.pyqtBoundSignal' object attribute 'connect' is read-only

我还创建了一个工作包装类,但我担心未知的后果。

包装类

class SignalWrapper(QObject):

    new_connection = pyqtSignal(object)

    def __init__(self, signal_to_wrap):
        super().__init__()
        self.wrapped_signal = signal_to_wrap
        self.signal = self.wrapped_signal.signal

    def connect(self, slot, type=None, no_receiver_check=False):
        self.new_connection.emit(slot)
        return self.wrapped_signal.connect(slot)

    def disconnect(self, slot=None): # real signature unknown; restored from __doc__
        return self.wrapped_signal.disconnect(slot)

    def emit(self, *args): # real signature unknown; restored from __doc__
        return self.wrapped_signal.emit(*args)

    def __call__(self, *args, **kwargs): # real signature unknown
        return self.wrapped_signal.__call__(*args, **kwargs)

    def __getitem__(self, *args, **kwargs): # real signature unknown
        return self.wrapped_signal.__getitem__(*args, **kwargs)

    def __repr__(self, *args, **kwargs): # real signature unknown
        return self.wrapped_signal.__repr__(*args, **kwargs)     

ExampleApp 的修改部分

class ExampleApp(QWidget):
    do_something = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.do_something = SignalWrapper(self.do_something)

我想知道在 PyQt 中是否有一种优雅的方式来拦截信号连接。

【问题讨论】:

    标签: python pyqt pyqt5 signals-slots


    【解决方案1】:

    您不应更改内置函数的默认行为。由于隐藏的逻辑,它会使您的代码难以理解和维护(为什么信号会使按钮可见?)。

    但是,您可以将连接包装在一个方法中:

    class ExampleApp(QWidget):
        def turnOnOption(self, callback):
            """
            Make button visible. Callback will be called when the work is done.
            """
            self.do_something.connect(callback)
            self.button.setVisible(True)
    
    window = ExampleApp()
    #window.do_something.connect(lambda result: print(result))
    window.turnOnOption(lambda result: print(result))
    

    这对其他开发人员来说是明确的,您的代码看起来不像黑魔法......

    【讨论】:

      【解决方案2】:

      QObject 类具有虚拟的 connectNotifydisconnectNotify 方法,可以重写它们以提供不同的行为。默认实现什么都不做。还有一个receivers 方法可以给出信号的当前连接数。

      下面的演示使用这些方法创建一个通用的连接观察器类,该类可以附加到任何QObject 子类。它监视一组信号,并在检测到初始连接或最终断开连接时发出connected 信号:

      from PyQt5.QtCore import *
      from PyQt5.QtWidgets import *
      
      class ConnectionWatcher(QObject):
          connected = pyqtSignal(str, bool)
      
          def __init__(self, target, *signals):
              if not target.findChild(ConnectionWatcher):
                  super().__init__(target)
                  self._signals = set(signals)
                  target.connectNotify = lambda s: self._notified(s, True)
                  target.disconnectNotify = lambda s: self._notified(s, False)
              else:
                  raise RuntimeError('target already has a connection watcher')
      
          def _notified(self, signal, connecting):
              name = str(signal.name(), 'utf-8')
              if name in self._signals:
                  count = self.parent().receivers(getattr(self.parent(), name))
                  if connecting and count == 1:
                      self.connected.emit(name, True)
                  elif not connecting and count == 0:
                      self.connected.emit(name, False)
      
      class ExampleApp(QWidget):
          do_something = pyqtSignal(str)
      
          def __init__(self, something_enabled: bool = False):
              super().__init__()
      
              self.button = QPushButton("Do something")
      
              watcher = ConnectionWatcher(self.button, 'pressed')
              watcher.connected.connect(self.handleConnection)
      
              c1 = self.button.pressed.connect(self.do_work)
              c2 = self.button.pressed.connect(self.do_work)
      
              self.button.pressed.disconnect(c1)
              self.button.pressed.disconnect(c2)
      
              self.setLayout(QVBoxLayout())
              self.layout().addWidget(self.button)
      
              self.show()
      
          def handleConnection(self, name, connecting):
              print('connecting:', name, connecting)
      
          def do_work(self):
              self.do_something.emit("Something!")
      
      
      if __name__ == '__main__':
      
          import sys
          app = QApplication(sys.argv)
          window = ExampleApp(something_enabled=True)
          window.do_something.connect(lambda result: print(result))
          app.exec()
      

      【讨论】:

        猜你喜欢
        • 2016-07-16
        • 2021-11-10
        • 1970-01-01
        • 2015-03-09
        • 1970-01-01
        • 2016-11-26
        • 1970-01-01
        • 2012-08-12
        • 2011-11-01
        相关资源
        最近更新 更多