4

Example:

class MyClass(QObject):
    signal_1 = pyqtSignal(str, int)
    signal_2 = pyqtSignal(int, int)
    signal_3 = pyqtSignal(str, int, int)

Let's say each of these signals is connected elsewhere to perform various functions, however, there's a specific function I want to be performed as well when any of the signals are emitted. What this indiscriminate function does cares only about, say, the last int argument emitted with the signal. The slot would ostensibly look like:

class OtherClass(QObject):

    ...

    @pyqtSlot(int)
    def unifiedResponse(self, index):
        # Here I would do something with that index

Is there a way to connect some arbitrary number of signals with arbitrary arguments either directly to a slot or to a repeater signal?

If there is a way to define a slot like so:

@pyqtSlot(???)
def unifiedResponse(self, *args):
    important_var = args[-1]

Then I could simply capture the last argument as such. However, I have been unsuccessful in determining how to form the slot signature.

Update:

I may have answered my own question, using lambdas:

signal_1.connect(lambda _, source: OtherClass.unifiedResponse(source))
signal_2.connect(lambda _, source: OtherClass.unifiedResponse(source))
signal_3.connect(lambda _, _, source: OtherClass.unifiedResponse(source))

The solution from @eyllanesc below however is preferred, as it allows for more flexibility the larger the signal count.

Connor Spangler
  • 805
  • 2
  • 12
  • 29
  • Does this answer your question? [Connecting slots and signals in PyQt4 in a loop](https://stackoverflow.com/questions/4578861/connecting-slots-and-signals-in-pyqt4-in-a-loop) – Matheus Torquato Aug 12 '20 at 16:56

2 Answers2

3

You have to set all the signatures of the signals through several pyqtSlot:

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QCoreApplication, QObject, QTimer


class MyClass(QObject):
    signal_1 = pyqtSignal(str, int)
    signal_2 = pyqtSignal(int, int)
    signal_3 = pyqtSignal(str, int, int)


class OtherClass(QObject):
    @pyqtSlot(str, int)
    @pyqtSlot(int, int)
    @pyqtSlot(str, int, int)
    def unifiedResponse(self, *args):
        print(args)


def main():
    import sys

    app = QCoreApplication(sys.argv)

    sender = MyClass()
    receiver = OtherClass()

    sender.signal_1.connect(receiver.unifiedResponse)
    sender.signal_2.connect(receiver.unifiedResponse)
    sender.signal_3.connect(receiver.unifiedResponse)

    def on_timeout():
        sender.signal_1.emit("StackOverflow", 1)
        sender.signal_2.emit(1, 2)
        sender.signal_3.emit("StackOverflow", 1, 2)

        QTimer.singleShot(1000, QCoreApplication.quit)

    QTimer.singleShot(1000, on_timeout)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

If in general you want to send several types of data then it is best to use a more generic data type such as a list (or an object):

class MyClass(QObject):
    signal_1 = pyqtSignal(list)
    signal_2 = pyqtSignal(list)
    signal_3 = pyqtSignal(list)


class OtherClass(QObject):
    @pyqtSlot(list)
    def unifiedResponse(self, args):
        print(args)


def main():
    import sys

    app = QCoreApplication(sys.argv)

    sender = MyClass()
    receiver = OtherClass()

    sender.signal_1.connect(receiver.unifiedResponse)
    sender.signal_2.connect(receiver.unifiedResponse)
    sender.signal_3.connect(receiver.unifiedResponse)

    def on_timeout():
        sender.signal_1.emit(["StackOverflow", 1])
        sender.signal_2.emit([1, 2])
        sender.signal_3.emit(["StackOverflow", 1, 2])

        QTimer.singleShot(1000, QCoreApplication.quit)

    QTimer.singleShot(1000, on_timeout)

    sys.exit(app.exec_())

Update:

There is no elegant way to access the last element, but the first ones since the slot signature must be a subset of the signal signature, for example the slot with signature "int" can accept signals with a signature that has the first type to "int", the other arguments are discarded:

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QCoreApplication, QObject, QTimer


class MyClass(QObject):
    signal_1 = pyqtSignal(int, str)
    signal_2 = pyqtSignal(int, int)
    signal_3 = pyqtSignal(int, str, int)


class OtherClass(QObject):
    @pyqtSlot(int)
    def unifiedResponse(self, index):
        print(index)


def main():
    import sys

    app = QCoreApplication(sys.argv)

    sender = MyClass()
    receiver = OtherClass()

    sender.signal_1.connect(receiver.unifiedResponse)
    sender.signal_2.connect(receiver.unifiedResponse)
    sender.signal_3.connect(receiver.unifiedResponse)

    def on_timeout():
        sender.signal_1.emit(1, "StackOverflow")
        sender.signal_2.emit(2, 2)
        sender.signal_3.emit(3, "StackOverflow", 1)

        QTimer.singleShot(1000, QCoreApplication.quit)

    QTimer.singleShot(1000, on_timeout)

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Oh man that is... not ideal. But if this is the only way then I suppose I should try approaching the problem from a different angle. – Connor Spangler Jun 03 '20 at 00:23
  • @ConnorSpangler What kind of answers did you expect? Maybe you have an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – eyllanesc Jun 03 '20 at 00:31
  • I don't believe so. I have multiple signals which indicate to a paint application when things have updated e.g. pixels or colors etc. When undoing and redoing, I want the app to switch to the appropriate tab in the application, which is identified with a variable that is already passed in these signals. Whenever these signals are emitted, one slot connected to all of them could grab that index and switch to the appropriate tab. I was really hoping there was a way to connect a signal to a slot but only pass some of the signal arguments. – Connor Spangler Jun 03 '20 at 00:34
  • @ConnorSpangler Why is the index in the last position? If you were in the first one then there is a solution. – eyllanesc Jun 03 '20 at 00:36
  • It doesn't have to be, simply a standard that arose naturally. I'm certainly willing and able to change the argument order. – Connor Spangler Jun 03 '20 at 00:37
  • Ah I have even accidentally stumbled on that behavior before. Didn't consider to use it as a feature! I found something of a solution myself using lambdas, outlined in my update. – Connor Spangler Jun 03 '20 at 00:50
0

I've been having problems to connect multiple PyQt Signals with variable arguments to a single slot.

That was the solution I found for connecting all the widgets from input_lineEdit_widgets to validate_values

class View_Controller():

    def connect_signals(self,ui):

        #This is a list of lineEdit widgets from a QtWidgets.QMainWindow (ui)
        input_lineEdit_widgets = [ui.prim_press_lineEdit, ui.gas_vol_lineEdit, ui.melt_1_temp_lineEdit,
                                 ui.melt_1_time_lineEdit, ui.melt_1_flow_lineEdit, ui.melt_2_temp_lineEdit,
                                 ui.melt_2_time_lineEdit, ui.melt_2_flow_lineEdit]

        for widgets in input_lineEdit_widgets:
            widgets.textChanged.connect(lambda *args, widgets = widgets: self.validate_values(widgets))


    def validate_values(self, widget):
        print(widget.objectName())

More details here

Matheus Torquato
  • 1,293
  • 18
  • 25