When layouts in a UI are not behaving as expected or performance is poor, it can be helpful to have a mental model of the layout process in order to know where to start debugging. For web browsers there are some good resources which provide a description of the process at different levels. The layout documentation for Qt describes the various layout facilities that are available but I haven’t found a detailed description of the flow, so this is my attempt to explain what happens when a layout is triggered that ultimately ends up with the widgets being resized and repositioned appropriately.
A widget’s contents are modified in some way that require a layout update. Such changes can include:
- Changes to the content of the widget (eg. the text in a label, content margins being altered)
- Changes to the
sizePolicy()of the widget
- Changes to the layout() of the widget, such as new child widgets being added or removed
The widget calls
QWidget::updateGeometry()which then performs several steps to trigger a layout:
- It invalidates any cached size information for the QWidgetItem associated with the widget in the parent layout.
- It recursively climbs up the widget tree (first to the parent widget, then the grandparent and so on), invalidating that widget’s layout. The process stops when we reach a widget that is a top level window or doesn’t have its own layout - we’ll call this widget the top-level widget, though it might not actually be a window.
If the top-level widget is not yet visible, then the process stops and layout is deferred until the widget is due to be shown.
If the top-level widget is shown, a LayoutRequest event is posted asynchronously to the top-level widget, so a layout will be performed on the next pass through the event loop.
If multiple layout requests are posted to the same top-level widget during a pass through the event loop, they will get compressed into a single layout request. This is similar to the way that multiple
QWidget::update()requests are compressed into a single paint event.
The top-level widget receives the LayoutRequest event on the next pass through the event loop. This can then be handled in one of two ways:
- If the widget has a layout, the layout will intercept the LayoutRequest event using an event filter and handle it by calling QLayout::activate()
- If the widget does not have a layout, it may handle the LayoutRequest event itself and manually set the geometry of its children.
When the layout is activated, it first sets the fixed, minimum and/or maximum size constraints of the widget depending on QLayout::sizeConstraint(), using the values calculated by QLayout::minimumSize(), maximumSize() and sizeHint(). These functions will recursively proceed down the layout tree to determine the constraints for each item and produce a final size constraint for the whole layout. This may or may not alter the current size of the widget.
The layout is then asked to resize its contents to fit the current size of the widget using QLayout::setGeometry(widget->size()). The specific implementation of the layout - whether it is a box layout, grid layout or something else then lays out its child items to fit this new size.
For each item in the layout, the QLayoutItem::setGeometry() implementation will typically ask the item for various size parameters (minimum size, maximum size, size hint, height for width) and then decide upon a final size and position for the item. It will then invoke QLayoutItem::setGeometry() to update the position and size of the widget.
If the layout item is itself a layout or a widget, steps 5-6 proceed recursively down the tree, updating all of the items whose constraints have been modified.