Tutorial: Working with Forms
Most real-world applications need more than one window. In this tutorial you will build a multi-form project with a main form and a settings dialog. You will learn how to open forms modally, pass data between them, and configure common form properties.
Step 1: Set Up the Project
- Create a new project called
MultiFormDemovia File → New Project. - The IDE creates
main.prgwith a starter form. - We will add a second source file for the settings form in the next step.
Step 2: Design the Main Form
- Open
main.prgin the Form Designer. - Set the form title to
"Multi-Form Demo"and size to700, 500. - Add a Label at the top to display the current username — set
cValueto"User: (none)". - Add a Button with
cPromptset to"Open Settings".
Your main form code will look like this:
#include "hbbuilder.ch" function Main() local oForm, oLblUser, oBtnSettings DEFINE FORM oForm TITLE "Multi-Form Demo" ; SIZE 700, 500 FONT "Segoe UI", 10 @ 30, 50 LABEL oLblUser VALUE "User: (none)" ; OF oForm SIZE 300, 24 @ 80, 50 BUTTON oBtnSettings PROMPT "Open Settings" ; OF oForm SIZE 140, 36 ; ACTION OpenSettings( oForm, oLblUser ) ACTIVATE FORM oForm CENTERED return nil
Step 3: Create the Settings Form
- Select File → New Form to add a second form to the project. Name it
settings.prg. - In the Form Designer for
settings.prg, set the title to"Settings"and size to400, 300. - Add a Label with
cValue"Your name:"at row 30, col 30. - Add a TextBox (
TGet) next to the label for the user to type their name. - Add a CheckBox below with
cPrompt"Enable dark mode". - Add two buttons: "OK" and "Cancel" at the bottom.
Step 4: Implement ShowModal and Data Passing
The key technique is to call ShowModal() on the settings form. This blocks the main form
until the user closes the dialog. After the modal form closes, you read back the values the
user entered.
static function OpenSettings( oMainForm, oLblUser ) local oSettings, oGetName, oChkDark, oBtnOK, oBtnCancel local cName := "", lDark := .F., nResult DEFINE FORM oSettings TITLE "Settings" ; SIZE 400, 300 FONT "Segoe UI", 10 ; STYLE "dialog" @ 30, 30 LABEL oLbl VALUE "Your name:" ; OF oSettings SIZE 100, 24 @ 30, 140 GET oGetName VAR cName ; OF oSettings SIZE 220, 24 @ 70, 30 CHECKBOX oChkDark VAR lDark ; PROMPT "Enable dark mode" ; OF oSettings SIZE 200, 24 @ 220, 180 BUTTON oBtnOK PROMPT "OK" ; OF oSettings SIZE 90, 32 ; ACTION ( oSettings:nModalResult := 1, oSettings:Close() ) @ 220, 280 BUTTON oBtnCancel PROMPT "Cancel" ; OF oSettings SIZE 90, 32 ; ACTION ( oSettings:nModalResult := 0, oSettings:Close() ) nResult := oSettings:ShowModal( oMainForm ) if nResult == 1 oLblUser:SetValue( "User: " + cName ) if lDark oMainForm:SetDarkMode( .T. ) else oMainForm:SetDarkMode( .F. ) endif endif return nil
Use ShowModal( oParent ) when the user must complete the dialog before returning to the parent.
Use Show() for non-blocking windows that the user can switch between freely.
Step 5: Form Properties Reference
Here are the most commonly used form properties you can set in the Object Inspector or in code:
| Property | Type | Description |
|---|---|---|
cTitle |
String | Text shown in the form's title bar. |
nWidth / nHeight |
Numeric | Form dimensions in pixels. |
lMaximize |
Logical | Whether the maximize button is enabled. |
lMinimize |
Logical | Whether the minimize button is enabled. |
lResizable |
Logical | Whether the user can resize the form. |
cStyle |
String | "normal", "dialog", or "toolwindow". |
nModalResult |
Numeric | Return value after ShowModal(). Set before calling Close(). |
Step 6: Build and Test
- Press F9 to build and run.
- Click "Open Settings" on the main form — the settings dialog appears modally.
- Type a name, toggle the checkbox, and click OK.
- The main form label updates to show the name you entered.
- Open the dialog again and click Cancel — the main form remains unchanged.
Class-Based Form Format
HarbourBuilder also supports a class-based form format that matches the IDE designer's output. This format is useful for more complex applications:
#include "hbbuilder.ch" CLASS TForm1 FROM TForm // IDE-managed Components DATA oLblUser // TLabel DATA oBtnSettings // TButton // Event handlers METHOD BtnSettingsClick() METHOD CreateForm() ENDCLASS METHOD CreateForm() CLASS TForm1 ::Title := "Multi-Form Demo" ::Left := 100 ::Top := 100 ::Width := 700 ::Height := 500 ::FontName := "Segoe UI" ::FontSize := 10 @ 30, 50 LABEL ::oLblUser VALUE "User: (none)" OF Self SIZE 300, 24 @ 80, 50 BUTTON ::oBtnSettings PROMPT "Open Settings" OF Self SIZE 140, 36 // Event wiring ::oBtnSettings:OnClick := { || ::BtnSettingsClick() } return nil METHOD BtnSettingsClick() CLASS TForm1 local oSettings := TSettings():New() oSettings:ShowModal( Self ) if oSettings:nModalResult == 1 ::oLblUser:cText := "User: " + oSettings:cUserName endif return nil FUNCTION Form1() LOCAL oForm := TForm1():New() oForm:CreateForm() oForm:Activate() RETURN oForm
Using OnCreate for Initialization
For initialization that requires child controls to have their HWNDs created, use the OnCreate event:
function Form1Create( oSelf ) // This runs after all child controls have been created oSelf:oList:SetItems( { "Item 1", "Item 2", "Item 3" } ) oSelf:oLblStatus:cText := "Ready" return nil
The IDE automatically detects functions named FormNCreate and wires them to the form's ::OnCreate event.
Form Lifecycle Flow
ACTIVATE FORM CENTERED"] --> B["User clicks
Open Settings"] B --> C["Settings Form
ShowModal( oMainForm )"] C --> D{"User action"} D -->|"OK"| E["nModalResult = 1
Close()"] D -->|"Cancel"| F["nModalResult = 0
Close()"] E --> G["Main form reads
cName and lDark"] F --> H["Main form ignores
dialog values"] style A fill:#58a6ff,stroke:#388bfd,color:#0d1117 style C fill:#d2a8ff,stroke:#bc8cff,color:#0d1117 style E fill:#3fb950,stroke:#2ea043,color:#0d1117 style F fill:#f0883e,stroke:#d18616,color:#0d1117
Now that you can work with multiple forms, continue to the Event Handling tutorial to learn about all the event types available in HarbourBuilder.