Frames and panels: the skeleton of every app
Every wxRuby3 application is built on the same foundation: a frame containing a panel containing everything else. This lesson builds that foundation step by step. By the end you will have a working skeleton that you will reuse — with small modifications — for every app in this series.
A note on typing vs copying. The examples in this series are short enough to type. We strongly recommend typing rather than copying and pasting. The small friction of typing forces you to read each line, and reading each line is how the patterns become familiar. Copy-pasting produces files that run; typing produces understanding.
Step 1 — The minimal app
Create a new file called app.rb and type the following:
|
|
Run it:
|
|
A small native window appears with the title “My App”. Close it and the script exits. That is a complete wxRuby3 application — an event loop started, a window displayed, and a clean exit when the window closed.
Two things to notice. First, AppFrame inherits from Wx::Frame — the class that provides the title bar, the close button, and all the window management the operating system expects. Second, Wx::App.run creates the application object, calls the block (which creates and shows the frame), then starts the event loop. The loop runs until the last window closes.
Step 2 — Size and position
Update initialize to give the window a consistent size and centre it on screen:
|
|
Run it. The window is now 800×600 pixels and centred on screen. centre (both spellings — centre and center — work) positions the window relative to the display each time the app opens.
To prevent the user from resizing the window smaller than your layout requires, add a minimum size after super:
|
|
Run it and try dragging the window smaller — it stops at 640×480. You can remove this line for now; it is here to show it exists. We will set minimum sizes in later lessons when layouts need it.
Step 3 — Structure: build_ui and bind_events
A frame that does everything in initialize becomes hard to read quickly. The convention used throughout this series splits setup into two private methods. Update app.rb to match this structure exactly — you will use it for every app from here on:
|
|
Run it. The window opens and closes exactly as before — the empty methods change nothing. The structure is now in place.
build_ui is where all widget creation goes. bind_events is where all evt_* handler registrations go. Keeping them separate means you always know where to look: find the widget in build_ui, find its behaviour in bind_events.
Step 4 — Adding a panel
Controls should never be placed directly inside a frame. Add a panel inside build_ui:
|
|
Run it. The window looks identical — a panel with no controls is invisible. But two important things are now in place.
Keyboard navigation. Tab order between controls only works correctly when they are parented to a panel. Controls placed directly on a frame do not participate in tab order on all platforms.
Native background. The panel paints the correct system background colour. Controls placed directly on a frame can produce visual artefacts on some platforms — wrong colours, flickering, or mismatched backgrounds.
The rule is simple and has no exceptions in this series: all controls go inside the panel, never directly on the frame.
Note that @panel is an instance variable. We will add controls to it in the next lesson, and those controls need to reference the panel from other methods — instance variables make that possible.
Step 5 — The status bar
A status bar is a strip along the bottom of the window for communicating state without interrupting the user with dialogs. Add it to build_ui:
|
|
Run it. A narrow strip appears at the bottom of the window showing “Ready”. You can update it from anywhere in the frame with set_status_text. The status bar is optional — not every app needs one — but it is easy to add here and remove later if not needed.
Step 6 — Handling the close event
By default, closing the window destroys the frame and exits the app. If you need to intercept the close — to ask “save changes?” before exiting — handle evt_close in bind_events. Add the following:
|
|
Run it. The app closes normally. event.skip tells wxRuby3 to continue with the default close behaviour. This is the important part: without event.skip, the window will not close. Handling evt_close consumes the event — the default action only happens if you explicitly pass it on with skip.
We will extend on_close in Module 3 to prompt the user when there are unsaved changes. For now the placeholder is correct.
The finished skeleton
Your app.rb should now look exactly like this:
|
|
Keep this file open. In the next lesson you will add controls to build_ui and wire them up in bind_events, one widget at a time.
Previous: Exploring the sampler | Next: Core widgets