views:

89

answers:

4

I've been having some trouble with a set of dynamically created buttons in PyQT.

I'm using a list to create buttons corresponding to different values. Because each button is different, I need to have them pass an argument to the "Button Clicked" function identifying themselves separately so I can perform different actions with them. Here is what I have so far:

for x in self.buttons:
    name = str(x)
    button = QtGui.QPushButton(Frame)
    button.setText(name)
    cp_result = self.changeProject(name)
    if cp_result is None:
        print 'changeProject(%s) is None!', name
    else:
        print 'changeProject(%s) is OK (%r)', name, cp_result
        #button.clicked.connect(cp_result)
    setattr(self, "btn_%s" % name, button)
    self.btnList.append(button)

def changeProject(self, name):
    for x in self.btnList:
        if x.isDown:
            #Change UI Frame to blahblah
    return 'A String'

Working with Alex, this is the latest code, testing a return value on changeProject, which clearly cannot be a string, but I still need to determine which button is pressed between the auto-generated buttons.

Current error: TypeError: connect() slot argument should be a callable or a signal, not 'str'

+1  A: 

You can add attribute dynamically to objects using setattr

for x in buttons:
    name = str(x)
    button = QtGui.QPushButton(Frame)
    button.setText(name)
    button.clicked.connect(self.changeProject(name))
    setattr(self, "btn_%s" % name, button)
lazy1
This helps the overuse of exec issue, any ideas on the connect, though? The error persists even with the setattr bit.
Cryptite
+1  A: 

What the error message is telling you is that self.changeProject("%s") for one of the values you're substituting for that %s returns None. Presumably you meant for that method to return something different?

It's impossible to help you much with the task of debugging changeProject further without seeing the code for it, of course. However, you could for example split the call into something like (once you've gotten rid of that ton of execs as per lazy1's suggestion):

cp_result = self.changeProject(name)
if cp_result is None:
    logging.error('changeProject(%s) is None!', name)
else:
    logging.info('changeProject(%s) is OK (%r)', name, cp_result)
    button.clicked.connect(cp_result)

This way instead of uselessly trying to "connect to None", you'll see all the names causing that to-you-surprising return value in your error log, and can then continue debugging based on that information. However, more likely than not, your bug might in fact become obvious by looking at the source of changeProject.

Edit: the argument to connect is of course coming from changeProject (not from another connect!-) -- fixed the snippet accordingly.

Alex Martelli
The issue is that of the two items in the list, both are strings and none are "None". Using the code above, the error message persists at the very first line. Even substituting the variable name for a hardcoded string value yields the same issue. the changeProject function does nothing, yet because I have yet to get that far without the code failing.
Cryptite
@Cryptite, what's on the list (args you pass to `changeProject`) is so incredibly irrelevant as to be risible: **all** that matters is **not** what argument you pass to your `changeProject` method, but **only** the (type of the) _result_ that said method returns. So, why do you bring it up at all?! Focus on **what does `connect` return** -- apparently it returns `None` and you have a terrible bug in the code of `changeProject` (which you're **still** carefully hiding from us would-be helpers!) if you don't _expect_ it to return `None`.
Alex Martelli
Sorry, no intention on hiding the code from you guys, the reason I was hesitant is because I'm trying two different sets of code between your suggestions and Prof.Ebral's. Going with yours, I think the confusion was that changeObject MUST return something, as it did not. Returning a string value from it now yields the error "TypeError: connect() slot argument should be a callable or a signal, not 'str'". I've edited the OP with the current code.
Cryptite
A: 

I think you running 'exec' is too much, IMO. Your problem is that you are trying to connect to the QPushButton's .clicked(), which is really a signal. Do you really need to pass the argument here

exec 'self.btn_%s.clicked.connect(self.changeProject("%s"))' % (x, x)

Because if you don't you can connect it like this:

self.connect(self.btn_%s, SIGNAL('clicked()'), self.changeProject)

If you need to know which button is clicked you can iterate through the list to find which button is clicked:

for x in buttons:
if x.isDown(): (function)


You can also use pyqtSignal.

Prof.Ebral
This worked to connect the clicked buttons to changeProject, however when iterating through the buttons to find out who "isDown", they both are evaluated as being pressed.
Cryptite
can you show the code for that? I get False for my buttons. (I'm new, found out pressing <Enter> saves edits, if that caused any issues .. sorry) I wanted to edit this to add that you might at as well turn you button list into a local object, ie. self.buttons. If all of the buttons are going to be local, you could just make the list local and re use that.
Prof.Ebral
A: 

Thanks for the assistance. I stumbled QSignalMapper, and this turned out to be exactly what I needed.

http://pysnippet.blogspot.com/2010/06/qsignalmapper-at-your-service.html

Cryptite