PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Friday, November 4, 2022

[FIXED] How to use lambda function with the PyQt "connect" function when the widgets are instanciated in a loop?

 November 04, 2022     lambda, pyqt5, python     No comments   

Issue

I am creating in a PyQt project a list of widgets that depend on the situation (here it is represented with a list of widget types). I don't know by advance the number of widgets needed, neither their type nor the number of widgets for each type.

Creating them is not complicated but I am having trouble when I have to catch their new value when the user wants to change them.

Here is the minimum reproducible example of my problem:

from PyQt5.QtWidgets import QApplication, QVBoxLayout, QCheckBox, QSpinBox, QWidget
import sys

class MyWidget(QWidget):
    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()
        
        for widgetNumber in range(len(widgetList)):
        
            if widgetList[widgetNumber] == 'QCheckBox':
                widget = QCheckBox()
                widget.stateChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.isChecked()))
            
            elif widgetList[widgetNumber] == 'QSpinBox':
                widget = QSpinBox()
                widget.valueChanged.connect(lambda: self.argumentChanged(widgetNumber, widget.value()))
            
            layout.addWidget(widget)
            
        self.setLayout(layout)
    
    def argumentChanged(self, widgetNumber, value):
        print("The widget number", widgetNumber, "that is a", self.widgetList[widgetNumber], "has been modified! New value:", value)
    
def window():
    app = QApplication(sys.argv)
    
    widgetList = ['QCheckBox', 'QCheckBox', 'QSpinBox']
    widget = MyWidget(widgetList)
    widget.show()
    
    sys.exit(app.exec_())
    
if __name__ == '__main__':
    window()

In this situation, the value of widgetNumber in the lambda functions will always be the last iteration of the loop. So if I had 4 widgets, I will have a 3 value inside every lambda function. The objective is to have the value of the current widget.

I also tried to change the expressions of the lambda functions to these:

lambda widgetNumber: self.argumentChanged(widgetNumber, widget.value()))

but the situation becomes stranger: when increasing the value of a SpinBox, the widget number is increasing, and vice-versa when decreasing it.


Solution

The variables used inside a lambda are always evaluated at execution: widgetNumber will always be the last value in the for loop.

Also, signals can have arguments, and if you use a positional argument in the lambda, that argument will be the value of that signal when it's emitted: lambda widgetNumber: will result in the spinbox value or the state of the check box.

If you need "static" arguments, you need to use keyword arguments. Since the signal has arguments, there's also no need to call the getter again:

lambda value, widgetNumber=widgetNumber: self.argumentChanged(widgetNumber, value)

Remember that lambdas should be preferably used only when there is no other, simpler (clearer or cleaner) solution.
In this case, we can use the sender() function provided by Qt, which returns the object that emitted the signal.

In the following example I've simplified the whole loop, and used a custom property to set the number directly on the widget (note that also a basic attribute could be used, but properties are more consistent with Qt).

class MyWidget(QWidget):
    def __init__(self, widgetList):
        super(QWidget, self).__init__()
        self.widgetList = widgetList
        layout = QVBoxLayout()

        for number, widgetType in enumerate(widgetList):

            if widgetType == 'QCheckBox':
                widget = QCheckBox()
                widget.toggled.connect(self.argumentChanged)

            elif widgetType == 'QSpinBox':
                widget = QSpinBox()
                widget.valueChanged.connect(self.argumentChanged)

            widget.setProperty('widgetNumber', number)
            layout.addWidget(widget)

        self.setLayout(layout)

    def argumentChanged(self, value):
        widget = self.sender()
        widgetNumber = widget.property('widgetNumber')
        print("The widget number", widgetNumber, 
            "that is a", self.widgetList[widgetNumber], 
            "has been modified! New value:", value)
    


Answered By - musicamante
Answer Checked By - Marilyn (PHPFixing Volunteer)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing