Skip to main content
Table of Contents

Headless data layer

Use the data layer independently — sort, filter, group, and export without any visual component on screen. Useful for background processing, batch exports, and unit-testable data pipelines.

Overview

TTMSFNCDataGridData is the layer that stores cell values and owns all data operations: sorting, filtering, grouping, and CSV export. Because it has no dependency on drawing or controls, you can instantiate it anywhere — including a background thread, a service, or a console application — and run full data operations without displaying anything.

The same API works on both the headless layer and on a live grid: Filter.Add, ApplyFilter, Sort, Group, Strings[], Floats[], Ints[], SaveToCSVData are all on TTMSFNCDataGridData and therefore available on TTMSFNCDataGrid too.

Quick example

Load 500 JSON records, filter by category, sort by price, and log the top five:

Headless quick examplePascalC++
uses
  System.SysUtils, FMX.TMSFNCDataGridData;

procedure TMyForm.LoadRecords;
begin
  FData.ClearData;
  FData.FixedRowCount       := 1;
  FData.Options.IO.StartRow := 1; // row 0 receives JSON key names as headers

  FData.LoadFromJSONData(
    JSONPath,
    '', '',
    ['id', 'name', 'category', 'price', 'stock', 'is_organic', 'is_vegan']);

  Log(Format('Loaded %d records', [FData.RowCount - FData.FixedRowCount]));
end;
void __fastcall TMyForm::LoadRecords()
{
  FData->ClearData();
  FData->FixedRowCount       = 1;
  FData->Options->IO->StartRow = 1; // row 0 receives JSON key names as headers

  DynamicArray<System::UnicodeString> Names;
  Names.Length = 7;
  Names[0] = "id";
  Names[1] = "name";
  Names[2] = "category";
  Names[3] = "price";
  Names[4] = "stock";
  Names[5] = "is_organic";
  Names[6] = "is_vegan";
  FData->LoadFromJSONData(JSONPath, "", "", Names);

  Log(Format("Loaded %d records", ARRAYOFCONST((FData->RowCount - FData->FixedRowCount))));
}

Step by step

1. Create and destroy

Create and destroy a headless data layerPascalC++
type
  TMyForm = class(TForm)
  private
    FData: TTMSFNCDataGridData;
  end;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  FData := TTMSFNCDataGridData.Create;
end;

procedure TMyForm.FormDestroy(Sender: TObject);
begin
  FData.Free;
end;
class TMyForm : public TForm
{
private:
  TTMSFNCDataGridData *FData;
public:
  __fastcall TMyForm(TComponent *Owner) : TForm(Owner) {}
  void __fastcall FormCreate(TObject *Sender);
  void __fastcall FormDestroy(TObject *Sender);
};

void __fastcall TMyForm::FormCreate(TObject *Sender)
{
  FData = new TTMSFNCDataGridData(nullptr);   // the constructor takes the host object
}

void __fastcall TMyForm::FormDestroy(TObject *Sender)
{
  delete FData;
}

TTMSFNCDataGridData is an ordinary object — it allocates on the heap and must be freed explicitly.

2. Load data

The data layer supports the same load methods as the grid:

Load data into the headless layerPascalC++
uses
  FMX.TMSFNCDataGridData;

procedure TMyForm.LoadData(const FileName: string);
begin
  // From JSON (most common for REST payloads)
  FData.LoadFromJSONData(FileName, '', '',
    ['id', 'name', 'category', 'price', 'stock']);

  // From CSV
  FData.Options.IO.Delimiter := ',';
  FData.LoadFromCSVData('products.csv');

  // Manually
  FData.ColumnCount   := 3;
  FData.FixedRowCount := 1;
  FData.RowCount      := 4;
  FData.Cells[0, 0] := 'ID';
  FData.Cells[1, 0] := 'Name';
  FData.Cells[2, 0] := 'Price';
  FData.Cells[0, 1] := 1;  FData.Cells[1, 1] := 'Widget A';  FData.Cells[2, 1] := 9.99;
  FData.Cells[0, 2] := 2;  FData.Cells[1, 2] := 'Widget B';  FData.Cells[2, 2] := 14.50;
  FData.Cells[0, 3] := 3;  FData.Cells[1, 3] := 'Widget C';  FData.Cells[2, 3] := 4.25;
