17. Object Trees and Ownership

QObjects are organized in trees: when you create an object with another object as a parent, the child is added to the parent’s children() list and automatically deleted when the parent is[^1]. There are several ways to set an object’s parent:

  • Creating an object by passing the parent to its __init__() method.
  • Using QObject.setParent(). This methods allows changing an object’s parent after creation.
  • Adding a widget to a layout. When adding a widget to a QLayout, the layout automatically reparents it to the layout’s owning widget.
  • Setting a scroll area’s widget with QScrollArea.setWidget(). The widget becomes its child.
  • Adding a widget to a QToolBox with addItem()reparents it to the toolbox via a scrollview viewport.
  • Adding a widget to a QSplitter with addWidget() reparents the widget to the splitter.

Note that Python objects also form tree hierarchies, but a Python parent-child relationship doesn’t imply a Qt parent-child relationship.

17.1 Parent-Child Relationships

An icon of a clipboard-list

You are tasked with creating five Qt push buttons and displaying them in a window.

You create five QPushButton instances in the window’s __init__() method:

  1. Create the first button and set the window as its parent. Omit adding it to the layout or as an instance member, leaving it as a local variable.

  2. Create the second button without setting a parent. Add it to the layout (which reparents it to the window), but keep it as a local variable.

  3. Create the third button, setting the window as its parent. Add it to the layout, but as local variable.

  4. Create the fourth button as a local variable without setting a parent.

  5. Create the fifth button as an instance member of the window class but without setting a Qt parent.

The program output is:

1 Button 1 parent:  Main Window
2 Button 2 parent:  Main Window
3 Button 3 parent:  Main Window
4 Button 4 parent:  None
5 Button 5 parent:  None
6 
7 Window members:
8 button5

The first three buttons become the window’s child widgets (directly or via the layout) and appear on the screen. The first button displays in the top-left corner since it is not in the layout. None of these three buttons are instance members of the Window Python object.

The fourth button is neither part of the Qt object hierarchy nor a Python object member, so it goes out of scope (and is garbage-collected) when __init__() returns, and is never shown.

The fifth button is an instance member of the window object (preventing garbage collection) but not part of the Qt object hierarchy, so it is not shown in the window.

17.2 Reparenting Qt Objects

You may need to change a widget’s parent when it moves between containers (for example, when the user rearranges panels, switches tabs, or drags an item). setParent() lets you reparent the widget without destroying and recreating it.

An icon of a clipboard-list

You need to create two top-level Qt windows and switch a child widget between them on demand.

To reparent a Qt object:

  1. Create two top-level Qt Windows. On clicking the main window’s ‘Show widgets’ button show two QWidget objects. Both are created without a parent which makes them top-level windows - if you close the main window, they remain visible and active.

  2. Create the child widget. Add a QLineEdit object to the layout of widget1, the first top-level window.

  3. On clicking the ‘Switch parent’ button, reparent the line edit. Use setParent() to assign the other top-level widget as its parent, and add it to its layout for visibility. The line edit displays its current parent’s object name.

17.3 Finding Qt Object Children

Qt lets you find an object’s children using these QObject methods:

  • findChild(type[, name[, options]]): Finds a single matching child.
  • findChildren(type, pattern[, options]): Finds multiple children by regex pattern.
  • findChildren(type[, name[, options]]): Finds multiple children by exact name.

with these parameters:

  • type: A PySide6 class such as QWidget, QLabel, or QCheckBox,
  • name: The child’s object name, set via setObjectName(). Defaults to an empty string so it must be set it explicitly for name-based searches (optional).
  • pattern: A QRegularExpression instance for pattern matching.
  • options: Either Qt.FindChildOptions.FindDirectChildrenOnly or Qt.FindChildOptions.FindDirectChildrenRecursively (optional).
An icon of a clipboard-list

You have a set of checkboxes in a group box and need to check them programmatically based on user input.

