Skip to main content
Table of Contents

Architecture

The DataGrid is built as three layers stacked on top of each other. Knowing which layer owns which job makes it easy to find APIs, write custom cells, and reason about performance.

The three layers

┌─────────────────────────────────────────┐
│ TTMSFNCDataGridRenderer                 │  ← drawing, layout, controls
│   ┌─────────────────────────────────┐   │
│   │ TTMSFNCDataGridData             │   │  ← data, sort/filter/group
│   │   ┌─────────────────────────┐   │   │
│   │   │ TTMSFNCDataGridCore     │   │   │  ← geometry, coordinates
│   │   └─────────────────────────┘   │   │
│   └─────────────────────────────────┘   │
└─────────────────────────────────────────┘
Layer Class Responsibility
Geometry TTMSFNCDataGridCore Column and row counts, fixed/freeze regions, cell coordinates, dimensions.
Data TTMSFNCDataGridData Cell values (TValue-based), formatting, sorting, filtering, grouping, calculations.
Drawing TTMSFNCDataGridRenderer Layout calculation, cell painting, embedded controls, scrolling, selection visuals.

The user-facing component, TTMSFNCDataGrid, wraps a root instance of TTMSFNCDataGridRenderer and exposes its API. When you embed a sub-grid in a cell (see Cell controls), a fresh TTMSFNCDataGridRenderer is created — so the same three-layer stack applies inside the nested grid too.

Why three layers?

The split lets each layer be reused without dragging the others along:

  • Headless processing — the data layer can sort, filter, and group without ever drawing.
  • Custom rendering — the drawing layer can be subclassed without touching how data is stored.
  • Cross-platform — TTMSFNCDataGridCore has no FMX, VCL, or Web dependencies, so the same coordinate system works everywhere.

Where to subclass

If you want to… Subclass / override
Change how a cell is painted TTMSFNCDataGridCell (see Custom cells)
Change how a cell is laid out Hook OnGetCellLayout (see Appearance & theming)
Change how data is converted to display text Hook OnGetCellFormatting (see Data, formatting & conversion)
Add a custom column type Subclass TTMSFNCDataGridColumn
Build a master-detail UI Use two adapter pairs (see Master-detail)

Combining the three layers

The three layers are easiest to understand when you see them used together. Through TTMSFNCDataGrid you configure the geometry layer (column and row counts, fixed rows), the data layer (typed cell values and sorting) and the drawing layer (an OnGetCellLayout handler that recolours cells) without ever leaving one object. The same data layer is also available on its own as TTMSFNCDataGridData, which is what you want for background processing where nothing is drawn. Use the combined form when you build a visible grid, and the headless form when the result never reaches the screen. The example below does both, so you can compare which calls belong to which layer.

Geometry, data and drawing layers used togetherPascalC++
procedure TForm1.UseTheThreeLayers;
var
  Data: TTMSFNCDataGridData;
begin
  // Geometry layer: counts and fixed regions.
  Grid.ColumnCount := 3;
  Grid.FixedRowCount := 1;
  Grid.RowCount := 4;

  // Data layer: typed cell values and sorting.
  Grid.Strings[0, 0] := 'Name';
  Grid.Strings[1, 0] := 'Status';
  Grid.Strings[2, 0] := 'Amount';
  Grid.Strings[0, 1] := 'Alpha';  Grid.Strings[1, 1] := 'Overdue'; Grid.Floats[2, 1] := 120.5;
  Grid.Strings[0, 2] := 'Beta';   Grid.Strings[1, 2] := 'Paid';    Grid.Floats[2, 2] := 80;
  Grid.Strings[0, 3] := 'Gamma';  Grid.Strings[1, 3] := 'Overdue'; Grid.Floats[2, 3] := 310.2;
  Grid.Sort(2, gsdDescending);

  // Drawing layer: change how cells look without touching how they are stored.
  Grid.OnGetCellLayout := GridGetCellLayout;

  // The data layer also works on its own, with no drawing at all.
  Data := TTMSFNCDataGridData.Create(Self);
  try
    Data.ColumnCount := 3;
    Data.RowCount := 3;
    Data.Floats[2, 0] := 5;
    Data.Floats[2, 1] := 1;
    Data.Floats[2, 2] := 3;
    Data.Sort(2, gsdAscending);
  finally
    Data.Free;
  end;
end;

procedure TForm1.GridGetCellLayout(Sender: TObject; ACell: TTMSFNCDataGridCell);
begin
  if (ACell.Column = 1) and SameText(ACell.Text, 'Overdue') then
    ACell.Layout.Fill.Color := gcMistyrose;
end;
void __fastcall TForm1::UseTheThreeLayers()
{
  // Geometry layer: counts and fixed regions.
  Grid->ColumnCount = 3;
  Grid->FixedRowCount = 1;
  Grid->RowCount = 4;

  // Data layer: typed cell values and sorting.
  Grid->Strings[0][0] = "Name";
  Grid->Strings[1][0] = "Status";
  Grid->Strings[2][0] = "Amount";
  Grid->Strings[0][1] = "Alpha";  Grid->Strings[1][1] = "Overdue"; Grid->Floats[2][1] = 120.5;
  Grid->Strings[0][2] = "Beta";   Grid->Strings[1][2] = "Paid";    Grid->Floats[2][2] = 80;
  Grid->Strings[0][3] = "Gamma";  Grid->Strings[1][3] = "Overdue"; Grid->Floats[2][3] = 310.2;
  Grid->Sort(2, gsdDescending);

  // Drawing layer: change how cells look without touching how they are stored.
  Grid->OnGetCellLayout = GridGetCellLayout;

  // The data layer also works on its own, with no drawing at all.
  TTMSFNCDataGridData *Data = new TTMSFNCDataGridData(this);
  try
  {
    Data->ColumnCount = 3;
    Data->RowCount = 3;
    Data->Floats[2][0] = 5;
    Data->Floats[2][1] = 1;
    Data->Floats[2][2] = 3;
    Data->Sort(2, gsdAscending);
  }
  __finally
  {
    delete Data;
  }
}

void __fastcall TForm1::GridGetCellLayout(TObject *Sender, TTMSFNCDataGridCell *ACell)
{
  if ((ACell->Column == 1) && SameText(ACell->Text, "Overdue"))
    ACell->Layout->Fill->Color = gcMistyrose;
}

See also