Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyQt5 signal-slot decorator example

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.

like image 850
Max Avatar asked Apr 06 '16 20:04

Max


1 Answers

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
like image 55
Brendan Abel Avatar answered Oct 20 '22 06:10

Brendan Abel