To find a Qt object’s child objects:

  1. Create the checkboxes. Instantiate three QCheckBox objects in a QGroupBox and set an object name for each. Also, create a QLineEdit for user input and a button to start the search.

  2. Find the checkboxes. When the button is pressed, use the QLineEdit’s text to create a QRegularExpression and pass it to findChildren().

  3. Update the checkboxes. Iterate over the found checkboxes and set each one’s check state to Checked.

17.4 Manual Ownership Transfer

Sometimes you want to dynamically reconfigure a UI by moving a widget from one container to another. Simply calling setParent() or letting a new layout take ownership reparents the widget but does not automatically update its signal/slot connections or its internal state (enabled/disabled). You are responsible for migrating the behavior yourself so the widget works correctly in its new context.

An icon of a clipboard-list

A ‘Clear’ button inside a group box clears its sibling QLineEdit’s text. You want to move that button to another group box so it clears a different lineedit.

We have two groupboxes, each containing a QLineEdit. A single ‘Clear’ button starts in Group 1 and clears its sibling lineedit. Clicking ‘Move the Button’ transfers the button to the other groupbox, and you must set it to clear its new sibling.

  1. Update state and connections. Moving a widget does not affect existing signal-slot connections. Our button’s clicked is still connected to the same lineedit’s clear() slot. We have to:

    • Disconnect the old connection.
    • Connect to the new lineedit.
    • Update state (enable/disable the appropriate lineedit).
  2. Transfer ownership. There is no need to remove the button from its original layout or use setParent() to change its parent, as adding it to the new layout takes care of the both.

Now if you move the ‘Clear’ button from one groupbox to another, it will clear its sibling lineedit text correctly.

17.5 Automatic Deletion

Being part of the Qt object tree is what makes automatic deletion possible.: when a parent object is destroyed, Qt walks its children() list and deletes each one, cascading down through the whole subtree. This section demonstrates this with a custom label that reports its own destruction.

An icon of a clipboard-list

You need to demonstrate the Qt’s automatic deletion process. You create four custom labels and have each one anounce its own destruction.

To observe Qt object deletion:

  1. Create a MyLabel subclass of QLabel that reports its own destruction from two different points:

    • __del__(), which runs when the Python object is deallocated.
    • The destroyed() signal, which is emitted when the underlying Qt object is destroyed. MyLabel reimplements __del__() and connects its own destroyed() signal to a static slot, logging both.
  2. Create four custom labels: label1 and label2 are added to the window’s layout, both becoming part of the Qt object tree. label3 is a local variable with no Qt parent. label4 is an instance member (self.label4) with no Qt parent. To make sure that the main window is destroyed when closed, set its Qt.WidgetAttribute.WA_DeleteOnClose flag.

  3. Override closeEvent() to print a marker to make it obvious when each deletion happens.

When you run the application, and then close the window, the output is:

 1 Label 1 parent: MainWindow
 2 Label 2 parent: MainWindow
 3 Label 3 parent: None
 4 Label 4 parent: None
 5 Window.__init__() end
 6 __del__:      Label 3
 7 destroyed():  Label 3
 8 
 9 Window close event
10 
11 __del__:      Label 1
12 destroyed():  Label 1
13 __del__:      Label 2
14 destroyed():  Label 2
15 destroyed():  Label 4

label3 is destroyed the moment __init__() returns. It was never added to the Qt object tree and never referenced anywhere but the local variable, so once that variable goes out of scope it is destroyed.

label and label2 survive until the window itself is destroyed. Closing the window triggers closeEvent(), and because of WA_DeleteOnClose, Qt then deletes the window’s C++ object, cascading through children() and deleting both labels in the order they were added.

label4 has no Qt parent, so the object tree deletion never reaches it. It is kept alive by the Python reference (self.label4) and is cleaned up once that reference disappears. Note the absence of __del__() call for label4 as it is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits.