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)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.