YOUSEFUL / DELPHI / SAMPLE PROJECTS

Grid quick start

VCL application Product: Atlas VCL · Components: TAtlasTable, TAtlasGrid, TAtlasValueEdit

The tutorial project for Atlas VCL. One form, built in code, shows a typed TAtlasTable in a TAtlasGrid:

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:

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:

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

Project files

Download all 34 projects

The ZIP contains every sample project with its README. See getting started for the search path to add.

Source

GridQuickStart.dpr

pascal
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.