GUI Development: Lazarus LCL, Delphi VCL & FMX

All three share the same model: a form class whose published fields are components placed in the designer, properties set in the Object Inspector, and event handlers (method pointers) written in code.

38.1 Frameworks at a glance

LCL (Lazarus Component Library) Windows, Linux, macOS, FreeBSD, Raspberry Pi native widgets via *widgetsets*: win32, gtk2, gtk3, qt5, qt6, cocoa VCL (Visual Component Library) Windows (32/64-bit) native Win32 controls, VCL Styles FMX (FireMonkey)

Windows, macOS, iOS, Android, Linux (via FMXLinux) GPU-rendered, styled controls

All three share the same model: a form class whose published fields are components placed in the designer, properties set in the Object Inspector, and event handlers (method pointers) written in code.

38.2 A Lazarus form

The project file (todoapp.lpr) generated by Lazarus:

Part of the form file (mainform.lfm) — plain text, editable, and version-control friendly:

unit MainForm;

{$mode objfpc}{$H+}

interface

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

type
  TForm1 = class(TForm)
    Edit1: TEdit;                  // created from the .lfm file
    Button1: TButton;
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}                         // links the form description

procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'To-Do List';
  Edit1.TextHint := 'New task...';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Trim(Edit1.Text) = '' then
  begin
    ShowMessage('Please enter a task.');
    Exit;
  end;
  ListBox1.Items.Add(Trim(Edit1.Text));
  Edit1.Clear;
  Edit1.SetFocus;
end;

procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
  if (ListBox1.ItemIndex >= 0) and
     (MessageDlg('Delete this task?', mtConfirmation, [mbYes, mbNo], 0) = mrYes) then
    ListBox1.Items.Delete(ListBox1.ItemIndex);
end;

end.
program TodoApp;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}cthreads,{$ENDIF}
  Interfaces,                      // selects the LCL widgetset
  Forms, MainForm;

{$R *.res}

begin
  RequireDerivedFormResource := True;
  Application.Scaled := True;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
object Form1: TForm1
  Left = 300
  Height = 360
  Top = 200
  Width = 420
  Caption = 'Form1'
  OnCreate = FormCreate
  object Edit1: TEdit
    Left = 8
    Top = 8
    Width = 300
  end
  object Button1: TButton
    Left = 316
    Top = 8
    Caption = 'Add'
    OnClick = Button1Click
  end
end

38.3 Creating controls at run time

Owner controls lifetime; Parent controls display. Set Align, Anchors, and BorderSpacing (LCL) or Margins/Padding (VCL/FMX) for resizable layouts.

procedure TForm1.AddButton(const ACaption: string; ATop: Integer);
var
  B: TButton;
begin
  B := TButton.Create(Self);        // owner: the form frees it
  B.Parent := Self;                 // parent: where it is displayed
  B.Caption := ACaption;
  B.SetBounds(8, ATop, 120, 30);
  B.OnClick := @Button1Click;       // Delphi: B.OnClick := Button1Click;
end;

38.4 Delphi VCL and FMX differences

Vcl.Forms, Vcl.StdCtrls, Vcl.Dialogs FMX.Forms, FMX.StdCtrls, FMX.Dialogs

Caption (labels/buttons), Text (edits) Text everywhere Integer pixels Single (device-independent) native Windows + VCL Styles styles (TStyleBook), platform-specific default styles

Align, Anchors, TGridPanel, TFlowPanel Align, TLayout, TGridPanelLayout, TFlowLayout, TScaledLayout GDI/GDI+, TCanvas, Direct2D

GPU canvas, 3D (TViewport3D), effects, animations data-aware controls (TDBGrid, TDBEdit) and LiveBindings

38.5 Common components across frameworks

TButton, TBitBtn, TSpeedButton TButton, TSpeedButton

TCheckBox, TRadioButton, TRadioGroup TCheckBox, TRadioButton

TListBox, TComboBox, TListView, TTreeView TStringGrid, TDrawGrid, TDBGrid TStringGrid, TGrid

TPanel, TGroupBox, TPageControl, TScrollBox TPanel, TLayout, TTabControl, TScrollBox

TMainMenu, TPopupMenu, TActionList TOpenDialog, TSaveDialog, ShowMessage, MessageDlg

TOpenDialog, ShowMessage, TDialogService TPaintBox + OnPaint, TImage TPaintBox, TImage

38.6 GUI guidelines

Keep business logic out of form units: forms call services/classes in separate units (testable without a GUI).

Use TActionList to share logic between menus, toolbars, and buttons.

Never block the main thread with long operations — use threads/tasks and update the UI via Synchronize/Queue.

Test High-DPI scaling (Application.Scaled, per-monitor DPI awareness in project options).

For Lazarus cross-platform apps, avoid Windows-only units and check each widgetset.