Layout with sizers
The form you built in the previous lesson works, but it looks rough. The widgets are positioned with hardcoded pos: coordinates — manual arithmetic that breaks the moment you resize the window, change a font, or add a new field. This is not how wxRuby3 apps are meant to be laid out.
Sizers are wxRuby3’s layout system. Rather than fixing pixel positions, sizers describe relationships between widgets — “these two controls should sit side by side”, “this widget should expand to fill available space”, “leave 8 pixels of padding around everything”. The sizer calculates the actual positions at runtime, and recalculates automatically whenever the window is resized.
In this lesson you will learn the two sizers you will reach for in almost every app, and then replace every pos: in your form with a proper sizer layout.
How sizers work
A sizer is an invisible layout container. You add widgets to it, tell it how each widget should behave, and attach it to a panel. The panel then delegates all size calculations to the sizer.
The basic pattern:
|
|
Two calls complete the setup. panel.set_sizer attaches the sizer to the panel — without it the sizer exists but has no effect. Then layout called on the frame tells the frame to resize the panel to fill the window and trigger the sizer calculations. Without layout, the panel stays at its default size and widgets bunch up in the corner.
|
|
In the skeleton from lesson 2.1, layout belongs in initialize after build_ui is called.
VBoxSizer and HBoxSizer
Wx::VBoxSizer stacks widgets vertically, top to bottom. Wx::HBoxSizer arranges them horizontally, left to right. These two sizers, nested inside each other, can produce almost any layout you will need.
Create a new file sizer_demo.rb and type this:
|
|
Run it. Three buttons stacked vertically, each with 8 pixels of space around them. Try resizing the window — the buttons stay at their natural size but the empty space adjusts. Now change VBoxSizer to HBoxSizer and run again — the same three buttons arranged in a row.
The add method
sizer.add(widget, proportion, flags, border) is the method you will type hundreds of times. Its four arguments control two distinct things — how much space a widget gets, and how it fills that space — and it is worth understanding them separately.
proportion — space along the main axis
In a VBoxSizer, the main axis is vertical. Proportion controls how much of the available vertical space each widget receives when the window is taller than the widgets need.
0— the widget takes only the height it needs; any leftover vertical space goes elsewhere1— the widget gets a share of leftover vertical space2— the widget gets twice as much leftover space as a widget with proportion1
To see this clearly, update sizer_demo.rb — note there is no Wx::EXPAND here so we isolate proportion from width behaviour:
|
|
Run it and resize the window vertically. Button A stays at its natural height. Buttons B and C grow taller to fill the extra vertical space — C always gets twice as much as B. The buttons stay at their natural width because Wx::EXPAND is not in the flags.
Wx::EXPAND — filling perpendicular space
Wx::EXPAND is a flag, not a proportion argument. In a VBoxSizer it tells the widget to fill the full available width — perpendicular to the main axis. Without it, widgets sit at their natural width regardless of how wide the window is.
Update the demo to see the difference:
|
|
Run it and resize in both directions. Button A stays small and fixed. Button B stretches to fill the window width but stays at natural height. Button C stretches in both directions — full width from Wx::EXPAND, growing height from proportion 1.
This is the combination you will use most in real forms: proportion 1 with Wx::EXPAND on input fields so they grow both ways with the window.
flags for borders and alignment
The remaining flags control which sides the border applies to, and how the widget aligns within its allocated space:
| Flag | Effect |
|---|---|
Wx::ALL |
Border on all four sides |
Wx::TOP, Wx::BOTTOM, Wx::LEFT, Wx::RIGHT |
Border on specific sides only |
Wx::ALIGN_CENTER |
Centre the widget in its allocated space |
Wx::ALIGN_CENTER_VERTICAL |
Centre vertically — useful in HBoxSizer rows |
Wx::ALIGN_RIGHT |
Align to the right of allocated space |
Flags are combined with |:
|
|
border
The last argument is the number of pixels of padding to apply on whichever sides are specified by the flags. Wx::ALL, 8 means 8 pixels on every side. Wx::TOP | Wx::BOTTOM, 4 means 4 pixels top and bottom, none left or right.
FlexGridSizer
Wx::FlexGridSizer arranges widgets in a grid of rows and columns. It is the right tool for forms — the classic “label on the left, control on the right” layout.
|
|
rows— number of rows. Use0to let the sizer calculate rows automatically from the number of itemscols— number of columnsvgap— vertical gap between rows in pixelshgap— horizontal gap between columns in pixels
FlexGridSizer differs from the simpler GridSizer in one important way: columns (and rows) can have different widths. You can mark a column as growable, so it expands to fill available space while other columns stay fixed:
|
|
For a two-column form, this means the label column stays narrow and the input column stretches as the window widens.
Try it — replace sizer_demo.rb with this:
|
|
Run it and resize the window. The label column holds its width; the input fields expand to fill the space. This is the pattern at the heart of every form in this series.
Notice the outer VBoxSizer wrapping the grid — this is the standard pattern. The FlexGridSizer handles the grid layout; an outer VBoxSizer adds the margin around it and positions it within the panel. Direct-to-panel without an outer sizer leaves no margin at the window edges.
Nested sizers
The real power of sizers comes from nesting them. An HBoxSizer inside a VBoxSizer row lets you put multiple widgets side by side within a vertically stacked layout — exactly what you need for the radio button rows in your form.
|
|
The vbox sees one item in that row — the HBoxSizer — and positions it like any other widget. Inside, the HBoxSizer arranges its two buttons horizontally.
Replacing pos: in your form
Now apply what you have learned. Open app.rb from the previous lesson. You are going to remove every pos: and size: argument and replace the whole layout with sizers.
The layout goal is a two-column form: labels on the left at fixed width, controls on the right expanding to fill space. Radio button rows need an inner HBoxSizer to arrange the buttons horizontally. The slider row needs an HBoxSizer to put the value label beside the slider.
The finished lesson app
Your complete app.rb with sizers replacing all absolute positioning:
|
|
This is the same form as the end of lesson 2.2 — same widgets, same events, same handlers — but now it looks right, resizes correctly, and required no manual pixel arithmetic to produce.
What changed and why
A few things in the new layout are worth pausing on.
Empty StaticText as a spacer. The subscribe checkbox and the radio group labels need to occupy the right column, leaving the left column empty. Wx::StaticText.new(@panel, label: '') creates an invisible placeholder that occupies the left cell without displaying anything. This is the simplest way to skip a cell in a FlexGridSizer.
HBoxSizer inside a grid cell. The size and colour rows each contain multiple radio buttons side by side. The FlexGridSizer can only hold one item per cell, so you put an HBoxSizer in the cell and add the buttons to that. The grid positions the sizer; the sizer positions the buttons within it.
proportion on the age row. The age_row HBoxSizer is added to the grid with proportion 1 and Wx::EXPAND, so it stretches to fill the full right column. Inside, the slider has proportion 1 (it expands) and the label has proportion 0 (it stays fixed). This gives the slider all the growing space while keeping the number label pinned to its right edge.
Previous: Core widgets | Next: Menus and toolbars