0

Alright, so I'm pretty deep into an app I'm making using PyQt5 and I have a section (QScrollArea) where I essentially have multiple lines of "accounts" which are QWidgets. The user can add an account and delete an account, but I would like to give them the ability to edit an account. On the same row as the account is a QPushButton, which is basically the edit button. If I have 3 accounts, then I can see 3 buttons. However, when I click the button for any account, all of the buttons refer to the exact same button object. I am storing the objects in a list and using findChildren() to get the objects. Both of these will give me three different button objects, but using clicked.connect() with the buttons reference only the latest, or bottom-most, button.

I am calling the clicked.connect() function after the part of my code that adds the account. I have also tried moving this to right below where I instantiate the button, but it didn't work.

Here is what I have:

btns = self.accountsWidget.findChildren(QPushButton)
    for btn in btns:
        btn.clicked.connect(lambda: self.editAccount(btn))

The method I call to try to edit the account is here:

    def editAccount(self, btn):
        print(btn)
        self.editAccountWindow = EditAccountsWindow()
        self.editAccountWindow.show()

The result of what's printed is always the last QPushButton object, and I'm only printing to see if I referenced the button I wanted to. That method just opens up a window, but what's shown on the window depends on calling the correct object. I want to use the object to reference which account I want to edit.

I have looked on many SO threads and could not find anything similar to this. I think I'm missing something, so please help a brother out :)

evanb629
  • 1
  • 5
  • See [`sender()`](https://doc.qt.io/qt-5/qobject.html#sender) – alec Apr 12 '20 at 22:22
  • @alec thank you so much. Works like a charm. – evanb629 Apr 12 '20 at 22:30
  • Have a look at [Connecting multiples signal/slot in a for loop in pyqt](https://stackoverflow.com/questions/46300229/connecting-multiples-signal-slot-in-a-for-loop-in-pyqt), and do some research about scope and closure with Python and lambda. Also, consider that a *possible* alternative is to use `functools.partial` instead of lambda. – musicamante Apr 12 '20 at 23:16
  • @musicamante I ordered a PyQt5 book a couple days ago so I could learn more about topics like that. And lambda functions just came easily to me in that scenario, but I'll look into functoools as well. Thanks for the reply. – evanb629 Apr 13 '20 at 01:32
  • Remember that lambas and partials are not "directly related" to PyQt (they're part of Python, PyQt just "obeys" to their behavior), and they are *alternatives*: while partial behavior is *similar* to lambda, they're **not** the same (especially for variable scope/closure), and you have to learn how decide which one use. I usually prefer lambda over partial (lambdas can be too verbose, but I like their "descriptiveness"; nonetheless that choice is also a matter of code style and personal habit/preference). Anyway, you *must* understand their concepts, despite you're dealing with PyQt or not. – musicamante Apr 13 '20 at 01:46

1 Answers1

-1
def editAccount(self, objects):
    sender = self.sender()
    index = 0
    for object in objects:
        if sender == object:
            account = account_handler.get_account(index) 
            ...

widgets = self.accountsScrollView.findChildren(QWidget, "accountsWidget")
for widget in widgets:
    editButtons = widget.findChildren(QPushButton, 'editButtonAccounts')
    for e in range(0, len(editButtons)):
        editButtons[e].clicked.connect(lambda: self.editAccount(edit_objects_list))
        ...
evanb629
  • 1
  • 5
  • 1
    Sorry to be rude, but, while *conceptually* your answer *might* be right, it has a lot of issues. First of all, you should always provide an explanation for your answers (code-only answer are not very welcome), and the indentation is wrong; then, `edit_objects_list` is never declared; it's not very performant; using `range()` with 0 as first argument is unnecessary as it's implied; cycling through a list like that is also unnecessary, you can just cycle through objects found with findChildren: `for widget in widget.findChildren(QPushButton, 'editButtonAccounts'): ...`; – musicamante Apr 13 '20 at 01:59
  • string based search within children should only be done when no other alternative is available: non-unique object names are discouraged, and whether you're using pyuic/uic or coded GUIs, you always have unique names, so, for custom built GUIs there's not much sense with `findChildren` at all; as soon as the object match has been found in the `for` cycle of `editAccount()`, there should be a `break`; there also should be an `index += 1`, or an `enumerate` in the for cycle; finally (sorry again, I'm very strict), on the code styling side of things, you should either use single or double quotes. – musicamante Apr 13 '20 at 02:03
  • @musicamante Sorry I'm just seeing this now, but thank you for your explanation. I have revised the code as you suggested and of course it works great. The reason I use findChildren() is because the number of children constantly changes, and I need to access a certain child at a certain time. – evanb629 Apr 20 '20 at 03:11
  • I can understand it, but remember that SO is not a "personal" Q&A site, as it's intended for long and persisting answers that can help *anybody*. If you want to provide an answer to your own question, that's fine (actually, it's very welcome!), but you should always ensure that that answer **is** helpful to other people too, especially if they don't know the context you're working with. The "power" of SO resides in its *existing* answers (that's why we all use it), so if an answer has enough context, it is helpful for *everybody*, not just you. Please, remember that, as it's really important. – musicamante Apr 20 '20 at 03:19