YOUSEFUL / DELPHI / SAMPLE PROJECTS
Virtual rows and a chart companion
A VCL window with two pages, built entirely in code (TForm.CreateNew, no
.dfm). It goes further than the quick start.
One million on-demand rows. A TAtlasVirtualData with RowCount = 1000000
computes every cell in OnGetCell. Nothing is stored except the cells the user
edits, which OnSetCell keeps in a dictionary. The page shows:
- three column bands (Source, Measurement, Review) and two frozen columns (Row, Sensor) that stay put while the wide Note column scrolls sideways;
- read-only columns set on the grid (
Columns[i].ReadOnly), because an on-demand source with anOnSetCellhandler reports every column as writable; - in-place editing with validation.
OnSetCellraisesEAtlasGridErrorfor a reading outside -50..150, a blank reading or a note longer than 40 characters. The grid keeps the editor open, leaves the data unchanged and reports the reason throughOnEditError, which this sample shows in the status line. Text that does not parse as a number is refused beforeOnSetCellis called; - conditional colours from
OnCellStyle: edited cells amber, readings above 100 light red, negative readings in blue text; - a Filter button that times
FilterTextover the million rows, and a Last row button that callsSelectCellon row 999,999.
Chart companion. A TAtlasPlot with two bar series, Plan and Actual, is
linked to a TAtlasPlotData. The grid below the chart shows one row per quarter
and one column per series, with sum totals. Editing a value moves the bar.
Clicking a bar selects its cell (RowOfPoint), and moving in the table shows
the chart argument of the row (ArgumentOfRow). The Quarter column is
read-only, and a blank value is refused with "A series value must be a number".
Build and run
Open VirtualAndPlotGrid.dpr in Delphi 13; the IDE creates the project file. Add the Atlas Virtual Grid src folder and the Atlas Plot src folder to the project's search path, then build and run for Win32 or Win64. No packages need to be installed.
What you should see
- The first page opens on the first twenty or so rows: Sensor A to H repeating, readings with two decimals (row 1 reads 50.00), timestamps one minute apart from 2026-01-01 00:00, and every fifth row checked.
- Click row 4's Reading, type
200and press Enter. The status line reads "Not accepted: A reading must lie between -50 and 150" and the editor stays open. Type142.5and press Enter: the value is stored, the cell turns amber once the cursor leaves it, and the status line counts the edited cells. - Double-click a Checked box, or select it and press F2 or Enter: it flips at once, with no editor. A single click only selects it.
- Type
Sensor Cin the box and press Filter: 125,000 of 1,000,000 rows match. In two Win32 runs while this sample was written, a filter over the million rows took 1.6 to 1.7 s and a descending sort on Reading 1.0 to 1.4 s. Unfiltered and unsorted, scrolling asks only for the cells on screen. - Last row jumps to row 1,000,000, timestamp 2027-11-26 10:39.
- On the second page, change Q2's Actual to
150and press Enter: the orange Q2 bar rises to 150 and the Actual total becomes 530.0.
Adapting it
- Replace
GetReadingwith a lookup into your own storage (a file, a cache, a service) andSetReadingwith a write back to it. RaiseEAtlasGridErrorto refuse a value; other exception classes are not caught by the grid. - Call
FReadings.Refreshwhen the values behindOnGetCellchange, and setRowCountwhen the number of rows changes. - Sorting, filtering and totals read every row once. On a large source, apply a filter on a button or after a pause in typing rather than on every key.
Project files
The ZIP contains every sample project with its README. See getting started for the search path to add.
Source
VirtualAndPlotGrid.dpr
program VirtualAndPlotGrid;
// Atlas Virtual Grid, deeper sample: a million on-demand rows with bands, frozen
// columns, validated in-place editing and conditional colours, and a chart shown
// and edited as a table. The form is built in code; see VirtualPlotForm.pas.
uses
Vcl.Forms,
VirtualPlotForm in 'VirtualPlotForm.pas';
var
MainForm: TVirtualPlotForm;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TVirtualPlotForm, MainForm);
Application.Run;
end.
VirtualPlotForm.pas
unit VirtualPlotForm;
// Two pages. "One million rows": a TAtlasVirtualData that computes its cells on
// demand and stores only the cells the user edits, shown with bands, two frozen
// columns, validated editing and conditional colours. "Chart companion": a
// TAtlasPlot shown and edited as a table through TAtlasPlotData.
interface
uses
System.SysUtils, System.Classes, System.Math, System.Diagnostics, System.UITypes,
System.Generics.Collections, Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls,
Vcl.ComCtrls, Vcl.Graphics, Atlas.Plot, Atlas.PlotSeries, Atlas.VGridData,
Atlas.VGridPlot, Atlas.VGrid;
const
RowTotal = 1000000;
// Data columns of the on-demand source.
ColRow = 0; ColSensor = 1; ColReading = 2; ColTaken = 3; ColChecked = 4; ColNote = 5;
type
TVirtualPlotForm = class(TForm)
private
FPages: TPageControl;
FStatus: TLabel;
FFilter: TEdit;
FReadings: TAtlasVirtualData;
// Only the cells the user changed are stored; everything else is computed.
FEdits: TDictionary<Int64, TAtlasCell>;
FGrid: TAtlasVirtualGrid;
FChart: TAtlasPlot;
FChartData: TAtlasPlotData;
FChartGrid: TAtlasVirtualGrid;
function AddPage(const Caption: string): TTabSheet;
procedure AddButton(Parent: TWinControl; X: Integer; const Caption: string; Handler: TNotifyEvent);
procedure BuildReadings(Page: TTabSheet);
procedure BuildChart(Page: TTabSheet);
procedure GetReading(Sender: TObject; Column, Row: Integer; var Cell: TAtlasCell);
procedure SetReading(Sender: TObject; Column, Row: Integer; const Cell: TAtlasCell);
procedure StyleReading(Sender: TObject; Column: TAtlasGridColumn; DataRow: Integer;
const Cell: TAtlasCell; var Background, TextColor: TColor);
procedure EditRejected(Sender: TObject; const Message: string);
procedure ApplyFilter(Sender: TObject);
procedure GoToLast(Sender: TObject);
procedure ChartRowChanged(Sender: TObject);
procedure PointClicked(Sender: TObject; Series: TAtlasPlotSeries; PointIndex: Integer);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
implementation
function CellKey(Column, Row: Integer): Int64;
begin
Result := Int64(Row) * 8 + Column;
end;
constructor TVirtualPlotForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
Caption := 'Atlas Virtual Grid: on-demand rows and a chart companion';
Width := 1000;
Height := 640;
Position := poScreenCenter;
FEdits := TDictionary<Int64, TAtlasCell>.Create;
FStatus := TLabel.Create(Self);
FStatus.Parent := Self;
FStatus.Align := alBottom;
FStatus.AlignWithMargins := True;
FStatus.Caption := 'F2, Enter, double-click or type to edit; Esc cancels. Click a header to sort.';
FPages := TPageControl.Create(Self);
FPages.Parent := Self;
FPages.Align := alClient;
BuildReadings(AddPage('One million on-demand rows'));
BuildChart(AddPage('Chart companion'));
end;
destructor TVirtualPlotForm.Destroy;
begin
// The grids and sources are owned by the form and go first; the edits they read go last.
inherited;
FEdits.Free;
end;
function TVirtualPlotForm.AddPage(const Caption: string): TTabSheet;
begin
Result := TTabSheet.Create(Self);
Result.PageControl := FPages;
Result.Caption := Caption;
end;
procedure TVirtualPlotForm.AddButton(Parent: TWinControl; X: Integer; const Caption: string; Handler: TNotifyEvent);
var Button: TButton;
begin
Button := TButton.Create(Self);
Button.Parent := Parent;
Button.SetBounds(X, 5, 88, 26);
Button.Caption := Caption;
Button.OnClick := Handler;
end;
procedure TVirtualPlotForm.BuildReadings(Page: TTabSheet);
var Bar: TPanel;
begin
FReadings := TAtlasVirtualData.Create(Self);
FReadings.AddColumn('Row', ckNumber);
FReadings.AddColumn('Sensor');
FReadings.AddColumn('Reading', ckNumber);
FReadings.AddColumn('Taken', ckDateTime);
FReadings.AddColumn('Checked', ckBoolean);
FReadings.AddColumn('Note');
FReadings.OnGetCell := GetReading;
FReadings.OnSetCell := SetReading; // without it every column is read-only
FReadings.RowCount := RowTotal;
Bar := TPanel.Create(Self);
Bar.Parent := Page;
Bar.Align := alTop;
Bar.Height := 36;
Bar.BevelOuter := bvNone;
FFilter := TEdit.Create(Self);
FFilter.Parent := Bar;
FFilter.SetBounds(8, 6, 200, 24);
FFilter.TextHint := 'Text to look for';
AddButton(Bar, 216, 'Filter', ApplyFilter);
AddButton(Bar, 312, 'Last row', GoToLast);
FGrid := TAtlasVirtualGrid.Create(Self);
FGrid.Parent := Page;
FGrid.Align := alClient;
FGrid.Data := FReadings; // one grid column per data column
FGrid.FrozenColumns := 2; // Row and Sensor stay put when scrolling sideways
FGrid.Columns[ColRow].Width := 80;
FGrid.Columns[ColRow].Format := '#,##0';
FGrid.Columns[ColRow].ReadOnly := True;
FGrid.Columns[ColSensor].ReadOnly := True;
FGrid.Columns[ColReading].Format := '0.00';
FGrid.Columns[ColTaken].Width := 140;
FGrid.Columns[ColTaken].Format := 'yyyy-mm-dd hh:nn';
FGrid.Columns[ColTaken].ReadOnly := True;
FGrid.Columns[ColNote].Width := 640; // wider than the window, so the grid scrolls sideways
FGrid.Columns[ColRow].Band := 'Source';
FGrid.Columns[ColSensor].Band := 'Source';
FGrid.Columns[ColReading].Band := 'Measurement';
FGrid.Columns[ColTaken].Band := 'Measurement';
FGrid.Columns[ColChecked].Band := 'Review';
FGrid.Columns[ColNote].Band := 'Review';
FGrid.OnCellStyle := StyleReading;
FGrid.OnEditError := EditRejected;
end;
procedure TVirtualPlotForm.GetReading(Sender: TObject; Column, Row: Integer; var Cell: TAtlasCell);
begin
// Cell arrives as a null of the column's kind. An edited value wins over the computed one.
if FEdits.TryGetValue(CellKey(Column, Row), Cell) then Exit;
case Column of
ColRow: Cell := TAtlasCell.OfNumber(Row + 1);
ColSensor: Cell := TAtlasCell.OfText('Sensor ' + Chr(Ord('A') + Row mod 8));
ColReading: Cell := TAtlasCell.OfNumber(RoundTo(50 + 60 * Sin(Row / 40) + (Row mod 7) * 3, -2));
ColTaken: Cell := TAtlasCell.OfDateTime(EncodeDate(2026, 1, 1) + Row / 1440);
ColChecked: Cell := TAtlasCell.OfBoolean(Row mod 5 = 0);
end; // ColNote stays null until edited
end;
procedure TVirtualPlotForm.SetReading(Sender: TObject; Column, Row: Integer; const Cell: TAtlasCell);
begin
// The grid has already parsed the text for the column's kind. Raising
// EAtlasGridError rejects the value: the editor stays open and OnEditError says why.
case Column of
ColReading:
if Cell.IsNull then raise EAtlasGridError.Create('A reading cannot be blank')
else if (Cell.Number < -50) or (Cell.Number > 150) then
raise EAtlasGridError.Create('A reading must lie between -50 and 150');
ColNote:
if Length(Cell.Text) > 40 then raise EAtlasGridError.Create('A note holds at most 40 characters');
end;
FEdits.AddOrSetValue(CellKey(Column, Row), Cell);
FStatus.Caption := Format('Row %d changed; %d edited cells held in memory', [Row + 1, FEdits.Count]);
end;
procedure TVirtualPlotForm.StyleReading(Sender: TObject; Column: TAtlasGridColumn; DataRow: Integer;
const Cell: TAtlasCell; var Background, TextColor: TColor);
begin
// Selection is painted over these backgrounds, so the current row stays visible.
if FEdits.ContainsKey(CellKey(Column.DataIndex, DataRow)) then Background := $00C8F0FF
else if (Column.DataIndex = ColReading) and not Cell.IsNull and (Cell.Number > 100) then Background := $00D8D8FF;
if (Column.DataIndex = ColReading) and not Cell.IsNull and (Cell.Number < 0) then TextColor := clBlue;
end;
procedure TVirtualPlotForm.EditRejected(Sender: TObject; const Message: string);
begin
FStatus.Caption := 'Not accepted: ' + Message;
end;
procedure TVirtualPlotForm.ApplyFilter(Sender: TObject);
var Watch: TStopwatch;
begin
// A filter reads every row once; on a million computed rows that takes a moment.
Screen.Cursor := crHourGlass;
try
Watch := TStopwatch.StartNew;
FGrid.FilterText := FFilter.Text;
FStatus.Caption := Format('%s of %s rows match "%s" (%d ms)', [FormatFloat('#,##0', FGrid.RowCount),
FormatFloat('#,##0', FReadings.RowCount), FGrid.FilterText, Watch.ElapsedMilliseconds]);
finally
Screen.Cursor := crDefault;
end;
end;
procedure TVirtualPlotForm.GoToLast(Sender: TObject);
begin
if FGrid.RowCount = 0 then Exit;
FGrid.SelectCell(ColReading, FGrid.RowCount - 1); // scrolls the row into view
if FGrid.CanFocus then FGrid.SetFocus;
end;
procedure TVirtualPlotForm.BuildChart(Page: TTabSheet);
const Quarters: array[0..3] of string = ('Q1', 'Q2', 'Q3', 'Q4');
var Plan, Actual: TAtlasPlotSeries; I: Integer;
begin
FChart := TAtlasPlot.Create(Self);
FChart.Parent := Page;
FChart.Align := alClient;
FChart.Title := 'Edit a value in the table and the bar follows; click a bar to find its cell';
FChart.OnPointClick := PointClicked;
Plan := FChart.AddSeries('Plan', pkBar);
Actual := FChart.AddSeries('Actual', pkBar);
for I := 0 to High(Quarters) do
begin
Plan.AddY(100 + I * 20, Quarters[I]);
Actual.AddY(90 + I * 25 - (I mod 2) * 15, Quarters[I]);
end;
FChartData := TAtlasPlotData.Create(Self);
FChartData.Plot := FChart; // column 0: the point labels; then one column per series
FChartGrid := TAtlasVirtualGrid.Create(Self);
FChartGrid.Parent := Page;
FChartGrid.Align := alBottom;
FChartGrid.Height := 170;
FChartGrid.Data := FChartData;
FChartGrid.Columns[0].Title := 'Quarter';
FChartGrid.Columns[1].Format := '#,##0.0';
FChartGrid.Columns[1].Total := tkSum;
FChartGrid.Columns[2].Format := '#,##0.0';
FChartGrid.Columns[2].Total := tkSum;
FChartGrid.OnEditError := EditRejected;
FChartGrid.OnSelectionChange := ChartRowChanged;
end;
procedure TVirtualPlotForm.ChartRowChanged(Sender: TObject);
var DataRow: Integer;
begin
DataRow := FChartGrid.DataRowOf(FChartGrid.Row);
if DataRow >= 0 then
FStatus.Caption := Format('%s sits at argument %g on the chart',
[FChartGrid.CellText(0, FChartGrid.Row), FChartData.ArgumentOfRow(DataRow)]);
end;
procedure TVirtualPlotForm.PointClicked(Sender: TObject; Series: TAtlasPlotSeries; PointIndex: Integer);
var Row: Integer;
begin
// The adapter gives the data row; the grid turns it into the row as shown (it may be sorted).
Row := FChartGrid.RowOfDataRow(FChartData.RowOfPoint(Series, PointIndex));
if Row >= 0 then FChartGrid.SelectCell(Series.Index + 1, Row);
end;
end.