Table of Contents

Large datasets

Patterns for handling tens or hundreds of thousands of rows — even millions when paired with a database adapter — without freezing the UI.

Overview

The grid is designed for large data: rendering, sorting, filtering, and grouping all scale to hundreds of thousands of in-memory rows. Above that, switch from "load it all" to "load what's visible" via the database adapter's buffered mode.

Scenario Approach
Up to ~100k rows in memory BeginUpdate / EndUpdate + bulk fill
100k–1M rows in memory Same, plus disable AutoSizeColumns, prefer ClearData over per-cell deletes
1M+ rows from a TDataSet Database adapter with LoadMode := almBuffered
Streaming CSV/binary LoadFromCSVStreamData inside BeginUpdate/EndUpdate

Quick example

procedure TForm1.LoadOneMillionRows;
var
  i: Integer;
begin
  Grid.BeginUpdate;
  try
    Grid.ClearAll;
    Grid.ColumnCount := 4;
    Grid.RowCount    := 1_000_001;          // 1M rows + 1 header

    Grid.Cells[0, 0] := 'ID';
    Grid.Cells[1, 0] := 'Value';
    Grid.Cells[2, 0] := 'Date';
    Grid.Cells[3, 0] := 'Description';

    // Fill rows. The grid never paints during BeginUpdate so this stays fast.
    for i := 1 to Grid.RowCount - 1 do
    begin
      Grid.Ints[0, i]   := i;
      Grid.Floats[1, i] := Random * 1000;
      Grid.Cells[2, i]  := Now - Random(1000);
      Grid.Cells[3, i]  := 'Row ' + IntToStr(i);
    end;
  finally
    Grid.EndUpdate;
  end;
end;

procedure TForm1.LoadFromBigDataset;
begin
  // Bind to a TDataSet and use buffered loading: only the visible window
  // is fetched. Memory stays small even for billion-row datasets.
  Adapter.LoadMode := almBuffered;
  Adapter.DataSource := DataSource1;
  FDQuery1.Open;
end;

Patterns

1. Always wrap bulk operations in BeginUpdate / EndUpdate

Grid.BeginUpdate;
try
  Grid.RowCount := 1_000_000;
  for i := 1 to Grid.RowCount - 1 do
    Grid.Cells[0, i] := i;
finally
  Grid.EndUpdate;
end;

Without BeginUpdate, the grid recalculates layout and repaints after every cell assignment — turning a 100ms operation into minutes.

2. Disable expensive features during the load

Grid.BeginUpdate;
try
  PreviousAutoSize := Grid.Options.Column.Stretching.Enabled;
  Grid.Options.Column.Stretching.Enabled := False;

  LoadAllRows;

  Grid.AutoSizeColumns;
  Grid.Options.Column.Stretching.Enabled := PreviousAutoSize;
finally
  Grid.EndUpdate;
end;

3. Use typed cell setters

Grid.Cells[col, row] := X is type-erased and slightly slower than the typed setters. Use these in tight inner loops:

Grid.Ints[col, row]     := SomeInt;
Grid.Floats[col, row]   := SomeFloat;
Grid.Booleans[col, row] := SomeBoolean;

4. Database adapter with buffered loading

For data that doesn't fit in memory:

Adapter.LoadMode   := almBuffered;
Adapter.DataSource := DataSource1;
FDQuery1.Open;                      // billion-row table is fine

In buffered mode the adapter only reads the visible window from the dataset. Sorting, filtering, and grouping become server-side concerns — push them down into SQL.

5. Paging instead of scrolling

If "show 50 rows at a time with prev/next buttons" fits the UX better than infinite scroll, enable paging:

Grid.Paging                             := True;
Grid.Options.Keyboard.PageScrollSize    := 50;

Then navigate with Grid.NextPage, PreviousPage, FirstPage, LastPage, or GoToPage(n).

6. Streamed CSV import

Grid.Options.IO.Delimiter := ',';
var fs := TFileStream.Create('huge.csv', fmOpenRead);
try
  Grid.BeginUpdate;
  try
    Grid.LoadFromCSVStreamData(fs);
  finally
    Grid.EndUpdate;
  end;
finally
  fs.Free;
end;

7. Virtual data with OnGetCellData

Instead of filling every cell upfront, you can keep your data in an external list and let the grid ask for values on demand. Hook OnGetCellData on the grid:

// Keep the data outside the grid
var FData: TArray<TArray<string>>;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Set the size — rows and columns — but don't fill any cells
  Grid.RowCount    := Length(FData) + 1;     // +1 for header row
  Grid.ColumnCount := 5;
  Grid.FixedRowCount := 1;
  Grid.Cells[0, 0] := 'ID';
  // ... set other headers ...
end;

procedure TForm1.GridGetCellData(Sender: TObject;
  ACell: TTMSFNCDataGridCellCoord;
  var AData: TTMSFNCDataGridCellValue);
var
  DataRow: Integer;
begin
  if ACell.Row = 0 then Exit;              // header is already set
  DataRow := ACell.Row - Grid.FixedRowCount;
  if (DataRow >= 0) and (DataRow < Length(FData)) then
    AData := FData[DataRow][ACell.Column];
end;

OnGetCellData fires for every cell as the grid renders or accesses a value. The grid does not write the returned value to its internal dictionary — each render call fires the event again. This is ideal for read-only views of a large in-memory list where you want zero copy overhead.

Note: Because the grid doesn't store the values internally, features that require reading from the internal dictionary (bulk sort, CSV export, clipboard) will see empty values. Use OnGetCellData for display-only virtualisation; for full functionality with large data prefer the database adapter with almBuffered loading.

Profiling tips

  • The default Grid.ScrollMode is pixel-smooth (gsmPixelScrolling). On underpowered devices, switch to gsmCellScrolling for better throughput.
  • Grid.ScrollUpdate controls whether redrawing happens continuously during a scroll drag or once at release — set the latter for jankier datasets.
  • Use OnAfterCalculate / OnBeforeCalculate to identify unintended recalculation cycles.
  • Set Options.IO.MaxRows to cap how many rows are loaded from a CSV file.
  • Grid.BeginUpdate / EndUpdate
  • Grid.Ints[col, row], Floats, Booleans — typed setters
  • Grid.Paging, Grid.PageIndex, Grid.PageCount
  • Grid.Options.Keyboard.PageScrollSize — fixed rows per page
  • Grid.NextPage, PreviousPage, FirstPage, LastPage, GoToPage
  • Grid.ScrollMode, Grid.ScrollUpdate
  • TTMSFNCDataGridDatabaseAdapter.LoadMode = almBuffered
  • Grid.LoadFromCSVStreamData
  • Grid.Options.IO.MaxRows
  • OnGetCellData — virtual data provider; fires per cell on render

See also