Xiong Chiamiov
Xiong Chiamiov

Reputation: 13734

Get a layout's widgets in PyQT

I have a QVBoxLayout that I've added a few widgets to, via addWidget(). I need to now delete those widgets, and it seems I need to use removeWidget() (which takes in a widget to be removed) to do that.

I thought that calling children() or findChildren(QWidget) on my layout would return a list of the widgets I've added into it; I'm in the debugger, though, and am just receiving empty lists.

Am I terribly misunderstanding something? I've just started doing PyQT this last week and have mostly been learning through trial and error with the API docs.

Upvotes: 24

Views: 46384

Answers (4)

Isa
Isa

Reputation: 49

Per a hint from the docs posted by Troubadour, it's possible in PyQt6 (and probably in PyQt5) to iterate over children of a layout through a simple for loop:

for w in myLayout.parent().children():
    print(w)

However since the layout itself is also among its parent's children, disowning it in this way seems to cause the application to segfault. I added a simple type check that will prevent any object that belongs to or inherits from QLayout class from being deleted.

for w in myLayout.parent().children():
    if not isinstance(w, QLayout):
       w.setParent(None)

Upvotes: 0

You can use basic reflection to see what's going on to get the heirarchy.


from __future__ import annotations

from PyQt5 import QLayout, QWidget

def print_inheritence_tree(parent: QWidget | QLayout, indent: str = ""):
    if isinstance(parent, QWidget):
        children = parent.findChildren(QWidget) + parent.findChildren(QLayout)
    else:
        children = parent.children()
    for child in children:
        object_name = child.objectName() or "<undefined>"
        print(f"{indent}{object_name} ({child.__class__.__name__})")
        print_inheritence_tree(child, indent + "  ")

Sending QTreeView() and you get this output:

qt_scrollarea_viewport (QWidget)
qt_scrollarea_hcontainer (QWidget)
  <undefined> (QScrollBar)
  <undefined> (QBoxLayout)
<undefined> (QScrollBar)
qt_scrollarea_vcontainer (QWidget)
  <undefined> (QScrollBar)
  <undefined> (QBoxLayout)
<undefined> (QScrollBar)
<undefined> (QHeaderView)
  qt_scrollarea_viewport (QWidget)
  qt_scrollarea_hcontainer (QWidget)
    <undefined> (QScrollBar)
    <undefined> (QBoxLayout)
  <undefined> (QScrollBar)
  qt_scrollarea_vcontainer (QWidget)
    <undefined> (QScrollBar)
    <undefined> (QBoxLayout)
  <undefined> (QScrollBar)
  <undefined> (QBoxLayout)
  <undefined> (QBoxLayout)
qt_scrollarea_viewport (QWidget)
qt_scrollarea_hcontainer (QWidget)
  <undefined> (QScrollBar)
  <undefined> (QBoxLayout)
<undefined> (QScrollBar)
qt_scrollarea_vcontainer (QWidget)
  <undefined> (QScrollBar)
  <undefined> (QBoxLayout)
<undefined> (QScrollBar)
<undefined> (QBoxLayout)
<undefined> (QBoxLayout)
<undefined> (QBoxLayout)
<undefined> (QBoxLayout)

It should then be easy enough to open a repl and determine what you can do from there.

Upvotes: 1

Troubadour
Troubadour

Reputation: 13431

That's odd. My understanding is that adding widgets via addWidget transfers ownership to the layout so calling children() ought to work.

However, as an alternative you could loop over the layout items by using count() and itemAt(int) to supply a QLayoutItem to removeItem(QLayoutItem*).

Edit:

I've just tried addWidget with a straight C++ test app. and it doesn't transfer QObject ownership to the layout so children() is indeed an empty list. The docs clearly say that ownership is transferred though...

Edit 2:

Okay, it looks as though it transfers ownership to the widget that has that layout (which is not what the docs said). That makes the items in the layout siblings of the layout itself in the QObject hierarchy! It's therefore easier to stick with count and itemAt.

Edit 3:

My interpretation of the docs was wrong. I was getting the layout item confused with the widget. The following clarifies what has always been the case.

https://doc.qt.io/qtforpython-5/overviews/layout.html

Widgets in a layout are children of the widget on which the layout is installed, not of the layout itself. Widgets can only have other widgets as parent, not layouts.

Upvotes: 19

Arnoud Vangrunderbeek
Arnoud Vangrunderbeek

Reputation: 373

To get a widget from a QLayout, you have to call its itemAt(index) method. As the name of this method implies, it will return an item instead of a widget. Calling widget() on the result will finally give you the widget:

myWidget = self.myLayout.itemAt(index).widget()

To remove a widget, set the parent widget to None:

myWidget.setParent(None)

Also really helpfull is the QLayout count() method. To find and delete all contents of a layout:

index = myLayout.count()
while(index >= 0):
    myWidget = myLayout.itemAt(index).widget()
    myWidget.setParent(None)
    index -=1

Upvotes: 32

Related Questions