YOUSEFUL / DELPHI / SAMPLE PROJECTS
Grid quick start
The tutorial project for Atlas VCL. One form, built in code, shows a typed
TAtlasTable in a TAtlasGrid:
- five columns of four kinds (
ackText,ackNumber,ackBoolean,ackDate), loaded in one step withImportCSV, including a quoted field that contains a comma; - a filter box that calls
SetFilteron every keystroke, across all columns and without regard to case; - sorting by clicking a column header (the grid does this itself: first click
ascending, next click descending) and a Clear sort button that calls
Sort(-1); - in-place editing: type in a cell and press Enter or move to another cell to store the value, or press Escape to discard it; the table rejects values that do not fit the column kind. A small form-level key handler makes Enter and Escape act at once (see below);
- a row of entry boxes where the quantity, price and restock date are
TAtlasValueEditcontrols: each checks its value when focus leaves it, and Add row checks them again withValidatebeforeAddRow; - Delete row for the selected row and Export CSV for the visible rows;
- a status line driven by
OnChangethat shows the visible and total row counts and the current sort.
Build and run
Open GridQuickStart.dpr in Delphi 13; the IDE creates the project file. Add the Atlas VCL src folder and the shared folder to the project's search path, then build and run for Win32 or Win64. No packages need to be installed. The shared folder is part of the sample download. It holds Atlas.TestVcl.pas, which the optional snapshot mode uses to save a picture of the window.
Start it without arguments to use the window.
GridQuickStart.exe window.png does not show the window; it paints the form off screen and saves it as a PNG through SaveFormImage from shared/Atlas.TestVcl.pas. Remove that unit and the if ParamCount > 0 branch if you copy the program elsewhere.
Expected result
A 900 × 560 window with the filter row and buttons at the top, the entry row below it and a grid with a dark header row and alternating row tint:
| Product | Quantity | Unit price | Available | Restock |
|---|---|---|---|---|
| Field notebook | 120 | 4.75 | true | 2026-10-03 |
| Drafting pencil | 300 | 1.2 | true | |
| Steel ruler | 45 | 6.5 | false | 2026-11-15 |
| Ink, archival | 80 | 12 | true | 2026-10-20 |
The status line reads 4 of 4 rows shown, unsorted. .... Things to try:
- Type
inkin the filter box: one row remains and the status shows1 of 4 rows. - Click the Quantity header twice: the header shows
^and thenv, and the rows are ordered numerically (45 before 120), not as text. - Type
12.50into a Unit price cell and press Enter: the editor stays open and shows12.5, the normalized number the table stores. - Type
lotsinto a Quantity cell and press Enter: the application reportsInvalid finite number: lots, the table keeps the old value and the editor keeps your text. Correct it, or press Escape to get the old value back. - Type a new quantity, press Escape, then press the Down arrow: the old value stays.
- Type a product name and click Add row with the quantity box empty: the
status line says
Quantity: A value is requiredand nothing is added. - Type
2026-02-30into the restock box and press Tab:TAtlasValueEditchecks its value when focus leaves and the application reportsExpected a valid date in YYYY-MM-DD format. - Click Export CSV:
inventory.csvis written next to the executable with the rows currently shown, in the current sort order.
The Enter and Escape handler
FormKeyDown (with KeyPreview := True) works around two limitations of the
grid's in-place editing in this baseline, both verified against the source:
- The first Enter only closes the editor. The value reaches the table at the next
Enter, cell move, header click or focus change, and until then the cell shows
its old value. The handler sets
EditorMode := False(which hands the editor text to the grid) and callsCommitEditat once. - Escape while the editor is open closes it, but
CancelEditclosing the editor hands the typed text back to the grid as a pending edit, and the next cell move stores it. A secondCancelEdit, with the editor already closed, discards it. The handler callsCancelEdittwice.
Without the handler the grid still stores edits when you move to another cell with the arrow keys, Tab or the mouse, or leave the grid; only Enter and Escape behave as described above.
What to change first
- Replace the columns in the constructor with your own schema. Call every
AddColumnbefore the first row is added; the table refuses schema changes once it holds data. - Replace the
ImportCSVtext with your own data, or callAddRowin a loop. The CSV header must name the columns exactly, in order. - Keep a business key (an order number, a product code) in its own column and
pass
Falseas the lastAddColumnargument to make it read-only. View row indices change with every sort and filter, so they are not identifiers. - Call
FGrid.CommitEditbefore any code of yours that sorts, filters or changes the table, as the handlers here do. A model change made while the editor is still open does not discard the typed text: the grid keeps it and stores it into the row that is under the cursor after the change, which may be a different record.
Project files
- GridQuickStart.dpr
- README.md
- shared/Atlas.TestVcl.pas snapshot helper, in the download
The ZIP contains every sample project with its README. See getting started for the search path to add.
Source
GridQuickStart.dpr
program GridQuickStart;
// Atlas VCL quick start: a typed TAtlasTable shown in a TAtlasGrid, with a
// filter box, header sorting, in-place editing, validated entry through
// TAtlasValueEdit, row deletion and CSV export. The form is built in code; no
// .dfm is needed.
//
// Run with a file name argument to save a PNG of the window instead of
// showing it: GridQuickStart.exe window.png
uses
Winapi.Windows, System.SysUtils, System.Classes, System.IOUtils, Vcl.Forms, Vcl.Controls,
Vcl.StdCtrls, Vcl.ExtCtrls,
Atlas.Data, Atlas.Grid, Atlas.Editors, Atlas.TestVcl;
type
TMainForm = class(TForm)
private
FTable: TAtlasTable;
FGrid: TAtlasGrid;
FFilter: TEdit;
FProduct: TEdit;
FQuantity: TAtlasValueEdit;
FPrice: TAtlasValueEdit;
FRestock: TAtlasValueEdit;
FAvailable: TCheckBox;
FStatus: TLabel;
procedure AddLabel(Parent: TWinControl; Left, Top: Integer; const Caption: string);
procedure AddButton(Parent: TWinControl; Left, Top: Integer;
const Caption: string; OnClick: TNotifyEvent);
function AddValueEdit(Parent: TWinControl; Left, Width: Integer;
Kind: TAtlasColumnKind; Required: Boolean): TAtlasValueEdit;
function Invalid(Edit: TAtlasValueEdit; const Name: string): Boolean;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FilterChanged(Sender: TObject);
procedure AddClicked(Sender: TObject);
procedure DeleteClicked(Sender: TObject);
procedure ExportClicked(Sender: TObject);
procedure ClearSortClicked(Sender: TObject);
procedure TableChanged(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
end;
constructor TMainForm.Create(AOwner: TComponent);
var
Toolbar, Footer: TPanel;
begin
inherited CreateNew(AOwner);
Caption := 'Atlas VCL: grid quick start';
Width := 900;
Height := 560;
Position := poScreenCenter;
KeyPreview := True; // FormKeyDown sees Enter and Escape before the grid editor
OnKeyDown := FormKeyDown;
Toolbar := TPanel.Create(Self);
Toolbar.Parent := Self;
Toolbar.Align := alTop;
Toolbar.Height := 108;
Toolbar.BevelOuter := bvNone;
AddLabel(Toolbar, 16, 17, 'Filter');
FFilter := TEdit.Create(Self);
FFilter.Parent := Toolbar;
FFilter.SetBounds(56, 14, 260, 24);
FFilter.OnChange := FilterChanged;
AddButton(Toolbar, 332, 11, 'Clear sort', ClearSortClicked);
AddButton(Toolbar, 448, 11, 'Delete row', DeleteClicked);
AddButton(Toolbar, 564, 11, 'Export CSV', ExportClicked);
AddLabel(Toolbar, 16, 50, 'Product');
AddLabel(Toolbar, 232, 50, 'Quantity');
AddLabel(Toolbar, 338, 50, 'Unit price');
AddLabel(Toolbar, 444, 50, 'Restock (YYYY-MM-DD)');
FProduct := TEdit.Create(Self);
FProduct.Parent := Toolbar;
FProduct.SetBounds(16, 68, 200, 24);
FQuantity := AddValueEdit(Toolbar, 232, 90, ackNumber, True);
FPrice := AddValueEdit(Toolbar, 338, 90, ackNumber, True);
FRestock := AddValueEdit(Toolbar, 444, 110, ackDate, False);
FAvailable := TCheckBox.Create(Self);
FAvailable.Parent := Toolbar;
FAvailable.SetBounds(570, 70, 90, 20);
FAvailable.Caption := 'Available';
FAvailable.Checked := True;
AddButton(Toolbar, 676, 65, 'Add row', AddClicked);
Footer := TPanel.Create(Self);
Footer.Parent := Self;
Footer.Align := alBottom;
Footer.Height := 32;
Footer.BevelOuter := bvNone;
FStatus := TLabel.Create(Self);
FStatus.Parent := Footer;
FStatus.SetBounds(16, 8, 860, 20);
// The schema is fixed before the first row is added.
FTable := TAtlasTable.Create(Self);
FTable.AddColumn('Product', ackText, 260);
FTable.AddColumn('Quantity', ackNumber, 110);
FTable.AddColumn('Unit price', ackNumber, 110);
FTable.AddColumn('Available', ackBoolean, 100);
FTable.AddColumn('Restock', ackDate, 120);
FTable.OnChange := TableChanged;
FGrid := TAtlasGrid.Create(Self);
FGrid.Parent := Self;
FGrid.Align := alClient;
FGrid.AlignWithMargins := True;
FGrid.Margins.SetBounds(16, 8, 16, 8);
FGrid.Table := FTable;
FTable.ImportCSV(
'Product,Quantity,Unit price,Available,Restock' + sLineBreak +
'Field notebook,120,4.75,true,2026-10-03' + sLineBreak +
'Drafting pencil,300,1.2,true,' + sLineBreak +
'Steel ruler,45,6.5,false,2026-11-15' + sLineBreak +
'"Ink, archival",80,12,true,2026-10-20' + sLineBreak);
end;
procedure TMainForm.AddLabel(Parent: TWinControl; Left, Top: Integer; const Caption: string);
var
Text: TLabel;
begin
Text := TLabel.Create(Self);
Text.Parent := Parent;
Text.SetBounds(Left, Top, 180, 16);
Text.Caption := Caption;
end;
procedure TMainForm.AddButton(Parent: TWinControl; Left, Top: Integer;
const Caption: string; OnClick: TNotifyEvent);
var
Button: TButton;
begin
Button := TButton.Create(Self);
Button.Parent := Parent;
Button.SetBounds(Left, Top, 100, 30);
Button.Caption := Caption;
Button.OnClick := OnClick;
end;
function TMainForm.AddValueEdit(Parent: TWinControl; Left, Width: Integer;
Kind: TAtlasColumnKind; Required: Boolean): TAtlasValueEdit;
begin
Result := TAtlasValueEdit.Create(Self);
Result.Parent := Parent;
Result.SetBounds(Left, 68, Width, 24);
Result.ValueKind := Kind;
Result.Required := Required;
end;
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
// In this baseline the grid's first Enter only closes the in-place editor,
// and Escape closes it without discarding the text. Make both keys act at once.
if (ActiveControl <> FGrid) or not FGrid.EditorMode then
Exit;
if Key = VK_RETURN then
begin
FGrid.EditorMode := False; // hand the editor text to the grid
FGrid.CommitEdit; // validate and store it, or raise EAtlasDataError
end
else if Key = VK_ESCAPE then
begin
FGrid.CancelEdit; // closing the editor keeps its text pending...
FGrid.CancelEdit; // ...so a second call discards it
Key := 0;
end;
end;
procedure TMainForm.FilterChanged(Sender: TObject);
begin
FGrid.CommitEdit; // a pending edit must land before the view changes
FTable.SetFilter(FFilter.Text);
end;
procedure TMainForm.ClearSortClicked(Sender: TObject);
begin
FGrid.CommitEdit;
FTable.Sort(-1);
end;
function TMainForm.Invalid(Edit: TAtlasValueEdit; const Name: string): Boolean;
begin
Result := not Edit.Validate;
if Result then
FStatus.Caption := Name + ': ' + Edit.ValidationMessage;
end;
procedure TMainForm.AddClicked(Sender: TObject);
const
Flags: array[Boolean] of string = ('false', 'true');
begin
if Trim(FProduct.Text) = '' then
begin
FStatus.Caption := 'Product: a name is required';
Exit;
end;
if Invalid(FQuantity, 'Quantity') or Invalid(FPrice, 'Unit price') or
Invalid(FRestock, 'Restock') then
Exit;
FGrid.CommitEdit;
FTable.AddRow([Trim(FProduct.Text), FQuantity.NormalizedValue, FPrice.NormalizedValue,
Flags[FAvailable.Checked], FRestock.NormalizedValue]);
FProduct.Clear;
FQuantity.Clear;
FPrice.Clear;
FRestock.Clear;
end;
procedure TMainForm.DeleteClicked(Sender: TObject);
begin
// Grid row 0 is the header; table rows are zero-based view indices.
if (FGrid.Row >= 1) and (FGrid.Row <= FTable.RowCount) then
begin
FGrid.CancelEdit;
FTable.DeleteRow(FGrid.Row - 1);
end;
end;
procedure TMainForm.ExportClicked(Sender: TObject);
var
FileName: string;
begin
FGrid.CommitEdit;
FileName := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), 'inventory.csv');
TFile.WriteAllText(FileName, FTable.ExportCSV, TEncoding.UTF8);
FStatus.Caption := 'Wrote the visible rows to ' + FileName;
end;
procedure TMainForm.TableChanged(Sender: TObject);
var
SortText: string;
begin
if FTable.SortColumn < 0 then
SortText := 'unsorted'
else if FTable.Ascending then
SortText := 'sorted by ' + FTable.Columns[FTable.SortColumn].Name + ' ascending'
else
SortText := 'sorted by ' + FTable.Columns[FTable.SortColumn].Name + ' descending';
FStatus.Caption := Format('%d of %d rows shown, %s. Click a header to sort; ' +
'Enter stores an edit, Escape discards it.',
[FTable.RowCount, FTable.SourceRowCount, SortText]);
end;
var
Form: TMainForm;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, Form);
if ParamCount > 0 then
SaveFormImage(Form, ParamStr(1))
else
Application.Run;
end.