YOUSEFUL / DELPHI / SAMPLE PROJECTS
Tree and editors
A console program that creates the Atlas VCL data controls on a form that is never shown and verifies what each one does:
TAtlasTree: builds a small hierarchy withAddNode, sorts children withSortChildren, finds a node withFindCaption(case-insensitive), moves a node to the root withMoveNodeand shows that moving a node under its own child raisesEAtlasTreeError. A lazy node gets its children fromOnLoadChildrenon the first expand only.ToJSON/LoadJSONcopy the tree to a second tree, and a JSON document without the required fields is rejected without changing it.TAtlasSheetGrid: shows aTAtlasWorkbookwith aSUMformula, follows a change to a referenced cell, applies a fixed-decimal format and detaches when the workbook is freed.TAtlasInspector: lists the published properties of a small settings component, assigns integer, floating-point, enumeration and Boolean values withSetProperty, rejects an invalid integer and a read-only property withEAtlasPropertyError, and detaches when the component is freed.TAtlasRichEdit: formats a selection, saves it withSaveRTF, loads the bytes into a second editor withLoadRTF(text and bold style survive), finds two matches withFindNextand replaces them withReplaceAll.TAtlasValueEdit: validates numbers and dates, shows that a decimal comma is not a number, and thatRequiredturns a blank value into an error.
Controls that wrap a Windows control need a window handle, so each one is given
the hidden form as Parent before it is used. A mismatch raises an exception,
the program prints FAIL: ... and exits with code 1.
Build and run
Open TreeAndEditors.dpr in Delphi 13; the IDE creates the project file. Add the Atlas VCL src folder to the project's search path, then build and run for Win32 or Win64. No packages need to be installed. The program needs no input. It checks its own results and stops with an error if any of them differs from the output below.
Expected output
TAtlasTree
ok: children sorted by caption = Data platform
ok: case-insensitive caption search
ok: node moved to the root level
ok: moving a node under its own child is rejected
ok: a lazy node shows an expand button before loading
ok: OnLoadChildren ran once on first expand
ok: children are not loaded twice
ok: JSON round trip keeps captions, expansion and lazy flags = [{"caption":"Engineering",...}]
ok: a rejected import leaves the tree unchanged
TAtlasSheetGrid
ok: column headers use letters = A
ok: B1 shows the calculated value = 42
ok: the grid follows workbook changes = 20
ok: cell formats apply to the displayed text = 20.0
ok: the grid detaches when its workbook is freed
TAtlasInspector
ok: published properties are listed with their values = 3
ok: SetProperty converts and assigns typed values
ok: an invalid value is rejected before assignment
ok: a read-only property is rejected
ok: destroying the component detaches the inspector
TAtlasRichEdit
ok: selection formatting applied
ok: SaveRTF returns RTF bytes
ok: LoadRTF restores the text = Atlas notes: draft one, draft two.
ok: LoadRTF restores the formatting
ok: FindNext selects the first match after the caret
ok: FindNext continues from the selection
ok: ReplaceAll reports two replacements
ok: text after ReplaceAll = Atlas notes: revision one, revision two.
TAtlasValueEdit
ok: a number is accepted
ok: NormalizedValue is the stored form = 12.5
ok: a locale decimal comma is rejected with a message
ok: blank is valid when not required
ok: blank is invalid when required
ok: 2026-02-29 is not a real date
ok: 2028-02-29 is a leap day
PASS: TreeAndEditorsThe tree JSON line shows the format ToJSON writes: an array of node objects,
each with caption, expanded, lazy and children. The Archive node was
expanded, so it is saved as expanded with its loaded child and lazy false; the
never-expanded Later node keeps lazy true.
What to change first
- Put the controls on a visible form (
Align := alClienton a page each) to see them; the calls stay the same. - Replace
TTaskSettingswith one of your own components. The inspector lists published string, character, integer, 64-bit integer, floating-point and enumeration properties (Booleans are enumerations); sets, classes and events are not shown. - In
TLazyLoader.LoadChildren, query your own data source for the children ofNode. If the handler raises, the tree removes the children it added and the node stays lazy, so the next expand tries again.
Project files
The ZIP contains every sample project with its README. See getting started for the search path to add.
Source
TreeAndEditors.dpr
program TreeAndEditors;
// Atlas VCL controls exercised on a hidden form: TAtlasTree hierarchy, lazy
// loading and JSON; TAtlasSheetGrid over a TAtlasWorkbook; TAtlasInspector
// editing published properties; TAtlasRichEdit formatting, RTF, find and
// replace; TAtlasValueEdit validation. Every result is verified and the
// program exits with 0 on success. Nothing is shown on screen.
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.Classes, Vcl.Forms, Vcl.Controls, Vcl.ComCtrls,
Vcl.Graphics, Atlas.Data, Atlas.Editors, Atlas.Tree, Atlas.Spreadsheet,
Atlas.SheetGrid, Atlas.Inspector, Atlas.RichEdit;
type
TLazyLoader = class
Calls: Integer;
procedure LoadChildren(Sender: TObject; Node: TTreeNode);
end;
TPriority = (prLow, prNormal, prHigh);
TTaskSettings = class(TComponent)
private
FTitle: string;
FRetries: Integer;
FRatio: Double;
FPriority: TPriority;
FEnabled: Boolean;
published
property Title: string read FTitle write FTitle;
property Retries: Integer read FRetries write FRetries;
property Ratio: Double read FRatio write FRatio;
property Priority: TPriority read FPriority write FPriority;
property Enabled: Boolean read FEnabled write FEnabled;
property Version: Integer read FRetries;
end;
procedure TLazyLoader.LoadChildren(Sender: TObject; Node: TTreeNode);
begin
Inc(Calls);
TAtlasTree(Sender).AddNode(Node, 'Loaded on expand');
end;
procedure Expect(Condition: Boolean; const What: string);
begin
if not Condition then
raise Exception.Create('Check failed: ' + What);
Writeln(' ok: ', What);
end;
procedure ExpectText(const Expected, Actual, What: string);
begin
if Expected <> Actual then
raise Exception.CreateFmt('%s: expected [%s], got [%s]', [What, Expected, Actual]);
Writeln(' ok: ', What, ' = ', Actual);
end;
procedure TreeSection(Form: TForm);
var
Tree, Copy: TAtlasTree;
Loader: TLazyLoader;
Root, Lazy, Node: TTreeNode;
Failed: Boolean;
begin
Writeln('TAtlasTree');
Loader := TLazyLoader.Create;
try
Tree := TAtlasTree.Create(Form);
Tree.Parent := Form; // nodes need a window handle
Copy := TAtlasTree.Create(Form);
Copy.Parent := Form;
Root := Tree.AddNode(nil, 'Engineering');
Tree.AddNode(Root, 'Quality');
Tree.AddNode(Root, 'Desktop');
Node := Tree.AddNode(Root, 'Data platform');
Tree.SortChildren(Root);
ExpectText('Data platform', Root.Item[0].Text, 'children sorted by caption');
Expect(Tree.FindCaption('desktop') = Root.Item[1], 'case-insensitive caption search');
Tree.MoveNode(Node, nil);
Expect((Node.Parent = nil) and (Root.Count = 2), 'node moved to the root level');
Failed := False;
try
Tree.MoveNode(Root, Root.Item[0]);
except
on E: EAtlasTreeError do
Failed := True;
end;
Expect(Failed, 'moving a node under its own child is rejected');
Tree.OnLoadChildren := Loader.LoadChildren;
Lazy := Tree.AddNode(nil, 'Archive', True);
Expect(Lazy.HasChildren and (Lazy.Count = 0), 'a lazy node shows an expand button before loading');
Lazy.Expand(False);
Expect((Loader.Calls = 1) and (Lazy.Count = 1), 'OnLoadChildren ran once on first expand');
Lazy.Collapse(False);
Lazy.Expand(False);
Expect(Loader.Calls = 1, 'children are not loaded twice');
Tree.AddNode(nil, 'Later', True);
Copy.LoadJSON(Tree.ToJSON);
ExpectText(Tree.ToJSON, Copy.ToJSON, 'JSON round trip keeps captions, expansion and lazy flags');
Failed := False;
try
Copy.LoadJSON('[{"caption":"no other fields"}]');
except
on E: EAtlasTreeError do
Failed := True;
end;
Expect(Failed and (Copy.ToJSON = Tree.ToJSON), 'a rejected import leaves the tree unchanged');
finally
Loader.Free;
end;
end;
procedure SheetSection(Form: TForm);
var
Workbook: TAtlasWorkbook;
Sheet: TAtlasSheetGrid;
Format: TAtlasCellFormat;
begin
Writeln('TAtlasSheetGrid');
Workbook := TAtlasWorkbook.Create(Form);
Workbook.SetCell('A1', '12');
Workbook.SetCell('A2', '30');
Workbook.SetCell('B1', '=SUM(A1:A2)');
Sheet := TAtlasSheetGrid.Create(Form);
Sheet.Parent := Form;
Sheet.Workbook := Workbook;
ExpectText('A', Sheet.Cells[1, 0], 'column headers use letters');
ExpectText('42', Sheet.Cells[2, 1], 'B1 shows the calculated value');
Workbook.SetCell('A2', '8');
ExpectText('20', Sheet.Cells[2, 1], 'the grid follows workbook changes');
Format := TAtlasCellFormat.Default;
Format.NumberFormat := anfFixed;
Format.Decimals := 1;
Workbook.SetCellFormat('B1', Format);
ExpectText('20.0', Sheet.Cells[2, 1], 'cell formats apply to the displayed text');
Workbook.Free;
Expect(Sheet.Workbook = nil, 'the grid detaches when its workbook is freed');
end;
procedure InspectorSection(Form: TForm);
var
Settings: TTaskSettings;
Inspector: TAtlasInspector;
Failed: Boolean;
begin
Writeln('TAtlasInspector');
Settings := TTaskSettings.Create(nil);
try
Settings.Title := 'Nightly build';
Settings.Retries := 3;
Inspector := TAtlasInspector.Create(Form);
Inspector.Parent := Form;
Inspector.Instance := Settings;
ExpectText('3', Inspector.Strings.Values['Retries'], 'published properties are listed with their values');
Inspector.SetProperty('Retries', '5');
Inspector.SetProperty('Ratio', '0.75');
Inspector.SetProperty('Priority', 'prHigh');
Inspector.SetProperty('Enabled', 'True');
Expect((Settings.Retries = 5) and (Settings.Ratio = 0.75) and
(Settings.Priority = prHigh) and Settings.Enabled, 'SetProperty converts and assigns typed values');
Failed := False;
try
Inspector.SetProperty('Retries', 'five');
except
on E: EAtlasPropertyError do
Failed := True;
end;
Expect(Failed and (Settings.Retries = 5), 'an invalid value is rejected before assignment');
Failed := False;
try
Inspector.SetProperty('Version', '9');
except
on E: EAtlasPropertyError do
Failed := True;
end;
Expect(Failed, 'a read-only property is rejected');
Settings.Free;
Settings := nil;
Expect(Inspector.Instance = nil, 'destroying the component detaches the inspector');
finally
Settings.Free;
end;
end;
procedure RichEditSection(Form: TForm);
var
Editor, Copy: TAtlasRichEdit;
Rtf: TBytes;
begin
Writeln('TAtlasRichEdit');
Editor := TAtlasRichEdit.Create(Form);
Editor.Parent := Form; // the control needs a window before streaming RTF
Copy := TAtlasRichEdit.Create(Form);
Copy.Parent := Form;
Editor.Text := 'Atlas notes: draft one, draft two.';
Editor.SelStart := 0;
Editor.SelLength := 11;
Editor.FormatSelection('Segoe UI', 14, [fsBold], clNavy);
Expect(fsBold in Editor.SelAttributes.Style, 'selection formatting applied');
Rtf := Editor.SaveRTF;
Expect(TEncoding.ASCII.GetString(Rtf, 0, 5) = '{\rtf', 'SaveRTF returns RTF bytes');
Copy.LoadRTF(Rtf);
ExpectText(Editor.Text, Copy.Text, 'LoadRTF restores the text');
Copy.SelStart := 0;
Copy.SelLength := 11;
Expect(fsBold in Copy.SelAttributes.Style, 'LoadRTF restores the formatting');
Editor.SelStart := 0;
Editor.SelLength := 0;
Expect(Editor.FindNext('draft') and (Editor.SelStart = 13), 'FindNext selects the first match after the caret');
Expect(Editor.FindNext('draft') and (Editor.SelStart = 24), 'FindNext continues from the selection');
Expect(Editor.ReplaceAll('draft', 'revision') = 2, 'ReplaceAll reports two replacements');
ExpectText('Atlas notes: revision one, revision two.', Editor.Text, 'text after ReplaceAll');
end;
procedure ValueEditSection;
var
Edit: TAtlasValueEdit;
begin
Writeln('TAtlasValueEdit');
Edit := TAtlasValueEdit.Create(nil);
try
Edit.ValueKind := ackNumber;
Edit.Text := '12.50';
Expect(Edit.Validate, 'a number is accepted');
ExpectText('12.5', Edit.NormalizedValue, 'NormalizedValue is the stored form');
Edit.Text := '1,5';
Expect(not Edit.Validate and (Edit.ValidationMessage <> ''), 'a locale decimal comma is rejected with a message');
Edit.Text := '';
Expect(Edit.Validate, 'blank is valid when not required');
Edit.Required := True;
Expect(not Edit.Validate, 'blank is invalid when required');
Edit.ValueKind := ackDate;
Edit.Text := '2026-02-29';
Expect(not Edit.Validate, '2026-02-29 is not a real date');
Edit.Text := '2028-02-29';
Expect(Edit.Validate, '2028-02-29 is a leap day');
finally
Edit.Free;
end;
end;
var
Form: TForm;
begin
try
Application.Initialize;
Form := TForm.CreateNew(nil);
try
TreeSection(Form);
SheetSection(Form);
InspectorSection(Form);
RichEditSection(Form);
ValueEditSection;
finally
Form.Free;
end;
Writeln('PASS: TreeAndEditors');
except
on E: Exception do
begin
Writeln('FAIL: ', E.ClassName, ': ', E.Message);
Halt(1);
end;
end;
end.