Tutorial: Event Handling
Events are the backbone of interactive applications. Every time a user clicks a button, types a key, resizes a window, or closes a form, HarbourBuilder fires an event that your code can handle. This tutorial covers all the major event types and the different ways to respond to them.
Step 1: Understanding Events in HarbourBuilder
Every HarbourBuilder control exposes a set of event properties (e.g. OnClick, OnChange).
You assign a code block or a method reference to these properties. When the event
fires, HarbourBuilder executes your handler.
(click, key, resize)"] --> B["OS Message
WM_COMMAND etc."] B --> C["HarbourBuilder
Event Dispatcher"] C --> D["Your Handler
Code Block / Method"] style A fill:#58a6ff,stroke:#388bfd,color:#0d1117 style B fill:#d2a8ff,stroke:#bc8cff,color:#0d1117 style C fill:#3fb950,stroke:#2ea043,color:#0d1117 style D fill:#f0883e,stroke:#d18616,color:#0d1117
Step 2: Generating Event Handlers via the Object Inspector
- Select a control on the form (e.g. a Button).
- In the Object Inspector, switch to the Events tab.
- Double-click the event name (e.g.
OnClick). - The IDE automatically:
- Creates a
static functionstub in your source file. - Assigns the code block to call that function.
- Jumps the cursor to the new function body so you can start coding.
- Creates a
Double-clicking an event in the Object Inspector is the recommended workflow. It generates
properly named handlers (e.g. OnBtnSaveClick) and wires up the code block automatically.
Step 3: OnClick — Button and Control Clicks
The most common event. Fires when the user clicks a button, label, image, or any clickable control.
Inline code block (simple logic):
oBtn:OnClick := { || MsgInfo( "Button was clicked!" ) }
Calling a separate function (complex logic):
oBtn:OnClick := { || OnBtnSaveClick( oForm, oGet ) } static function OnBtnSaveClick( oForm, oGet ) local cValue := oGet:GetValue() if Empty( cValue ) MsgAlert( "Please enter a value." ) return nil endif MsgInfo( "Saved: " + cValue ) oForm:SetTitle( "Saved!" ) return nil
Step 4: OnChange — Value Changes
Fires whenever the value of an input control changes. Useful for TextBox (TGet), ComboBox,
CheckBox, and Spinner controls.
oGetSearch:OnChange := { || OnSearchChange( oGetSearch, oListBox ) } static function OnSearchChange( oGet, oList ) local cFilter := oGet:GetValue() oList:Filter( { |cItem| Upper( cFilter ) $ Upper( cItem ) } ) return nil
For TextBox controls, OnChange fires after each character typed. If you are doing expensive
operations (like database queries), consider using a TTimer to debounce the input.
Step 5: OnKeyDown — Keyboard Input
Fires when a key is pressed while the control has focus. The handler receives the key code and modifier flags as parameters.
oGet:OnKeyDown := { |nKey, nFlags| OnGetKeyDown( nKey, nFlags, oGet ) } static function OnGetKeyDown( nKey, nFlags, oGet ) do case case nKey == VK_RETURN MsgInfo( "Enter pressed! Value: " + oGet:GetValue() ) case nKey == VK_ESCAPE oGet:SetValue( "" ) case nKey == VK_F1 MsgInfo( "Help: type a value and press Enter." ) endcase return nil
Common key constants: VK_RETURN, VK_ESCAPE, VK_TAB, VK_DELETE,
VK_F1 through VK_F12, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT.
Step 6: OnResize — Form and Control Resizing
Fires when the user resizes the form. Use this to reposition or resize controls dynamically for a responsive layout.
oForm:OnResize := { || OnFormResize( oForm, oMemo, oStatusBar ) } static function OnFormResize( oForm, oMemo, oStatusBar ) local nW := oForm:nWidth local nH := oForm:nHeight // Make the memo fill the form with a 10-pixel margin oMemo:SetSize( nW - 20, nH - 80 ) // Keep status bar at the bottom oStatusBar:SetPos( nH - 30, 0 ) oStatusBar:SetSize( nW, 30 ) return nil
Step 7: OnClose — Form Closing
Fires when the user tries to close the form (clicking the X button, pressing Alt+F4, or calling
Close()). Return .F. from the handler to prevent the form from closing — useful for
unsaved-changes prompts.
oForm:OnClose := { || OnFormClose( oForm ) } static function OnFormClose( oForm ) local nAnswer if lDataModified nAnswer := MsgYesNoCancel( "Save changes before closing?" ) do case case nAnswer == 1 // Yes SaveData() return .T. case nAnswer == 2 // No return .T. case nAnswer == 3 // Cancel return .F. // Prevent closing endcase endif return .T.
This is a powerful pattern for data-entry forms. Always give the user a way to force-close (e.g. the Cancel option) so they are never trapped in a form they cannot exit.
Event Parameters Summary
| Event | Parameters | Return | Controls |
|---|---|---|---|
OnClick |
(none) | ignored | Button, Label, Image, Panel |
OnChange |
(none) | ignored | TGet, ComboBox, CheckBox, Spinner |
OnKeyDown |
nKey, nFlags |
ignored | All focusable controls |
OnResize |
(none) | ignored | TForm |
OnClose |
(none) | .T. allow, .F. prevent |
TForm |
Code Block vs. Method Handler
HarbourBuilder supports two styles for event handlers:
Code block — best for short, inline logic:
oBtn:OnClick := { || oLabel:SetValue( Time() ) }
Method handler — best for complex logic that needs its own function:
oBtn:OnClick := { || OnBtnClick( oForm ) } static function OnBtnClick( oForm ) // Complex logic here: database calls, validation, etc. ... return nil
If the handler is more than one expression, use a separate function. It keeps your form definition clean and makes the logic easier to debug and test.
Now that you understand events, continue to the Database CRUD tutorial to build a data-driven application with SQLite.