QtCore.QObject.connect in a loop only affects the last instance
Asked Answered
M

4

0

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?

Mascia answered 22/10, 2013 at 6:44 Comment(1)
I have no idea why this question was tagged C++. It's clearly Python code.Herthahertz
Y
1

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.

Yttria answered 29/10, 2013 at 0:27 Comment(0)
A
7

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_())
Associationism answered 22/10, 2013 at 16:20 Comment(3)
That doesn't work because no arguments are passed to the slot when the signal is emitted. You need to create a lambda with no arguments, but which still has its own copy of instance. Hence my answer where you use a function to make the lambdaMaudiemaudlin
@three_pineapples. It works perfectly fine: see my updated answer for a working example. As I explained in my original answer, the default argument is used to cache the loop variable, so there's no need to use a closure (although that will work, too).Associationism
Ah, apologies, I skipped over the instance=instance part of it (I just read instance). Your method is far cleaner than my suggestion and I'll start using it in my own codeMaudiemaudlin
Y
1

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.

Yttria answered 29/10, 2013 at 0:27 Comment(0)
M
0

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!

Maudiemaudlin answered 22/10, 2013 at 9:48 Comment(0)
H
0

An example of creating the separate function "make_callback", and passing parameters to it in the for loop. This resolves the issue of only the last lambda function being connected to the instance.

for button_widget, button_dict in zip(self.button_widget_dictionary.values(),
                                                  self.button_page_dictionary.values()):
                button_widget.pressed.connect(self.make_callback(button_dict))


def make_callback(self,button_dict):

        return lambda: self.central_widget.add_graph_tab_dict(
            button_dict['Tab_options']['name'][0] + ': ' 
          + button_dict['Tab_options']["graph_title"][0:4] + "...",
            button_dict)

Hurlburt answered 13/10, 2023 at 21:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.