Qt Layouts and Main Window


  • Description: Qt layout managers (QHBoxLayout/QVBoxLayout/QGridLayout/QFormLayout/QStackedLayout), QSplitter, size policy and stretch, QMainWindow (menus, toolbars, dock widgets, status bar), QAction, palette and transparency, buddy and tab order
  • My Notion Note ID: K2A-B3-3
  • Created: 2018-03-04
  • Updated: 2026-05-18
  • License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io

Table of Contents


1. Why Layouts

  • Hard-coded geometry (move/resize) breaks on DPI changes, font changes, translations (German often 30% longer than English), and window resizing. Layouts let the framework recompute positions every time the parent resizes.
  • Rule of thumb: never call setGeometry on a child widget. Put it in a layout, set a size policy, let Qt place it.
  • A layout is itself a QLayout (not a QWidget) — you set it on a widget via widget->setLayout(layout). After that, the layout owns the children it manages.

2. Box Layouts (QHBoxLayout, QVBoxLayout)

  • Horizontal row or vertical column.
auto *row = new QHBoxLayout;
row->addWidget(label);
row->addWidget(lineEdit);
row->addWidget(button);

auto *col = new QVBoxLayout;
col->addLayout(row);                 // nest a layout inside a layout
col->addWidget(textArea);
col->addStretch();                   // soak up remaining space — pushes prev. items up

mainWidget->setLayout(col);
  • addStretch(int factor = 0) is the workhorse for alignment. To right-align a button: row->addStretch(); row->addWidget(button). To center: stretch on both sides.
  • addSpacing(int) inserts a fixed gap (ignores stretch); addStretch() inserts elastic space.

3. QGridLayout and QFormLayout

  • QGridLayout — 2-D grid with row/column spans:
auto *grid = new QGridLayout;
grid->addWidget(new QLabel(tr("Name:")),     0, 0);
grid->addWidget(nameEdit,                    0, 1, 1, 2);  // row 0, col 1, span 1x2
grid->addWidget(new QLabel(tr("Address:")),  1, 0);
grid->addWidget(addressEdit,                 1, 1, 2, 2);  // 2x2 block
grid->setColumnStretch(1, 1);                              // col 1 absorbs slack
  • QFormLayout — label/field pairs, automatically chooses platform-correct label position (above on macOS, left on Windows):
auto *form = new QFormLayout;
form->addRow(tr("&Name:"),  nameEdit);
form->addRow(tr("&Email:"), emailEdit);
form->addRow(tr("&Age:"),   ageSpin);
  • &Name: makes 'N' a mnemonic that focuses nameEditQFormLayout wires the buddy relationship automatically.

4. QStackedLayout

  • Like a tab widget without tabs — one child visible at a time, swap with setCurrentIndex(i). Pairs with anything that produces an index (QListWidget, QComboBox, custom buttons).
auto *pages = new QStackedLayout;
pages->addWidget(generalPage);
pages->addWidget(advancedPage);
pages->addWidget(aboutPage);

connect(navList, &QListWidget::currentRowChanged,
        pages,   &QStackedLayout::setCurrentIndex);
  • For a tabbed UI with visible tab strips, prefer QTabWidget (which uses QStackedLayout internally).

5. Size Policy, Hints, Stretch, Spacing

  • Every widget exposes sizeHint() ("the size I'd like") and minimumSizeHint() ("the smallest sensible size"). Layouts honor these, then distribute leftover space according to size policy.
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);          // never grow
edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);        // grow H, not V
text->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);    // fills space
Policy Behavior
Fixed Stick to sizeHint.
Minimum sizeHint is the minimum; can grow if extra space and no other expander.
Maximum sizeHint is the maximum; shrink down to minimumSizeHint.
Preferred Default. sizeHint ideal; can shrink or grow.
Expanding Eats extra space.
MinimumExpanding sizeHint is min; soaks up extra.
Ignored Layout ignores sizeHint entirely.
  • setStretch(i, n) on a layout multiplies how greedily slot i claims extra space — useful when two Expanding siblings should split space 2:1.
  • Container spacing: layout->setContentsMargins(l, t, r, b) for outer padding; layout->setSpacing(n) for gaps between children. Default values come from QStyle — they look "right" for the OS, so override sparingly.

6. QSplitter

  • Two or more child widgets separated by user-draggable handles. Saves and restores its split ratio with saveState() / restoreState(QByteArray).
auto *splitter = new QSplitter(Qt::Horizontal);
splitter->addWidget(navPanel);
splitter->addWidget(contentArea);
splitter->setStretchFactor(0, 1);   // nav gets 1 part
splitter->setStretchFactor(1, 4);   // content gets 4 parts initially
splitter->setCollapsible(0, false); // nav can't be dragged to width 0
  • QSplitter is a QWidget, not a QLayout — put it directly into a layout or use it as the central widget of a QMainWindow.
  • For three-pane layouts (Mail-style: folders | list | preview), nest splitters: a vertical splitter inside the right pane of a horizontal one.

7. QMainWindow Anatomy

  • QMainWindow is a QWidget with predefined slots for the conventional desktop-app chrome.
+--------------------------------------------+
| MenuBar                                    |
+--------------------------------------------+
| ToolBar(s)                                 |
+----+----------------------------------+----+
|    |                                  |    |
| L  |                                  | R  |
| e  |       Central Widget             | t  |
| f  |       (your app's main UI)       | i  |
| t  |                                  | g  |
|    |                                  | h  |
| D  |                                  | t  |
| o  |                                  |    |
| c  |                                  | D  |
| k  |                                  | o  |
|    |                                  | c  |
+----+----------------------------------+----+
| StatusBar                                  |
+--------------------------------------------+

7.1 Central Widget

  • The required component. Set once; replacing it deletes the old one.
class MainWindow : public QMainWindow {
public:
    MainWindow() {
        auto *editor = new QTextEdit;
        setCentralWidget(editor);
    }
};

7.2 Menu Bar and QAction

  • QAction is the unit of "thing the user can trigger" — a menu item, toolbar button, shortcut, all bound to one signal.
auto *openAct = new QAction(QIcon(":/icons/open.png"), tr("&Open..."), this);
openAct->setShortcuts(QKeySequence::Open);          // Ctrl+O / Cmd+O per platform
openAct->setStatusTip(tr("Open a file"));
connect(openAct, &QAction::triggered, this, &MainWindow::openFile);

auto *fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(openAct);
  • Reuse the same QAction in menuBar(), toolBar(), and a context menu — one definition, three insertion points, one signal/slot connection.
  • QKeySequence::StandardKey enum (Open, Save, SaveAs, Print, Quit, Cut, Copy, Paste, ...) resolves to the platform-native shortcut at runtime.

7.3 Tool Bars

auto *fileTb = addToolBar(tr("File"));
fileTb->addAction(openAct);
fileTb->addAction(saveAct);
fileTb->addSeparator();
fileTb->addAction(quitAct);
  • Adding a QAction automatically creates a QToolButton with the action's icon and tooltip.
  • setMovable(true) (default on most styles) lets the user drag the toolbar between top/bottom/left/right; persist position with saveState() / restoreState().

7.4 Dock Widgets

  • Side panels that can be detached, rearranged, or hidden.
auto *dock = new QDockWidget(tr("Layers"), this);
dock->setWidget(layerListWidget);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
addDockWidget(Qt::LeftDockWidgetArea, dock);
  • The View menu typically populates from dock->toggleViewAction() — that action automatically reflects visibility state.

7.5 Status Bar

statusBar()->showMessage(tr("Ready"));               // temporary (clears after timeout)
statusBar()->showMessage(tr("Saving..."), 3000);     // 3-second message
statusBar()->addWidget(progressBar);                 // normal — left side
statusBar()->addPermanentWidget(connectionLabel);    // permanent — right side
  • Three regions:
    • TemporaryshowMessage() text, displaced by QAction::statusTip on hover.
    • NormaladdWidget(), left-aligned, hidden when a temporary message is showing.
    • PermanentaddPermanentWidget(), right-aligned, always visible.

8. Palette and Color Roles

  • QPalette is the named-color table a widget paints from. Each entry is a (group, role) pair: group = active/inactive/disabled, role = window/text/button/highlight/etc.
QPalette p = textEdit->palette();
p.setColor(QPalette::Base, Qt::black);          // background of editable area
p.setColor(QPalette::Text, Qt::green);          // text color
textEdit->setPalette(p);
Role Meaning
Window Widget background.
WindowText Foreground text on the window.
Base Background of editable / list widgets (line edits, lists).
AlternateBase Alternating row color in lists.
Text Text in Base-colored areas.
Button Background of buttons.
ButtonText Text on buttons.
Highlight Selected-item background.
HighlightedText Selected-item text.
  • For broad theming, prefer stylesheets (widget->setStyleSheet("QPushButton { ... }")) — they cascade and use CSS-like selectors. Palette changes are surgical edits to one role.

9. Transparency and Frameless Windows

  • Three independent kinds of transparency, often conflated:
Technique Affects Code
Alpha in palette/style One widget's painted color QColor(255, 255, 255, 128) (50% white)
setWindowOpacity(0.5) Whole window Hardware-accelerated; cheap
Translucent background Window background only setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground);
  • Frameless + translucent + custom paintEvent is the classic recipe for splash screens and custom-shaped windows:
void Splash::paintEvent(QPaintEvent *) {
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);
    p.fillRect(rect(), QColor(0, 0, 0, 180));            // 70% black overlay
    p.setPen(Qt::white);
    p.drawText(rect(), Qt::AlignCenter, tr("Loading..."));
}
  • Frameless windows lose all OS chrome (close button, drag region, resize handles). You implement these yourself with mousePressEvent/mouseMoveEvent to fake dragging, custom buttons for close/minimize.

10. Buddies and Tab Order

  • A "buddy" is the widget that gets focus when a QLabel's mnemonic is triggered:
auto *nameLabel = new QLabel(tr("&Name:"));
auto *nameEdit  = new QLineEdit;
nameLabel->setBuddy(nameEdit);          // Alt+N now focuses nameEdit
  • QFormLayout::addRow(label, field) wires the buddy automatically — yet another reason to prefer it for forms.
  • Tab order = the order widgets receive focus on Tab. Defaults to creation order; override with QWidget::setTabOrder:
QWidget::setTabOrder(firstName, lastName);
QWidget::setTabOrder(lastName,  email);
QWidget::setTabOrder(email,     submitButton);
  • Designer has a Tab Sequence editing mode (Edit → Edit Tab Order) — drag arrows visually instead of writing setTabOrder calls.

11. References