Прослушайте новые сигнальные соединения на pyqtBoundSignal - PullRequest
1 голос
/ 06 апреля 2019

У меня есть приложение PyQt5, которое имеет дополнительные функциональные возможности (скажем, кнопку), которое отображается только тогда, когда приложению явно сказано «Включите эту опцию».Когда эта кнопка вызывается, приложение выполняет некоторое вычисление и затем выдает сигнал с результатом.

Вместо того, чтобы явно передавать опцию конструктора, я хотел бы, чтобы программа отображала функциональность, когда кто-то подключается кsignal.

Рабочий пример :

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 только для чтения.

Функция Wrapper :

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:

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.

Ответы [ 2 ]

2 голосов
/ 06 апреля 2019

Вы не должны изменять поведение встроенной функции по умолчанию.Это сделает ваш код трудным для понимания и поддержки из-за скрытой логики (почему сигнал делает кнопку видимой?).

Но вы можете заключить соединение в метод:

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

Это явно для других разработчиков, и ваш код не похож на черную магию ...

1 голос
/ 06 апреля 2019

Класс QObject имеет виртуальные методы connectNotify и disconnectNotify , которые могут быть переопределены для обеспечения различного поведения. Реализации по умолчанию ничего не делают. Существует также метод приёмник , который дает текущий счетчик подключений для сигнала.

Демонстрация ниже использует эти методы для создания универсального класса наблюдателя соединения, который может быть присоединен к любому подклассу 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()
...