Skillversitas

Layouts

Layouts are the managers that position the child widgets and decide how they resize with the window - the box layouts that stack them, the grid layout that arranges them in rows and columns, the form layout that pairs the labels with the fields, and the layout's margins and spacing that give the arrangement its breathing room. The model is the one that makes a multi-widget UI manageable: the window (or the container widget) has one top-level layout, the layouts nest (a layout can hold another layout), and each layout applies its rule to its children as the window resizes. This chapter covers the box layouts (the QVBoxLayout, the QHBoxLayout, the workhorse), the QGridLayout (the two-dimensional arrangement), the QFormLayout (the form), the QStackedLayout (the page switch), the layout's geometry control (the setContentsMargins, the setSpacing), and the layout rules that keep a UI from collapsing on the resize.

The Box Layouts: The Workhorse

from PyQt5.QtWidgets import (QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QPushButton)
from PyQt5.QtCore import Qt

# the vertical box - the most-used layout:
lay = QVBoxLayout()
lay.setSpacing(8)                # the gap between children
lay.setContentsMargins(12, 12, 12, 12)  # the L, T, R, B padding

# the addWidget: the child, with the STRETCH factor (the
# 0 = the natural size, the >0 = the share of the extra)
lay.addWidget(toolbar, 0)        # the fixed: the natural
lay.addWidget(main_area, 1)      # the stretch: it grows
lay.addWidget(status, 0)         # the fixed: the natural

# the horizontal box - same API, the horizontal direction:
hbox = QHBoxLayout()
hbox.addWidget(left, 1)          # the stretch
hbox.addWidget(right, 0)         # the fixed

# the addLayout: the NESTING (a layout inside a layout):
inner = QHBoxLayout()
inner.addWidget(a)
inner.addWidget(b)
lay.addLayout(inner)             # the row inside the column

# the layout on a widget: the setLayout (the widget's
# top-level layout) - the widget holds ONE layout
container = QWidget()
container.setLayout(lay)
# the widget with no layout: the children are not positioned
# by it (they overlap at 0,0) - the layout is required

