Catalog · How the catalog works
lazarus-jgd-form-layout
JGoodies Forms Layout inspired for Lazarus IDE
- ★ 2 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
JGD Forms Layout for Lazarus IDE
A powerful grid-based layout manager component for Lazarus IDE, inspired by the JGoodies Forms Layout library from the Java world. Build responsive, professional desktop forms with declarative grid specifications.
Language: Pascal (Free Pascal Compiler)
License: MIT
Repository: jedt3d/lazarus-jgd-form-layout
Table of Contents
- Overview
- Features
- Installation
- Core Component Architecture
- Usage Guide
- Examples
- Unit Tests
- Key Classes & Types
- Design Principles
- Troubleshooting
Overview
The JGD Forms Layout component brings the elegance and power of the JGoodies Forms Layout pattern to Lazarus and Free Pascal development. Instead of struggling with anchors, margins, and manual calculations, simply declare your grid layout as a string specification.
This layout engine handles:
- Grid-based positioning of controls
- Automatic sizing (preferred, minimum, constant)
- Dialog Unit (DLU) support for DPI-independent layouts
- Flexible growth weights for responsive resizing
- Column/row spanning for complex layouts
- Alignment control (left, right, center, fill) per control
Features
✅ Declarative Layout System – Define grids with human-readable string specs
✅ Dialog Units (DLU) – Font-relative sizing for DPI-independent layouts
✅ Growth Weights – Distribute surplus space proportionally
✅ Design-Time Integration – Full Object Inspector support in Lazarus IDE
✅ Span Support – Controls can occupy multiple cells
✅ Automatic Sizing – Preferred, minimum, and constant size modes
✅ Unit Tested – Comprehensive FPCUnit test suite
✅ Production Ready – Clean architecture with zero external dependencies beyond LCL
Installation
Step 1: Get the Package
Clone or download the repository:
git clone https://github.com/jedt3d/lazarus-jgd-form-layout.git
cd lazarus-jgd-form-layoutStep 2: Install in Lazarus IDE
- Open Lazarus IDE
- Go to Package → Open Package File
- Navigate to and select
jgd_forms_layout.lpk - Click Compile
- After compilation, click Install to register the component in the IDE
- Lazarus will rebuild and the
TJgdFormLayoutcomponent will appear in the JGoodies component palette
Step 3: Verify Installation
Create a new application and check that TJgdFormLayout is available in the Designer component palette under the JGoodies tab.
macOS / Apple Silicon Build Note
On recent macOS/Xcode toolchains, Lazarus Cocoa builds can fail during the final link step even after this package compiles successfully. The failure usually looks like this:
ld: malformed method list atom ... cocoawsextctrls.oThis is a Lazarus/FPC Cocoa linking issue, not a jgd_forms_layout compile
error. Rebuild the IDE with Apple's classic linker option:
lazbuild --add-package jgd_forms_layout.lpk --build-ide=-k-ld_classicIf the package is already registered in Lazarus, rebuild the IDE with:
lazbuild --build-ide=-k-ld_classicCocoa example applications in this repository already include the shared
@../macos-linker.cfg response file in their Lazarus project options. That
file applies the linker workaround only when FPC is targeting macOS:
#ifdef darwin
-k-ld_classic
#endifThat means the examples can be built normally:
lazbuild ex_simple/SimpleApp.lpiWhen code signing a Lazarus-generated .app bundle on macOS, make sure the
bundle executable under Contents/MacOS is a regular file before running
codesign. Lazarus creates that entry as a symlink to the outer executable,
and Apple's signing tool rejects a symlink as the main bundle executable.
Replace the symlink with a copy of the built executable, then sign the bundle:
rm -f lib/aarch64-darwin/SimpleApp.app/Contents/MacOS/SimpleApp
cp lib/aarch64-darwin/SimpleApp \
lib/aarch64-darwin/SimpleApp.app/Contents/MacOS/SimpleApp
codesign --force --deep --sign - lib/aarch64-darwin/SimpleApp.appIn the Lazarus IDE, add -k-ld_classic to the selected IDE build profile before
rebuilding from Package > Install/Uninstall Packages. Apple marks the classic
linker as deprecated, so recheck this workaround after macOS, Xcode, Lazarus, or
FPC upgrades.
Core Component Architecture
Main Classes
TJgdFormLayout
The primary layout component. Inherits from TPanel and overrides the AlignControls method to implement the grid-based layout algorithm.
Key Properties:
ColumnSpecs: string– Comma-separated column specificationsRowSpecs: string– Comma-separated row specificationsControlConstraints: TJgdFormLayoutControlCollection– Design-time mapping of controls to grid cells
Key Methods:
DluToPx(ADlu: Double; AIsHorizontal: Boolean): Integer– Convert Dialog Units to pixelsColSpecsParsed: TColSpecArray– Read-only parsed column specificationsRowSpecsParsed: TRowSpecArray– Read-only parsed row specifications
TJgdFormLayoutControlItem
Represents a single control's placement and alignment constraints within the grid.
Properties:
Control: TControl– Reference to the child controlColumn: Integer– Starting column (1-based)Row: Integer– Starting row (1-based)ColumnSpan: Integer– Number of columns to spanRowSpan: Integer– Number of rows to spanHAlign: TJgdHAlign– Horizontal alignmentVAlign: TJgdVAlign– Vertical alignment
TJgdFormLayoutControlCollection
Collection container for TJgdFormLayoutControlItem instances. Automatically maintained by the layout engine.
Type Definitions
TJgdHAlign = (jgdLeft, jgdRight, jgdCenter, jgdFill, jgdDefault);
TJgdVAlign = (jgdTop, jgdBottom, jgdCenter, jgdFill, jgdDefault);
TJgdSizeKind = (szConstant, szPreferred, szMinimum, szDefault);Usage Guide
Basic Layout Specification
Layout specifications are strings containing comma-separated column or row definitions.
procedure TMyForm.FormCreate(Sender: TObject);
begin
pnlLayout.ColumnSpecs := 'pref, 4dlu, pref, fill:pref:grow';
pnlLayout.RowSpecs := 'pref, 4dlu, pref, 4dlu, pref';
end;This creates:
- 4 columns: Two fixed-width columns (preferred size), a 4-DLU gap, and one growing column
- 5 rows: Three rows with preferred sizing, separated by 4-DLU gaps
Column and Row Specifications
Each specification can include:
- Size Kind (left part before
:if multiple parts):preforp– Preferred size (size of largest child in that column/row)minorm– Minimum size (useful for spacing)dordefault– Default size- A number with unit – Constant size
- Size Value:
pref– Use component's preferred sizemin– Use component's minimum size100px– 100 pixels (explicit pixel value)10dlu– 10 Dialog Units (DPI-independent)75– Number without unit defaults to pixels
- Alignment (optional, before first
:):- For columns:
leftorl,rightorr,centerorc,fillorf - For rows:
toport,bottomorb,centerorc,fillorf
- For columns:
- Growth Weight (optional, after last
:):groworg– Grow weight of 1.0g(2.5)orgrow(2.5)– Custom grow weight
Examples:
// Simple preferred size
'pref'
// Fixed pixel width
'100px'
// Dialog units
'12dlu'
// Left-aligned with preferred size
'left:pref'
// Grow weight
'pref:grow'
// Full specification: right-aligned, 50 pixels, grow weight 0.5
'right:50px:g(0.5)'
// Fill entire space with custom grow weight
'fill:pref:grow(2.0)'Control Placement and Spanning
Add controls to the form and set their layout constraints via the ControlConstraints collection in the Object Inspector:
- Select the
TJgdFormLayoutpanel - In the Object Inspector, locate the
ControlConstraintsproperty - Click the
(...)button to open the collection editor - For each control you want to place:
- Click Add
- Set
Controlto your child control - Set
ColumnandRow(1-based indices) - Set
ColumnSpanandRowSpanif needed - Set alignment options (
HAlign,VAlign)
Or programmatically:
procedure SetupLayout(ALayout: TJgdFormLayout);
var
Item: TJgdFormLayoutControlItem;
begin
ALayout.ColumnSpecs := 'right:pref, 4dlu, fill:100dlu:grow';
ALayout.RowSpecs := 'pref, 4dlu, pref';
// Place a label in column 1, row 1
Item := ALayout.ControlConstraints.Add;
Item.Control := Label1;
Item.Column := 1;
Item.Row := 1;
// Place an edit in column 3, row 1
Item := ALayout.ControlConstraints.Add;
Item.Control := Edit1;
Item.Column := 3;
Item.Row := 1;
Item.HAlign := jgdFill;
end;Alignment Options
Horizontal Alignment (Columns):
jgdLeft– Align control to left edge of celljgdRight– Align control to right edge of celljgdCenter– Center control in celljgdFill– Stretch control to fill cell widthjgdDefault– Use column specification default, orjgdFillif unspecified
Vertical Alignment (Rows):
jgdTop– Align control to top edge of celljgdBottom– Align control to bottom edge of celljgdCenter– Center control vertically in celljgdFill– Stretch control to fill cell heightjgdDefault– Use row specification default, orjgdFillif unspecified
Examples
This repository includes five comprehensive example applications demonstrating different layout scenarios.
Simple Application
Location: ex_simple/
A minimal "Hello World" form showing basic layout setup.
Key File: ex_simple/uSimpleForm.pas
type
TSimpleForm = class(TForm)
pnlMain: TJgdFormLayout;
lblMessage: TLabel;
procedure FormCreate(Sender: TObject);
end;
procedure TSimpleForm.FormCreate(Sender: TObject);
begin
lblMessage.Caption := 'Hello from Simple standard application!';
end;To Run:
cd ex_simple
lazbuild SimpleApp.lpi
./lib/x86_64-*/SimpleAppLogin Form
Location: ex_login/
A classic login dialog demonstrating multi-row layouts, gaps, and button panels.
Layout Structure:
- 3 Columns: Right-aligned labels (col 1), 6-DLU gap (col 2), growing input fields (col 3)
- 7 Rows: Title, gap, username field, gap, password field, gap, button panel
Key Features:
- Uses
right:prefto align labels to the right - Uses
fill:100dlu:growfor responsive input fields - Strategic use of
12dluand6dlugaps for visual hierarchy - Demonstrates button grouping in a panel
File: ex_login/README.md (includes detailed layout specification)
To Run:
cd ex_login
lazbuild LoginForm.lpi
./lib/x86_64-*/LoginFormEmployee Form
Location: ex_employee/
A comprehensive data entry form with multiple sections and advanced layout patterns.
Key Features:
- Complex multi-section layout (personal info, address, employment details)
- Grid-based spacing and alignment
- Demonstrates control spanning
- Shows how to organize large forms hierarchically
Key Files:
ex_employee/uEmployee.pas– Data modelex_employee/uEmployeeForm.pas– Form UI and business logicex_employee/uEmployeeForm.lfm– Form design (extensive layout configuration)
To Run:
cd ex_employee
lazbuild EmployeeApp.lpi
./lib/x86_64-*/EmployeeAppAddress Form
Location: ex_address/
A detailed address entry form showcasing multi-control rows and section grouping.
Key Features:
- Multi-line address fields with proper alignment
- Complex grid layout with various column spans
- Demonstrates responsive field sizing
- Section-based organization with visual separators
Key Files:
ex_address/uAddressForm.pas– Form implementationex_address/uAddressForm.lfm– Extensive layout with address fields
To Run:
cd ex_address
lazbuild AddressApp.lpi
./lib/x86_64-*/AddressAppCredit Card Form
Location: ex_creditcard/
A sophisticated payment form demonstrating advanced layout techniques for sensitive data entry.
Key Features:
- Multi-row card details entry (number, expiry, CVV)
- Aligned input fields with appropriate widths
- Professional spacing and visual hierarchy
- Demonstrates mixed column spans for complex layouts
- Shows best practices for sensitive information presentation
Key Files:
ex_creditcard/uCreditCardForm.pas– Form implementation with validation logicex_creditcard/uCreditCardForm.lfm– Payment form layout design
To Run:
cd ex_creditcard
lazbuild CreditCardApp.lpi
./lib/x86_64-*/CreditCardAppUnit Tests
The project includes a comprehensive automated test suite using FPCUnit.
Location: tests/
Test Files:
tests/test_layout.lpr– Main test program entry pointtests/test_layout.lpi– Lazarus project file
Test Cases
TestColSpecParser
Validates column specification parsing with various format combinations:
procedure TJgdLayoutTests.TestColSpecParser;
var
Layout: TJgdFormLayout;
begin
Layout := TJgdFormLayout.Create(nil);
try
Layout.ColumnSpecs := 'left:pref, 10px, fill:pref:grow, r:50dlu:g(0.5)';
// Validates:
// - Column count parsing
// - Alignment extraction (left, fill, right)
// - Size kind detection (pref, constant)
// - Size unit handling (px, dlu)
// - Growth weight parsing including custom values like g(0.5)
AssertEquals('Should parse 4 column specs', 4, Length(Layout.ColSpecsParsed));
AssertEquals('Col 1 Align', Ord(TJgdHAlign.jgdLeft), ...);
// ... more assertions
finally
Layout.Free;
end;
end;TestRowSpecParser
Similar to TestColSpecParser but for row specifications with vertical alignment options:
Layout.RowSpecs := 'top:pref, 4dlu, bottom:pref:grow, t:100px:g(2.0)';
// Validates:
// - Vertical alignment keywords (top, bottom, center, fill)
// - Row count and spec parsing
// - Growth weight handlingTestDluConversion
Validates the Dialog Unit to pixel conversion function:
procedure TJgdLayoutTests.TestDluConversion;
var
Layout: TJgdFormLayout;
begin
Layout := TJgdFormLayout.Create(nil);
try
Layout.Font.Height := -16; // 16px font height
// Horizontal DLU: char width = font height / 2
// 4 DLU horizontal = 8 pixels
AssertEquals('Horizontal DLU to Px', 8, Layout.DluToPx(4, True));
// Vertical DLU: row height = font height / 8 (approximation)
// 8 DLU vertical = 16 pixels
AssertEquals('Vertical DLU to Px', 16, Layout.DluToPx(8, False));
finally
Layout.Free;
end;
end;Running Tests
Compile and run the test suite:
cd tests
lazbuild test_layout.lpi
./test_layoutExpected output (0 failures, all tests passing):
======= Test Results =======
Test classes run: 1
Tests run: 3
Failures: 0
Errors: 0
OKKey Classes & Types
Type Definitions
// Horizontal alignment options for columns
TJgdHAlign = (jgdLeft, jgdRight, jgdCenter, jgdFill, jgdDefault);
// Vertical alignment options for rows
TJgdVAlign = (jgdTop, jgdBottom, jgdCenter, jgdFill, jgdDefault);
// Size specification kind
TJgdSizeKind = (szConstant, szPreferred, szMinimum, szDefault);
// Parsed column specification record
TJgdColSpec = record
Align: TJgdHAlign; // Horizontal alignment
SizeKind: TJgdSizeKind; // How to calculate size
SizeValue: Double; // Size value (if constant)
SizeUnit: string; // Unit: 'px' or 'dlu'
GrowWeight: Double; // Growth weight for surplus distribution
end;
// Parsed row specification record (similar structure, vertical alignment)
TJgdRowSpec = record
Align: TJgdVAlign;
SizeKind: TJgdSizeKind;
SizeValue: Double;
SizeUnit: string;
GrowWeight: Double;
end;Class Hierarchy
TComponent
?????? TControl
?????? TWinControl
?????? TPanel
?????? TJgdFormLayout
?????? Properties: ColumnSpecs, RowSpecs, ControlConstraints
?????? Methods: DluToPx(), AlignControls() (overridden)
?????? Collections
?????? TJgdFormLayoutControlCollection
?????? TJgdFormLayoutControlItem[]Design Principles
1. Separation of Concerns
- ujgdformslayout.pas – Core layout engine and data structures
- ujgdformslayoutregister.pas – Component registration (design-time integration)
- jgd_forms_layout.lpk – Package configuration
2. Memory Efficiency
- Uses
recordtypes (TJgdColSpec,TJgdRowSpec) instead of classes for parsed specifications to optimize cache locality - Parsed specs are cached in arrays, not recreated on every layout pass
3. Infinite Loop Prevention
- The
FIsAligningflag prevents recursive collection updates during the alignment algorithm - Child control automatic registration is skipped during component loading to avoid redundant work
4. DPI Independence
- Dialog Units (DLU) are calculated relative to the current font, not absolute pixel values
- This allows layouts to scale automatically with system font settings
5. Declarative Configuration
- Users specify layouts as strings, not through complex property dialogs
- String specs are parsed and cached, minimizing performance impact
Troubleshooting
Issue: Controls Not Appearing
Solution: Ensure you've added all child controls to the ControlConstraints collection and that their column/row values are within the bounds of your grid specification.
Issue: Layout Not Updating After Property Change
Solution: The layout automatically updates when you change ColumnSpecs or RowSpecs. If it doesn't, try calling Realign() or Invalidate() on the panel.
Issue: DPI-Dependent Sizing
Cause: Using pixel-based specifications (100px) instead of Dialog Units.
Solution: Use dlu units for DPI-independent sizing:
// Avoid this for cross-platform layouts:
'pref, 10px, pref'
// Use this instead:
'pref, 10dlu, pref'Issue: Package Won't Install
Solution:
- Ensure you have the latest Lazarus IDE installed
- Go to Tools → Options → Environment → Lazarus Directory and verify the path is correct
- Try Package → Clean Up before reinstalling
Issue: Package Compiles but Lazarus IDE Rebuild Fails on macOS
Cause: The package build succeeded, but the Lazarus IDE failed during the
final Cocoa link step. On some recent macOS/Xcode setups, Apple's default linker
rejects a Lazarus Cocoa object file such as cocoawsextctrls.o and reports
malformed method list atom.
Solution: Rebuild the IDE with the classic linker option:
lazbuild --build-ide=-k-ld_classicFor your own Cocoa LCL applications, either pass the same option to the project build:
lazbuild --opt=-k-ld_classic path/to/project.lpiOr add a small FPC response file to the project and reference it from Project Options > Compiler Options > Custom Options:
#ifdef darwin
-k-ld_classic
#endifIssue: Test Suite Compilation Fails
Cause: Missing FPCUnit framework or incorrect path configuration.
Solution: In Lazarus, go to Project → Project Options → Compiler Options and ensure the unit search paths include the FPC source directory containing the fpcunit unit.
License
This project is licensed under the MIT License. See the LICENSE file in the repository for details.
Contributing
Contributions are welcome! Feel free to:
- Report issues and feature requests
- Submit pull requests with improvements
- Improve documentation and examples
- Add additional test cases
References
- JGoodies Forms Layout (Java): https://www.jgoodies.com/freeware/libraries/forms/
- Lazarus IDE: https://www.lazarus-ide.org/
- Free Pascal Compiler: https://www.freepascal.org/
- Dialog Units Documentation: Microsoft Windows documentation on dialog units for DPI-independent UI sizing
Author
Project: jedt3d
Repository: https://github.com/jedt3d/lazarus-jgd-form-layout
Last Updated: June 2026

