PyQt5 signal-slot decorator example

15,140

Why use the pyqtSlot() decorator at all when removing pyqtSlot(int) has no effect on the code? Can you give an example of when it would be necessary?

Already addressed in your previous question, but I'll re-iterate again.

Although PyQt4 allows any Python callable to be used as a slot when connecting signals, it is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it. PyQt4 provides the pyqtSlot() function decorator to do this.

Connecting a signal to a decorated Python method also has the advantage of reducing the amount of memory used and is slightly faster.

Also, as addressed in your previous question, you cannot create signals as instance variables, they must be class attributes.

This will never work

def setSignal(self,type):
    self.signal = pyqtSignal(type)

It must be

class MyWidget(QWidget):
    signal = pyqtSignal(int)

Create a metaclass that allows for the implementation of many different types of QWidget subclasses.

You cannot define a metaclass for QObjects, since they already use their own metaclass (this is what allows the signal magic to work). However, you can still make a QObject subclass factory using the type function.

def factory(QClass, cls_name, signal_type):
    return type(cls_name, (QClass,), {'signal': pyqtSignal(signal_type)})

MySlider = factory(QSlider, 'MySlider', int)
self.sld = MySlider(Qt.Horizontal)
self.sld.signal.connect(self.on_signal)

Have the metaclass produce its own signal that can be called from outside the class.

In short, don't do this. You shouldn't be emitting signals from outside the class. The factory example above isn't very useful, because you're not defining custom methods on those classes to emit those signals. Typically, this is how you would utilize custom signals

class MyWidget(QWidget):
    colorChanged = pyqtSignal()

    def setColor(self, color):
        self._color = color
        self.colorChanged.emit()


class ParentWidget(QWidget):

    def __init__(self):
        ...
        self.my_widget = MyWidget(self)
        self.my_widget.colorChanged.connect(self.on_colorChanged)
        # This will cause the colorChanged signal to be emitted, calling on_colorChanged
        self.my_widget.setColor(Qt.blue)

    def on_colorChanged(self):
        # do stuff
Share:
15,140
Max
Author by

Max

Updated on June 04, 2022

Comments

  • Max
    Max almost 2 years

    I am currently in the process of creating a class that produces a pyqtSignal(int) and pyqtSlot(int). The difficulty lies in creating a signal that emits a specific value.

    Suppose I want to produce something similar to the following simple example:

    import sys
    from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
    from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
        QVBoxLayout, QApplication)
    
    
    class Example(QWidget):
    
        def __init__(self):
            super().__init__()
    
            self.initUI()
    
        def printLabel(self, str):
            print(str)
    
        @pyqtSlot(int)
        def on_sld_valueChanged(self, value):
            self.lcd.display(value)
            self.printLabel(value)
    
        def initUI(self):
    
            self.lcd = QLCDNumber(self)
            self.sld = QSlider(Qt.Horizontal, self)
    
            vbox = QVBoxLayout()
            vbox.addWidget(self.lcd)
            vbox.addWidget(self.sld)
    
            self.setLayout(vbox)
            self.sld.valueChanged.connect(self.on_sld_valueChanged)
    
            self.setGeometry(300, 300, 250, 150)
            self.setWindowTitle('Signal & slot')
            self.show()
    
    if __name__ == '__main__':
    
        app = QApplication(sys.argv)
        ex = Example()
        sys.exit(app.exec_())
    

    My first question for the above code is:

    • Why use the pyqtSlot() decorator at all when removing pyqtSlot(int) has no effect on the code?
    • Can you give an example of when it would be necessary?

    For specific reasons, I would like to produce my own signal using the pyqtSignal() factory and am given decent documentation here. The only problem however, is that the very simple example does not give a solid foundation for how to emit specific signals.

    Here is what I am trying to do but have found myself lost:

    1. Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
    2. Have the metaclass produce its own signal that can be called from outside the class.

    This is what I am going for:

    from PyQt5.QtWidgets import QPushButton, QWidget
    from PyQt5.QtCore import pyqtSignal, pyqtSlot
    from PyQt5.QtWidgets import QSlider
    
    def template(Q_Type, name: str, *args):
        class MyWidget(Q_Type):
    
            def __init__(self) -> None:
                super().__init__(*args)
                self._name = name
    
            def setSignal(self,type):
                self.signal = pyqtSignal(type)
    
            def callSignal(self):
                pass
    
        return MyWidget
    

    As you can see. I give the widget a name because I find this to be useful, and I also try to instantiate the QWidget from within the class to simplify code.

    This is how I would like a main class to produce the widgets from the first example:

    import sys
    from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
    from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
        QVBoxLayout, QApplication)
    
    
    class Example(QWidget):
    
        def __init__(self):
            super().__init__()
    
            self.initUI()
    
        def printLabel(self, str):
            print(str)
    
        @pyqtSlot(int)
        def sld_valChanged(self, value):
            self.lcd.display(value)
            self.printLabel(value)
    
        def initUI(self):
    
            #instantiate the QWidgets through template class
            self.lcd = template(QLCDNumber,'lcd_display')
            self.sld = template(QSlider, 'slider', Qt.Horizontal)
    
            #create signal
            #self.sld.setSignal(int)
    
            vbox = QVBoxLayout()
            vbox.addWidget(self.lcd)
            vbox.addWidget(self.sld)
    
            self.setLayout(vbox)
    
            #connect signal - this won't send the value of the slider
            #self.sld.signal.connect(self.sld_valChanged)
            self.sld.valueChanged.connect(self.sld_valChanged)
    
            self.setGeometry(300, 300, 250, 150)
            self.setWindowTitle('Signal & slot')
            self.show()
    
    if __name__ == '__main__':
    
        app = QApplication(sys.argv)
        ex = Example()
        sys.exit(app.exec_())
    

    There are only 2 problems that need to be solved here:

    1. The template metaclass is set up incorrectly, and I am unable to instantiate the QWidget from within the metaclass. The Q_Type is telling me that its type is PyQt5.QtCore.pyqtWrapperType when it should be PyQt5.QtWidgets.QSlider.
    2. Even though I am creating a newSignal via the templated class, how do I get the QSlider to send the changed value that I want it to send. I believe this is where emit() comes into play but do not have enough knowledge as to how it is useful. I know I could make some sort of function call self.sld.emit(35) if I would like the signal to pass the value 35 to the slot function. The question becomes not how but where should I implement this function?

    I may be totally offbase and overthinking the solution, feel free to correct my code so that I can have my signal emit the value of the slider.

  • Max
    Max about 8 years
    My question then leads to: how to organize code, especially when dealing with many widgets. It seems sloppy to simply put a multitude of widgets in 1 main class.
  • Brendan Abel
    Brendan Abel about 8 years
    A typical application will have dozens of widgets, all contained in a single window or dialog. There's nothing sloppy about that. Take a look at the official Qt and PySide example applications. They'll give you an idea of how applications are organized.
  • Max
    Max about 8 years
    Suppose I want to log the color change to a file. (What I want to do is log every signal for every widget and call an outside framework). I know I can therefore call the logging function within setColor but to call a function from the other framework I need to have colorChanged pass the color into on_colorChanged.
  • Max
    Max about 8 years
    I made edits to your example class to show what it is I'm trying to do.
  • Lucky
    Lucky over 6 years
    tnx, it helped :)