The box layouts are the workhorse: the QVBoxLayout stacks children top-to-bottom, the QHBoxLayout lays them left-to-right, and the addWidget adds a child with the optional stretch factor - the 0 (the default) gives the child its natural size, and the factor greater than 0 gives it a share of the extra space when the window grows. The canonical editor layout is the three-add: the fixed toolbar (stretch 0) at the top, the growing main area (stretch 1) in the middle, the fixed status (stretch 0) at the bottom - and that one QVBoxLayout with three addWidget calls is the skeleton of the running example's window. The setSpacing is the gap between all children, and the setContentsMargins (the left, top, right, bottom) is the padding inside the layout's edge - the two properties that give the layout its breathing room. The addLayout is the nesting - a layout inside a layout (the row of buttons inside the column) - and the setLayout is the attachment (the widget's top-level layout, one per widget). The rule that the layout enforces: a widget with children needs a layout, or the children overlap at the origin - the layout is what positions them.

The QGridLayout: Rows and Columns

from PyQt5.QtWidgets import (QWidget, QGridLayout, QLabel,
                             QLineEdit, QPushButton)
from PyQt5.QtCore import Qt

# the grid - the two-dimensional arrangement:
grid = QGridLayout()
grid.setHorizontalSpacing(8)     # the column gap
grid.setVerticalSpacing(8)       # the row gap
grid.setContentsMargins(12, 12, 12, 12)

# the addWidget(widget, row, col, rowSpan, colSpan,
#               alignment): the cell + the span
grid.addWidget(QLabel("Name:"),  0, 0)
grid.addWidget(name,             0, 1)
grid.addWidget(QLabel("Email:"), 1, 0)
grid.addWidget(email,            1, 1)
grid.addWidget(submit,           2, 1, Qt.AlignRight)
# the span: the widget that covers the cells 3..4 in col 1:
# grid.addWidget(wide, 3, 1, 1, 2)

# the column / the row stretch: the share of the extra
grid.setColumnStretch(1, 1)      # the field column grows
grid.setRowStretch(2, 0)

# the column / the row minimum: the bound
grid.setColumnMinimumWidth(0, 80)

# the grid is the form layout: the label column + the field
# column, the rows aligned, the field column the stretch

The QGridLayout is the two-dimensional layout: children are addWidgeted at a row and a column with an optional row and column span (the cells the widget covers) and an alignment within the cell. The setHorizontalSpacing/setVerticalSpacing are the uniform gaps, the setContentsMargins the padding, and the setColumnStretch/setRowStretch are the stretch shares (the column or the row that grows with the window - the field column of the form). The setColumnMinimumWidth is the bound (the column that never shrinks below). The canonical use is the form - the label column and the field column, each row a field, the field column the stretch - and the grid is what makes that arrangement declarative. The QFormLayout (the next section) is the specialized grid for the label-field form, and the raw QGridLayout is for the arrangement that is not the simple form (the multi-column, the spanning). The container's shape matches the layout's shape: a toolbar or a status bar is a box, a form is a grid or a form layout, and the choice is the layout's geometry.

The QFormLayout and the QStackedLayout

from PyQt5.QtWidgets import (QFormLayout, QStackedLayout,
                             QLabel, QLineEdit, QWidget)

# the form layout - the label-field pairing (the specialized
# grid): the addRow is the row
form = QFormLayout()
form.addRow("Name:", name)       # the label + the field
form.addRow("Bio:", bio)         # the field spans (the label
                                 # column empty for the multi-line)
form.addRow(wrap_checkbox)       # the field alone (no label)
# the form layout is the FORM - the label-field grid without
# the manual row/col of the QGridLayout

# the stacked layout - the page switch (the ONE visible):
stack = QStackedLayout()
stack.addWidget(page_one)        # the page 0
stack.addWidget(page_two)        # the page 1
stack.setCurrentIndex(1)         # the visible page
stack.addWidget(page_three)
# the QStackedWidget is the widget form (the same, the
# widget with the stacked layout built in):
# from PyQt5.QtWidgets import QStackedWidget
# sw = QStackedWidget(); sw.addWidget(p1); sw.addWidget(p2)
# the stacked is the TAB-less page switch - the wizard's
# pages, the view switch the code drives (the QTabWidget
# is the tabbed form, the later pattern)

The two specialized layouts. The QFormLayout is the label-field pairing - the addRow with the label and the field (or the field alone for the checkbox that is its own label, or the field spanning for the multi-line) - and it is the form without the manual row and column of the QGridLayout: the layout computes the label column and the field column, and the addRow is the declarative form. The QStackedLayout is the page switch - the children are the pages, only one is visible at a time, and the setCurrentIndex/setCurrentWidget select the visible page (the QStackedWidget is the widget form with the stacked layout built in) - the wizard's pages, the view switch the code drives, the tab-less alternative to the QTabWidget (the tabbed form, which is the stacked plus the tab bar). The pattern across the specialized: each exists for the layout need the box and the grid do not cover as cleanly - the form's label-field pairing, the page switch - and reaching for them is deliberate, the container's shape matched to the layout's shape.

The Layout Rules

  • The widget's top-level layout is set with setLayout, one per widget. A widget with children and no layout has them overlap at the origin - the layout is the requirement, and the nesting (addLayout) is how the arrangement composes.
  • One growing region, everything else the natural size. The stretch factor: the main area is the 1, the toolbar and the status are the 0. More than one growing region in a box fights over the space, and the stretch is the share.
  • Use the QFormLayout for the form, the QGridLayout for the arrangement that is not the simple form. The form's label-field pairing is the QFormLayout; the multi-column and the spanning are the QGridLayout. The layout's shape matches the arrangement's shape.
  • The setContentsMargins and the setSpacing are the breathing room. Set them on the layout (the uniform padding and the gap), not per-child, unless one child genuinely needs its own - the dense layout without the margins reads as crowded.
  • The layout is the resize behavior. The layout is what positions the children as the window resizes - the fixed stay their size, the stretch grow, and the layout is the only mechanism that makes the UI survive the resize correctly.

Summary

Layouts position the widgets and decide the resize: the QVBoxLayout/QHBoxLayout are the workhorse - the addWidget with the stretch factor decides which child grows with the window, and the canonical editor is the three-add (the fixed toolbar, the growing main area, the fixed status). The QGridLayout is the two-dimensional arrangement - the addWidget at the row and column with the span, the setColumnStretch for the growing column, the form's label-field grid its canonical use. The QFormLayout is the specialized form (the addRow the declarative pairing), and the QStackedLayout (the QStackedWidget) is the page switch (the one visible, the setCurrentIndex). The setContentsMargins and the setSpacing are the breathing room, and the setLayout is the attachment (one top-level layout per widget, the addLayout the nesting). The rules keep a layout from collapsing: one growing region in a box, the layout's shape matched to the arrangement's shape, the margins and the spacing as the uniform padding, and the layout as the mechanism that makes the UI survive the resize.