end;
void __fastcall TMyForm::LoadData(const System::UnicodeString FileName)
{
  // From JSON (most common for REST payloads)
  DynamicArray<System::UnicodeString> Names;
  Names.Length = 5;
  Names[0] = "id";
  Names[1] = "name";
  Names[2] = "category";
  Names[3] = "price";
  Names[4] = "stock";
  FData->LoadFromJSONData(FileName, "", "", Names);

  // From CSV
  FData->Options->IO->Delimiter = L',';
  FData->LoadFromCSVData("products.csv");

  // Manually
  FData->ColumnCount   = 3;
  FData->FixedRowCount = 1;
  FData->RowCount      = 4;
  FData->Strings[0][0] = "ID";
  FData->Strings[1][0] = "Name";
  FData->Strings[2][0] = "Price";
  FData->Ints[0][1] = 1;  FData->Strings[1][1] = "Widget A";  FData->Floats[2][1] = 9.99;
  FData->Ints[0][2] = 2;  FData->Strings[1][2] = "Widget B";  FData->Floats[2][2] = 14.50;
  FData->Ints[0][3] = 3;  FData->Strings[1][3] = "Widget C";  FData->Floats[2][3] = 4.25;
}

Set FData.Options.IO.StartRow := 1 before loading JSON so that row 0 receives the JSON key names as column headers automatically.

3. Filter

Filter the headless layerPascalC++
uses
  System.SysUtils, FMX.TMSFNCDataGridData;

procedure TMyForm.FilterData;
var
  R, Count: Integer;
begin
  FData.Filter.Clear;
  FData.Filter.Add(COL_CATEGORY, gftEqual, 'Meat');
  FData.ApplyFilter;

  // After ApplyFilter, iterate visible rows with IsRowDisplayed
  Count := 0;
  for R := FData.FixedRowCount to FData.RowCount - 1 do
    if FData.IsRowDisplayed(R) then
      Inc(Count);
  Log(Format('%d matching rows', [Count]));

  // Remove all filters and show every row again
  FData.RemoveFilter;
end;
void __fastcall TMyForm::FilterData()
{
  FData->Filter->Clear();
  FData->Filter->Add(COL_CATEGORY, gftEqual, TValue::From<System::UnicodeString>("Meat"));
  FData->ApplyFilter();

  // After ApplyFilter, iterate visible rows with IsRowDisplayed
  int Count = 0;
  for (int R = FData->FixedRowCount; R < FData->RowCount; R++)
    if (FData->IsRowDisplayed(R))
      Count++;
  Log(Format("%d matching rows", ARRAYOFCONST((Count))));

  // Remove all filters and show every row again
  FData->RemoveFilter();
}

4. Sort

Sort the headless layerPascalC++
uses
  FMX.TMSFNCDataGridData;

procedure TMyForm.SortData;
begin
  FData.Sort(COL_PRICE, gsdDescending);
end;
void __fastcall TMyForm::SortData()
{
  FData->Sort(COL_PRICE, gsdDescending);
}

Sort and filter interact: sorting re-orders only the displayed rows; hidden rows stay hidden.

5. Group with aggregations

Group and aggregate the headless layerPascalC++
uses
  System.SysUtils, FMX.TMSFNCDataGridData;

procedure TMyForm.GroupData;
var
  R: Integer;
begin
  FData.Group(COL_CATEGORY);
  FData.GroupCount(COL_CATEGORY); // count column
  FData.GroupSum(COL_PRICE);      // sum column

  // Walk the result to inspect group rows and their summaries
  for R := FData.FixedRowCount to FData.RowCount - 1 do
  begin
    if FData.IsRowNode(R) then
      Log(FData.Strings[FData.FixedColumnCount, R]) // group header text
    else if FData.IsRowSummary(R) then
      Log(Format('  count=%.0f   sum=$%.2f',
        [FData.Floats[COL_CATEGORY, R], FData.Floats[COL_PRICE, R]]));
  end;

  FData.Ungroup;
end;
void __fastcall TMyForm::GroupData()
{
  FData->Group(COL_CATEGORY);
  FData->GroupCount(COL_CATEGORY); // count column
  FData->GroupSum(COL_PRICE);      // sum column

  // Walk the result to inspect group rows and their summaries
  for (int R = FData->FixedRowCount; R < FData->RowCount; R++)
  {
    if (FData->IsRowNode(R))
      Log(FData->Strings[FData->FixedColumnCount][R]); // group header text
    else if (FData->IsRowSummary(R))
      Log(Format("  count=%.0f   sum=$%.2f",
        ARRAYOFCONST(((long double)FData->Floats[COL_CATEGORY][R], (long double)FData->Floats[COL_PRICE][R]))));
  }

  FData->Ungroup();
}

6. Export

Export the headless layer to CSVPascalC++
uses
  FMX.TMSFNCDataGridData;

procedure TMyForm.ExportData;
begin
  FData.Options.IO.Delimiter := ',';
  FData.SaveToCSVData('output.csv');
end;
void __fastcall TMyForm::ExportData()
{
  FData->Options->IO->Delimiter = L',';
  FData->SaveToCSVData("output.csv");
}

7. Read typed values

Use typed accessors for performance in tight loops:

Read typed valuesPascalC++
uses
  FMX.TMSFNCDataGridData;

procedure TMyForm.ReadTypedValues(Row: Integer);
var
  Name: string;
  Price: Double;
  Stock: Integer;
begin
  Name  := FData.Strings[COL_NAME,  Row];
  Price := FData.Floats [COL_PRICE, Row];
  Stock := FData.Ints   [COL_STOCK, Row];
end;
void __fastcall TMyForm::ReadTypedValues(int Row)
{
  System::UnicodeString Name = FData->Strings[COL_NAME][Row];
  double Price = FData->Floats[COL_PRICE][Row];
  int Stock = FData->Ints[COL_STOCK][Row];
}

Connecting the headless layer to a visual grid

If you later want to display the processed data in a TTMSFNCDataGrid, you can copy it row by row or simply re-load the same source into the grid:

Load the same source into a visual gridPascalC++
uses
  FMX.TMSFNCDataGrid;

procedure TMyForm.ShowInGrid(const FileName: string);
begin
  Grid.LoadFromJSONData(FileName, '', '', ['id', 'name', 'price']);
end;
void __fastcall TMyForm::ShowInGrid(const System::UnicodeString FileName)
{
  DynamicArray<System::UnicodeString> Names;
  Names.Length = 3;
  Names[0] = "id";
  Names[1] = "name";
  Names[2] = "price";
  Grid->LoadFromJSONData(FileName, "", "", Names);
}

For a shared-source pattern, use TTMSFNCDataGridDatabaseAdapter against a common TDataSet. The data layer is best used for scenarios where you need data operations without any UI — for example, generating a filtered CSV in a background thread or computing statistics at startup.

  • TTMSFNCDataGridData
  • FData.LoadFromJSONData / LoadFromCSVData
  • FData.Filter.Add(AColumn, AFilterType, ACondition) — typed filter overload
  • FData.ApplyFilter / RemoveFilter
  • FData.IsRowDisplayed(ARow) — True if the row is visible after filter
  • FData.IsRowNode(ARow) — True if the row is a group header
  • FData.IsRowSummary(ARow) — True if the row is a group summary
  • FData.Sort(AColumn, ADirection)
  • FData.Group(AColumn) / Ungroup
  • FData.GroupSum / GroupCount / GroupAverage / GroupMin / GroupMax
  • FData.SaveToCSVData(AFileName) — delimiter set via Options.IO.Delimiter
  • FData.Strings[col, row] / Floats / Ints / Booleans — typed accessors
  • FData.FixedRowCount / ColumnCount / RowCount
  • FData.Options.IO.StartRow — set to 1 so JSON keys land in row 0 as headers

See also