I have a class QWidget class containing a QLabel. The class generates QCheckbox'es in a QVBox. I am trying to connect each checkbox to the method nameCheckBox which will update the QLabel to display the title of the last checked box. However when a checkbox is effectively un/checked, it is always detected as Unchecked. Also the returned name is always the last created checkbox. I don't understand where my mistake is. Here is my code:
import sys
from PyQt4 import QtCore
from PyQt4.QtGui import *
from MenusAndToolbars import MenuWindow
class checkBoxWidget(QWidget):
"""
This widget has a QVBox which contains a QLabel and QCheckboxes.
Qcheckbox number is connected to the label.
"""
def __init__(self):
QWidget.__init__(self)
self.__setUI()
def __setUI(self):
vbox = QVBoxLayout(self)
label = QLabel('Last clicked button: ' + "None", self)
vbox.addWidget(label)
listCB = []
for i in range(10):
listCB.append( QCheckBox('CheckBox Nb. ' + str(i+1) ) )
listCB[i].stateChanged.connect(lambda: self.nameCheckBox(label, listCB[i]) )
vbox.addWidget( listCB[i] )
def nameCheckBox(self, label, checkBox):
if checkBox.isChecked():
print "Checked: " + checkBox.text()
label.setText('Last clicked button: ' + checkBox.text())
else:
print "Unchecked: " + checkBox.text()
def main():
app = QApplication(sys.argv)
window = QMainWindow()
window.setCentralWidget( checkBoxWidget() )
window.show()
#window = WidgetWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
EDIT 1
I found several "hack" solutions.
SOLUTION 1 : Creating a callBack function does the trick:
def callBack(self, list, index, label):
return lambda: self.nameCheckBox(label, list[index])
Then I connect the QCheckbox().stateChanged signal this way:
listCB[i].stateChanged.connect( self.callBack(listCB, i, label) )
SOLUTION 2: using the partial module:
First we import the module:
from functools import partial
Then the signal connection is done this way:
listCB[i].stateChanged.connect( partial( self.nameCheckBox, label, listCB[i] ) )
However I would like to use lambda expression in one line. Especially I would like to understand how it works. Following links I understood the issue is about lambda scope. As Oleh Prypin advised me, I wrote:
listCB[i].stateChanged.connect(lambda i=i: self.nameCheckBox(label, listCB[i]) )
Here the variable i is a new one. However my original issue remains. I then tried this out of curiosity:
listCB[i].stateChanged.connect( lambda label=label, listCB=listCB, i=i: self.nameCheckBox(label, listCB[i] ) )
But I get the following error:
Traceback (most recent call last):
Checked: CheckBox Nb. 2
File "Widgets.py", line 48, in <lambda>
listCB[i].stateChanged.connect( lambda label=label, listCB=listCB, i=i: self.nameCheckBox(label, listCB[i] ) )
File "Widgets.py", line 59, in nameCheckBox
label.setText('Last clicked button: ' + checkBox.text())
AttributeError: 'int' object has no attribute 'setText'
Here it seems the correct button is recognized when un/checked. However it seems the new label variable is seen as an int? What happens here?
binds to the variable
i, meaning the value ofiwill be the one at the moment the lambda is called, not when it's created, which is, in this case, always 9.Possible fix:
There is a lot of general information on this topic. Starting points: Google search, another question Creating lambda inside a loop.
Unfortunately, my fix didn't work, because that signal provides an argument
checkedto the function it calls, overriding that default argument with a 0 or 2 depending on the check state. This will work (ignore the unwanted argument):And here is an alternative way to write that class: