- 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);
col->addWidget(textArea);
col->addStretch();
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.
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);
grid->addWidget(new QLabel(tr("Address:")), 1, 0);
grid->addWidget(addressEdit, 1, 1, 2, 2);
grid->setColumnStretch(1, 1);
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 nameEdit — QFormLayout 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);
edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
text->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
| 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);
splitter->setStretchFactor(1, 4);
splitter->setCollapsible(0, false);
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 |
+--------------------------------------------+
- 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);
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.
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().
- 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"));
statusBar()->showMessage(tr("Saving..."), 3000);
statusBar()->addWidget(progressBar);
statusBar()->addPermanentWidget(connectionLabel);
- Three regions:
- Temporary —
showMessage() text, displaced by QAction::statusTip on hover.
- Normal —
addWidget(), left-aligned, hidden when a temporary message is showing.
- Permanent —
addPermanentWidget(), 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);
p.setColor(QPalette::Text, Qt::green);
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));
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);
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