Search code examples
pythonqtpyqtsignals-slots

PyQt5 log a signal


I am attempting to create a simple connection between a signal and slot but have found myself stuck trying to call an extra function. There are 2 options I have come to. Here is the code that calls my class.

(Option 1):

self.flip = MyClass("Flip Image")
self.flip.clicked.connect(self.do_flip)

The QWidget is then instantiated by my class, whose major purpose is to log the signal made. This is how I believe my class should be implemented:

class MyClass(QPushButton):

    def __init__(self, name: str) -> None:
        super().__init__(name)
        self._name = name

    def mousePressEvent(self, *args, **kwargs):
        self.log_info()

    def log_info(self):
        log(self._name)

What I don't understand is why the do_flip slot is never called? This makes no sense to me, so I instead tried overriding the clicked signal via

(Option 2):

class MyClass(QPushButton):

    def __init__(self, name: str) -> None:
        super().__init__(name)
        self._name = name

    def clicked(self, *args, **kwargs):
        self.log_info()
        #add connect() here?

    def log_info(self):
        log(self._name)

But with this code I get a friendly AttributeError: 'function' object has no attribute 'connect'. I can't find any documentation on the clicked method and how it calls connect. Is there a way for me to make the connection for clicked to the slot or do I have to create my own signal? I find that this will become very useful when trying to override other signals that emit specific values too.

If so this leads me to:

Last Resort - My Own Signal

The class would be called like this:

self.flip = MyClass("Flip Image")
self.flip.mysig.connect(self.do_flip)

And implemented like this:

class MyClass(QPushButton):

    mysig = pyqtSignal()

    def __init__(self, name: str) -> None:
        super().__init__(name)
        self._name = name

    def mousePressEvent(self, *args, **kwargs):
        self.mysig.emit()
        self.log_info()

    def log_info(self):
        log(self._name)

And this works. But I'm not sure my implementation makes sense. It feels kind of hacky. I'd prefer not to use this last resort option, and I'm hoping there is a cleaner option.


Solution

  • In Option 1 you are overriding the mousePressEvent method of QPushButton without calling the default implementation using super, and this I believe is the reason why the clicked signal is never emitted. For instance:

    class MyClass(QPushButton):
    
    def __init__(self, name: str) -> None:
        super().__init__(name)
        self._name = name
    
    def mousePressEvent(self, event):
        self.log_info()
        super().mousePressEvent(event)
    
    def log_info(self):
        log(self._name)
    

    In Option 2 you are declaring clicked as a normal python callable while instead it should be a pyqtSignal(). Also this is definitely not the way to go.

    The correct way to achieve what you need is to use multiple slots for the same signal, i.e:

    class MyClass(QPushButton):
    
        def __init__(self, name: str) -> None:
            super().__init__(name)
            self._name = name
    
        @pyqtSlot()
        def log_info(self):
            log(self._name)
    
    self.flip = MyClass("Flip Image")
    self.flip.clicked.connect(self.do_flip)
    self.flip.clicked.connect(self.flip.log_info)