Multi-form development in Lazarus

1. Dual Forms

We will implement a popup feature with Form1 (main form) + Form2 (popup window)

1. Operation Steps

  1. Create a new project, drag Button1 onto Form1, set Caption = Open Form2, and generate the OnClick event (you can double-click the button directly in the form designer to quickly create the click event);
  2. Use the top menu File → New Form to create Form2;
  3. Place Button1 on Form2, set Caption = Close, and generate its OnClick event;
  4. Shortcuts for switching forms: Shift+F12 open form list / F12 toggle design/code view;
  5. Reference Unit2 in Unit1: add Unit2 inside interface uses.

Unit1 (Main Form)

Two key points to note: Unit2; // Import Unit2

The second point is inside the click handler: open Form2 as a modal dialog with Form2.ShowModal; // Pop up Form2 modally

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Unit2; // Import Unit2 

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form2.ShowModal; // Pop up Form2 modally
end;

end.

Unit2 (Child Form)

Add the second form to your project

After creation, an extra tab will appear in the code editor

One tab holds the main form source code, the other stores Form2’s backend code

To edit Form2, select Unit2 and press F12 to open its designer window

Add a button, set its caption to Close, and implement the close logic as covered in previous lessons. You may complete this part on your own.

unit Unit2;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;

type

  { TForm2 }

  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  Form2: TForm2;

implementation

{$R *.lfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  Close; // Close the current Form2
end;

end.

2. Core Differences Between Show and ShowModal (Comparison Demo)

UI Modification

Add two buttons on Form1:

  • btnShow: Caption = Show
  • btnShowModal: Caption = ShowModal
// Non-modal Show
procedure TForm1.btnShowClick(Sender: TObject);
begin
  Form2.Show;
  ShowMessage('Form2 opened, main form remains interactive');
end;

// Modal ShowModal
procedure TForm1.btnShowModalClick(Sender: TObject);
begin
  Form2.ShowModal;
  ShowMessage('This prompt only appears after closing Form2');
end;Code language: JavaScript (javascript)

Difference Breakdown

  1. Show (Non-modal)
  • Code execution continues immediately after opening the child form;
  • The main form can be dragged and other buttons clicked freely;
  • Equivalent to Form2.Visible := True; and auto-brings the window to front.
  1. ShowModal (Modal Popup)
  • Code pauses until the child form is closed;
  • The main form is locked and cannot be operated;
  • It is a function that returns ModalResult to detect how the window was closed.

In short: A modal window blocks interaction with the main form until closed, while a non-modal window lets you operate both windows simultaneously.

3. Cross-Form Calls: Resolve Circular Reference Errors

Requirement: Form1 opens Form2, Form2 opens Form1
Mutual references inside interface uses trigger circular unit errors. Solution: Move references to the implementation section

Unit1

unit Unit1;

interface
uses Forms,StdCtrls;

type
  TForm1=class(TForm)
    Button1:TButton;
    procedure Button1Click(Sender:TObject);
  end;
var Form1:TForm1;

implementation
uses Unit2; // Place reference here
{$R *.lfm}

procedure TForm1.Button1Click(Sender:TObject);
begin
  Form2.Show;
end;
end.

Unit2

unit Unit2;

interface
uses Forms,StdCtrls;

type
  TForm2=class(TForm)
    Button1:TButton;
    procedure Button1Click(Sender:TObject);
  end;
var Form2:TForm2;

implementation
uses Unit1; // Mutual references go in implementation
{$R *.lfm}

procedure TForm2.Button1Click(Sender:TObject);
begin
  Form1.Show;
end;
end.

4. Pass Values Between Forms (Public Member Variable Example)

Function: Increment a counter each time the main form opens the child form; the child form can display the open count

Define Public Integer Variable in Unit2

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
  public
    Count: Integer; // Public variable accessible from Unit1
  end;

Increment Counter on Unit1 Button Click

procedure TForm1.Button1Click(Sender: TObject);
begin
  Inc(Form2.Count); // Increase counter by 1
  Form2.ShowModal;
end;Code language: JavaScript (javascript)

Display Open Count Popup in Unit2

procedure TForm2.Button1Click(Sender: TObject);
begin
  ShowMessage('This form has been opened '+IntToStr(Count)+' times');
end;Code language: JavaScript (javascript)

Coding standard recommendation: Minimize use of global/public variables; prefer Property parameters for value passing.

5. Dynamically Create Forms

No reliance on auto-generated global form variables from the project

Method 1: Dynamic Creation of Pre-designed Form2

  1. Open Project.lpr and delete Application.CreateForm(TForm2, Form2);
  2. Form1 button code:
procedure TForm1.Button1Click(Sender: TObject);
var
  frm2: TForm2;
begin
  frm2 := TForm2.Create(nil); // Manual instance creation
  frm2.ShowModal;
  FreeAndNil(frm2); // Release memory to avoid leaks
end;Code language: JavaScript (javascript)

Method 2: Fully Code-generated Form (No Pre-design Required)

Click the button to generate a brand new form with a built-in close button

unit Unit1;

interface

uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure DynamicBtnClick(Sender: TObject); // Custom dynamic button click handler
  public
  end;

var Form1: TForm1;

implementation
{$R *.lfm}

procedure TForm1.DynamicBtnClick(Sender: TObject);
begin
  ShowMessage('Closing dynamic form now');
  TForm(TButton(Sender).Parent).Close;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyForm: TForm;
  MyBtn: TButton;
begin
  MyForm := TForm.Create(nil);
  MyForm.SetBounds(100,100,220,150);
  MyForm.Caption := 'Dynamically Generated Form';

  // Create button owned by MyForm; auto-destroyed when form closes
  MyBtn := TButton.Create(MyForm);
  MyBtn.SetBounds(10,10,200,30);
  MyBtn.Caption := 'Close Form';
  MyBtn.Parent := MyForm;
  MyBtn.OnClick := @DynamicBtnClick;

  MyForm.ShowModal;
  FreeAndNil(MyForm);
end;
end.

Choose Debug Information format Prompt Appears

Simply configure the debug format to resolve this.

Multi-form development in Lazarus

Leave a Reply

Your email address will not be published. Required fields are marked *