Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

SIGNAL and SLOT communication between classes



  • I'm just starting out with my first graphical interface written in Python and pySide2. My limited experience with OO programming is NOT helping but I will persevere.
    To keep it simple for me I've started with the pySide2 tutorial examples and tried to extend them and I'm now working with t6.py which presents a Quit button and a 3x3 grid of slider/LCD pairs. When the slider value changes it emits a signal to update the LCD.
    I extended that to add a second 3x3 grid of QLineEdit objects with the intention that the signal from the slider should also update the text in the QLineEdit. (I also tried with QLabel objects equally unsuccessfully.)

    The code looks like this (with comments where I'm struggling):

    # PySide2 tutorial 6
    
    
    import sys
    from PySide2 import QtCore, QtGui, QtWidgets
    from PySide2.QtWidgets import QLineEdit
    
    
    class LCDRange(QtWidgets.QWidget):
        def __init__(self, parent=None):
            QtWidgets.QWidget.__init__(self, parent)
    
            lcd = QtWidgets.QLCDNumber(2)
            self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
            self.slider.setRange(0, 99)
            self.slider.setValue(0)
            self.connect(self.slider, QtCore.SIGNAL("valueChanged(int)"),
                         lcd, QtCore.SLOT("display(int)"))
    
            layout = QtWidgets.QVBoxLayout()
            layout.addWidget(lcd)
            layout.addWidget(self.slider)
            self.setLayout(layout)
    
    
    class NumDisplay(QtWidgets.QWidget):
        def __init__(self, r, c, slider, parent=None):
            QtWidgets.QWidget.__init__(self, parent)
    
            self.r = r
            self.c = c
            self.slider = slider
            print (f"numDisplay[{r}][{c}] refers to {self.slider}")
            num = QtWidgets.QLineEdit(f"{r}/{c}")
            num.setText(f"Co-ordinates: ({r},{c})")
            self.connect(self.slider.slider, QtCore.SIGNAL("valueChanged(int)"),
                         num, QtCore.SLOT("setText(str(int))") )             ## Fails with "QObject::connect: No such slot QLineEdit::setText(str(int))"  
     #                 num, QtCore.SLOT("setText(int)") )                     ## Fails with "QSlider::valueChanged(int) --> QLineEdit::setText((int)"
    
            layout = QtWidgets.QHBoxLayout()
            layout.addWidget(num)
            self.setLayout(layout)
    
    class MyWidget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            QtWidgets.QWidget.__init__(self, parent)
            self.setWindowTitle("pySide2 tutorial 6 - modified")
    
            quit = QtWidgets.QPushButton("Quit")
            quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
            self.connect(quit, QtCore.SIGNAL("clicked()"),
                         app, QtCore.SLOT("quit()"))
    
            sliderGrid = QtWidgets.QGridLayout()
            numberGrid = QtWidgets.QGridLayout()
            layout = QtWidgets.QVBoxLayout()
            layout.addWidget(quit)
            layout.addLayout(sliderGrid)
            layout.addLayout(numberGrid)
            self.setLayout(layout)
            slider = []
            number = []
    
            for row in range(3):
                slider.insert(row,[])
                number.insert(row,[])
                for column in range(3):
                    slider[row].insert(column, LCDRange())
                    sliderGrid.addWidget(slider[row][column], row, column)
                    number[row].insert(column, NumDisplay(row, column, slider[row][column]))
                    numberGrid.addWidget(number[row][column], row, column)
                    print(row, column)
                    print(slider[row][column])
                    print(number[row][column])
    
    
    app = QtWidgets.QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    sys.exit(app.exec_())

  • Moderators

    Try:

    num, QtCore.SLOT("setText(str))")
    

    Most probably you just need to add a custom slot to label, which will accept an integer. I don't know Python or pySide so I can't help much with that. But in general you need to subclass QLabel, add a slot along the lines of:

    // c++, sorry!
    public slots:
      void setInteger(const int value) {
        setText(QString::number(value));
      }
    

    And then you just use that new label subclass in your UI (and connect to your new slot).



  • @MNix

        self.connect(self.slider.slider, QtCore.SIGNAL("valueChanged(int)"),
                         num, QtCore.SLOT("setText(str(int))") )             ## Fails with "QObject::connect: No such slot QLineEdit::setText(str(int))"  
     #                 num, QtCore.SLOT("setText(int)") )                     ## Fails with "QSlider::valueChanged(int) --> QLineEdit::setText((int)"
    
    

    You sure it's slider.slider and not just slider? Your slider is an instance of a class which itself has a member named slider? But if Python accepts what you have put perhaps that is the case.

    The types of any arguments passed from a signal to a slot must match. To change types between int and str as you have tried will require you to use Python lambdas, which you don't want to learn yet, do you? So follow @sierdzio's advice and, for now at least, make your slot accept the int type sent by the signal and do the str() work there inside. Following his way of doing things (there are other ways), subclass QLineEdit:

    # in your subclass
    def sliderValueChanged(value):
        setText(str(value))
    

    I don't know why PySide2 tutorials are encouraging this way of using connect(). The Pythonic, easier to read syntax is:

    self.slider.valueChanged.connect(num.sliderValueChanged)
    


  • Thank you both! I was not understanding that the SLOT (what's the difference between "Slot" and "SLOT"??) belongs to the class of the target object. Now I see how I get that control. Thank you.

    @JonB. Yes, it was slider.slider. I only realised what I was accessing by that name as I wrote the question. I've renamed the parent to lcdSlider for clarity. That's the instance of LCDRange being passed to the mnQLineEdit class so the LCDRange instance in sliderGrid[x][y] is tied to the corresponding mnQLineEdit instance in numberGrid[x][y].

    I could not make the sample syntax work but the more Pythonic syntax does the job.

    class mnQLineEdit(QtWidgets.QLineEdit):
    def init(self, text):
    super().init()

    @QtCore.Slot()
    def valueChanged(self, value):
        #print(f"valueChange received {value}")
        self.setText(str(value))
    

    class NumDisplay(QtWidgets.QWidget):
    def init(self, r, c, lcdSlider, parent=None):
    QtWidgets.QWidget.init(self, parent)

        self.r = r
        self.c = c
        self.lcdSlider = lcdSlider
        print (f"numDisplay[{r}][{c}] refers to {self.lcdSlider}")
        num = mnQLineEdit(f"{r}/{c}")
        num.setText(f"Co-ordinates: ({r},{c})")
    
        # self.connect(self.lcdSlider.slider, QtCore.SIGNAL("valueChanged(i)"),
        #              num, QtCore.SLOT(num.valueChanged()) )   
    
        self.lcdSlider.slider.valueChanged.connect(num.valueChanged)
    
        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(num)
        self.setLayout(layout)
    

    ![alt texta30e2b0e-091d-498f-a892-83289a848e53-image.png ](image url)



  • @MNix
    Good that you are getting the hang of signals/slots, from Python. You will need it all the time for Qt programming.

    I don't know where you got those QtCore.SLOT("setText(str(int))") from(?). First, I haven't seen PySide2 or PyQt5 actually using it in practice, all code I've seen uses the sender.signal.connect(receiver.slot) syntax (which you will not see in C++ examples you look at). And secondly, you wouldn't be able to write what you are trying to do there for converting the parameter in the string argument, hence the error message.

    Now that you are an expert! You have successfully used the simple way of declaring the slot to match the signal exactly, and then doing any work on the parameter in the slot code. This is the simplest, use it whenever you can. However, you ask me, how would I do what I tried to do, now that I am a confident user? And I would answer: via Python lambda:

    self.lcdSlider.slider.valueChanged.connect(lambda intValue: num.valueChanged(str(intValue)))
    

    This effectively defines an "anonymous" method which takes a parameter, intValue, which we know to be an int, and calls num.valueChanged() passing the str() value of the int, so the slot would receive a str. So we can call methods with different types/order/numbers of parameters than the exact-matching slot would have.

    Here I would stick to your original method, the slot taking the int, and therefore no need for a lambda. It makes sense that the slot for a slider value change would receive a number; the fact that you want to use the string of that number inside the slot for some purpose fits better into the slot code than the slot declaration. But over time you will find uses for these lambdas.

    Here endeth the first lesson....



  • @JonB Thanks for your support and advice.

    I'd been experimenting on my own and had sort of got the hang of the "sender.signal.connect(receiver.slot)" syntax. I find that much clearer than the other too but the samples that come with PySide2 (...\Python\Python38-32\Lib\site-packages\PySide2\examples\tutorial) all use the "ugly" form.

    The key lesson, for me, was to understand that signals and slots are class dependent and can be overridden in my own subclass of the standard. Once that penny dropped, and I began to understand how to identify the owning class (INSTANCE actually) of methods, things became so much clearer.

    I'd seen lambdas also but decided they were a level of complication I didn't need to try to absorb just yet. Your example, however, is very helpful to put them in a concrete context for me, thanks.



  • @MNix
    I have a feeling that all PySide2 documentation/examples are taken directly from the C++ ones, and sort of "auto-translated". PyQt5, with which PySide2 is literally 99% compatible, uses the sender.signal.connect(receiver.slot) for its examples. Have a look through https://wiki.qt.io/Qt_for_Python_Signals_and_Slots. That uses this syntax, but also describes the alternatives like QtCore.Signal/Slot(), and is approachable.

    Over time you will find you want the lambdas for some connections, especially where you want to pass something about the signal sender to the slot receiver as an argument.


Log in to reply