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:
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
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:
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 := 1before loading JSON so that row 0 receives the JSON key names as column headers automatically.
3. Filter
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
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
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
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:
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:
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.
Related API
TTMSFNCDataGridDataFData.LoadFromJSONData/LoadFromCSVDataFData.Filter.Add(AColumn, AFilterType, ACondition)— typed filter overloadFData.ApplyFilter/RemoveFilterFData.IsRowDisplayed(ARow)— True if the row is visible after filterFData.IsRowNode(ARow)— True if the row is a group headerFData.IsRowSummary(ARow)— True if the row is a group summaryFData.Sort(AColumn, ADirection)FData.Group(AColumn)/UngroupFData.GroupSum/GroupCount/GroupAverage/GroupMin/GroupMaxFData.SaveToCSVData(AFileName)— delimiter set viaOptions.IO.DelimiterFData.Strings[col, row]/Floats/Ints/Booleans— typed accessorsFData.FixedRowCount/ColumnCount/RowCountFData.Options.IO.StartRow— set to 1 so JSON keys land in row 0 as headers
See also
- Architecture — where the data layer fits in the three-layer design.
- Data, formatting & conversion — how values are stored.
- Filtering — full filtering reference.
- Grouping — grouping and aggregation reference.
- Import (JSON) — JSON structure mapping options.