QtCore.QObject.connect in a loop only affects the last instance
Question:
I have got a loop. I created a QCheckBox
and put it in a QTableWidget
cell, and everything is Ok. In each step of loop I have called a connect
function, for myslot SLOT, but only the last QCheckBox
instance is applied. I googled a lot and have found many people have my problem. I have applied their solutions, but my problem remains.
for row in xrange(len(uniqueFields)):
instance = QtGui.QCheckBox(uniqueFields[row], findInstance.tableWidget)
print QtCore.QObject.connect(instance,
QtCore.SIGNAL(_fromUtf8("stateChanged (int)")),
lambda: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(),
instance.checkState(), instance))
findInstance.tableWidget.setRowCount(findInstance.tableWidget.rowCount() + 1)
findInstance.tableWidget.setCellWidget(row, 0, instance)
Note: my connect
function return True
.
How to create connect
function in a loop that enumerates all of the instances
?
Answers:
The problem is that you are creating a function using lambda
where some of the variables inside the function are not being passed in as arguments to the function. When the lambda function is executed, when the signal is emitted, it uses the value of those variables (like instance
) at that moment in time. To be clear, every lambda function you make is using the value of instance
at runtime, rather than define time. So instance
only holds a reference to the object used in the last iteration of our loop, which explains the behaviour you are seeing.
Some useful information can be found here (read the comments too) http://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot/
From the comments of the above link:
What you can do is have another function generate the lambda, i.e.
something like:
def make_callback(param):
return lambda: self.on_button(param)
And in the connection, call make_callback(i)
. Then a different lambda is
created for each iteration.
So you would want to generalise this and pass in things like instance
to the make_callback
function and then place your lambda
definition inside the make_callback
function. I would provide a clear example of this, but as the other answer says, your formatting appears to have become very messed up in your question and I would likely get it wrong for your specific application. If you aren’t following what I’ve said, make the code in your question clearer and I’ll have a go at creating an example!
Put the loop variable in a default argument, like this:
lambda state, instance=instance: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(), instance.checkState(), instance)
This will give each lambda
its own local copy of the instance
variable.
EDIT
Here’s a simple script that demonstrates how to use default lambda arguments:
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
for index in range(4):
instance = QtGui.QCheckBox('Checkbox(%d)' % index, self)
instance.stateChanged.connect(
lambda state, instance=instance:
self.mySlot(instance.text()))
layout.addWidget(instance)
def mySlot(self, text):
print('clicked: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I have same problem , you should use functools.partial
such as:
for key, val in a_DICT_THAT_YOU_STORED_YOUR_OBJECTS_AND_STRINGS:
obj = partial( findInstance.projectsInstance.myslot,arg1="TWCH",arg2=self,arg3=key,arg4=val.checkState() )
QtCore.QObject.connect(val, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), obj)
Of course, argX should set to your real name of your argument of your function name.
I have got a loop. I created a QCheckBox
and put it in a QTableWidget
cell, and everything is Ok. In each step of loop I have called a connect
function, for myslot SLOT, but only the last QCheckBox
instance is applied. I googled a lot and have found many people have my problem. I have applied their solutions, but my problem remains.
for row in xrange(len(uniqueFields)):
instance = QtGui.QCheckBox(uniqueFields[row], findInstance.tableWidget)
print QtCore.QObject.connect(instance,
QtCore.SIGNAL(_fromUtf8("stateChanged (int)")),
lambda: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(),
instance.checkState(), instance))
findInstance.tableWidget.setRowCount(findInstance.tableWidget.rowCount() + 1)
findInstance.tableWidget.setCellWidget(row, 0, instance)
Note: my connect
function return True
.
How to create connect
function in a loop that enumerates all of the instances
?
The problem is that you are creating a function using lambda
where some of the variables inside the function are not being passed in as arguments to the function. When the lambda function is executed, when the signal is emitted, it uses the value of those variables (like instance
) at that moment in time. To be clear, every lambda function you make is using the value of instance
at runtime, rather than define time. So instance
only holds a reference to the object used in the last iteration of our loop, which explains the behaviour you are seeing.
Some useful information can be found here (read the comments too) http://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot/
From the comments of the above link:
What you can do is have another function generate the lambda, i.e.
something like:def make_callback(param): return lambda: self.on_button(param)
And in the connection, call
make_callback(i)
. Then a different lambda is
created for each iteration.
So you would want to generalise this and pass in things like instance
to the make_callback
function and then place your lambda
definition inside the make_callback
function. I would provide a clear example of this, but as the other answer says, your formatting appears to have become very messed up in your question and I would likely get it wrong for your specific application. If you aren’t following what I’ve said, make the code in your question clearer and I’ll have a go at creating an example!
Put the loop variable in a default argument, like this:
lambda state, instance=instance: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(), instance.checkState(), instance)
This will give each lambda
its own local copy of the instance
variable.
EDIT
Here’s a simple script that demonstrates how to use default lambda arguments:
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
for index in range(4):
instance = QtGui.QCheckBox('Checkbox(%d)' % index, self)
instance.stateChanged.connect(
lambda state, instance=instance:
self.mySlot(instance.text()))
layout.addWidget(instance)
def mySlot(self, text):
print('clicked: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I have same problem , you should use functools.partial
such as:
for key, val in a_DICT_THAT_YOU_STORED_YOUR_OBJECTS_AND_STRINGS:
obj = partial( findInstance.projectsInstance.myslot,arg1="TWCH",arg2=self,arg3=key,arg4=val.checkState() )
QtCore.QObject.connect(val, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), obj)
Of course, argX should set to your real name of your argument of your function name.