Pyqt4 hooks up QCheckbox state correctly
I have a class QWidget
that contains QLabel
. The class generates QCheckbox
' es in QVBox
. I am trying to wire each checkbox to a method nameCheckBox
that will update QLabel
to display the title of the last checked box. However, when a checkbox is effectively disabled / checked, it is always defined as Unchecked. Also the returned name is always the last checkbox created. 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 have found several "hacked" 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 signal QCheckbox().stateChanged
like this:
listCB[i].stateChanged.connect( self.callBack(listCB, i, label) )
SOLUTION 2: using a module partial
:
First, we import the module:
from functools import partial
Then the signal connection is done as follows:
listCB[i].stateChanged.connect( partial( self.nameCheckBox, label, listCB[i] ) )
However, I would like to use the lambda expression on one line. I would especially like to understand how it works. By following links I realized that the problem is with the lambda volume. As Oleg Prypin advised me, I wrote:
listCB[i].stateChanged.connect(lambda i=i: self.nameCheckBox(label, listCB[i]) )
Here the variable i
is new. However, my original problem remains. Then I tried this out of curiosity:
listCB[i].stateChanged.connect( lambda label=label, listCB=listCB, i=i: self.nameCheckBox(label, listCB[i] ) )
But I am getting 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, the correct button seems to be recognized when un / checked. However, it seems like the new variable is label
being treated as an int? What's going on here?
source to share
lambda: self.nameCheckBox(label, listCB[i])
is bound to a variable i
, which means that the value i
will be the same as when the lambda was called, not when it was created, which in this case is always 9.
Possible fix:
lambda i=i: self.nameCheckBox(label, listCB[i])
There is a lot of general information on this topic. Starting points: Google search , another question Creating a lambda inside a loop .
Unfortunately my fix doesn't work because this signal provides an argument to the checked
called function, overriding this default argument from 0 or 2 depending on the validation state. This will work (ignore the unwanted argument):
lambda checked, i=i: self.nameCheckBox(label, listCB[i])
And here's an alternative way to write this class:
class CheckBoxWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setupUi()
def setupUi(self):
vbox = QVBoxLayout(self)
self.label = QLabel("Last clicked button: None")
vbox.addWidget(self.label)
for i in range(10):
cb = QCheckBox("CheckBox Nb. " + str(i+1))
cb.stateChanged.connect(self.nameCheckBox)
vbox.addWidget(cb)
def nameCheckBox(self, checked):
checkBox = self.sender()
if checked:
print("Checked: " + checkBox.text())
self.label.setText("Last clicked button: " + checkBox.text())
else:
print("Unchecked: " + checkBox.text())
source to share