Blog
All Blog Posts | Next Post | Previous Post
Next Generation Data Grid for Delphi: Headless Data Layer
Friday, May 15, 2026
Intro
Not every data operation belongs on screen. Sometimes you need to load a data set, filter it, sort it, group it, calculate summaries, and export the result without showing a grid at all.
That is where the headless data layer in TMS FNC Data Grid becomes especially useful. With TTMSFNCDataGridData, the same engine that powers
the visual TTMSFNCDataGrid can be used independently in services, background jobs,
console tools, tests, batch exports, or startup data preparation routines.
In other words: you can use the grid's data intelligence without needing the grid's visual surface.
Why Use a Headless Data Layer?
A visual grid is perfect when users need to inspect and interact with data. But many application workflows happen before, after, or completely outside the UI:
Background processing: load data, transform it, and prepare results while the UI remains responsive
Batch exports: generate filtered CSV files without creating a form or control
Automated tests: verify sorting, filtering, grouping, and aggregation logic without UI automation
Services and console apps: reuse familiar grid data operations in non-visual applications
The important part is that TTMSFNCDataGridData owns the data operations. Methods
such as Filter.Add, ApplyFilter, Sort, Group,
SaveToCSVData, and the typed accessors like Strings[],
Floats[], and Ints[] are available directly on the data layer.
Creating the Data Object
Because TTMSFNCDataGridData is a regular Delphi object, it can be created anywhere
you would normally create an object. There is no need for a form, parent control, or
rendering context.
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;
That small setup gives you a reusable in-memory data layer with the same core operations you would expect from the visual grid.
Loading Data
The headless layer supports the same practical loading scenarios as the grid. You can load JSON, load CSV, or fill cells manually when you want full control.
// From JSON
FData.Options.IO.StartRow := 1;
FData.LoadFromJSONData(
JSONPath,
'', '',
['id', 'name', 'category', 'price', 'stock', 'is_organic', 'is_vegan']);
// From CSV
FData.LoadFromCSVData('products.csv', ',');
Setting Options.IO.StartRow to 1 before loading JSON is a useful detail:
row 0 can receive the JSON key names as column headers, while the actual records start
below that fixed header row.
Manual population is equally direct:
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;
Filtering Without a Grid
Filtering works the same way it does with the visual grid. Add one or more filter conditions, apply them, and then work with the displayed rows.
FData.Filter.Clear; FData.Filter.Add(COL_CATEGORY, gftEqual, 'Meat'); FData.ApplyFilter;
After calling ApplyFilter, rows that do not match are hidden from the active view.
You can use IsRowDisplayed when iterating the data:
var Count := 0;
for var R := FData.FixedRowCount to FData.RowCount - 1 do
if FData.IsRowDisplayed(R) then
Inc(Count);
Log(Format('%d matching rows', [Count]));
To return to the full data set, call RemoveFilter.
FData.RemoveFilter;
Sorting the Active Result
Sorting is just as compact. The data layer can sort a column in ascending or descending order without requiring any visual repaint cycle.
FData.Sort(COL_PRICE, gsdDescending);
Sorting and filtering work together. When a filter is active, the sort operation reorders the displayed rows while keeping filtered-out rows hidden.
Grouping and Aggregations
The headless layer is also useful when you need summary data. You can group by a column and calculate values such as count, sum, average, minimum, or maximum.
FData.Group(COL_CATEGORY); FData.GroupCount(COL_CATEGORY); FData.GroupSum(COL_PRICE);
Once grouped, the result contains group header rows and summary rows that can be inspected programmatically:
for var R := FData.FixedRowCount to FData.RowCount - 1 do
begin
if FData.IsRowNode(R) then
Log(FData.Strings[FData.FixedColumnCount, R])
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;
This makes it convenient to build reporting features where the final output is a log, CSV file, dashboard feed, or another data structure rather than an interactive grid.
Exporting the Result
After loading, filtering, sorting, and grouping, you can export the processed result directly to CSV.
FData.SaveToCSVData('output.csv', ',');
This is a practical pattern for server-side export buttons, scheduled reporting tasks, or command-line tools that need the same data behavior as your application's UI.
Reading Typed Values
When looping over rows, use the typed accessors to keep your code clear and efficient. Instead of reading everything as text and converting manually, read the expected data type directly from the layer.
var Name := FData.Strings[COL_NAME, Row]; var Price := FData.Floats [COL_PRICE, Row]; var Stock := FData.Ints [COL_STOCK, Row];
For tight processing loops, this keeps the intent of the code obvious: names are strings, prices are floating point values, stock counts are integers.
Showing the Data Later
Using the headless layer does not lock you into a non-visual workflow. If the processed
data later needs to be shown in a TTMSFNCDataGrid, you can load the same source into
the grid or copy values into the visual component.
// Let the grid load its own visual copy TMSFNCDataGrid1.LoadFromJSONData( JSONPath, '', '', ['id', 'name', 'price']);
For database-backed scenarios, TTMSFNCDataGridDatabaseAdapter can be used with a
shared TDataSet. You can bind that adapter to a visual grid, or bind it directly to
TTMSFNCDataGridData when the workflow is headless and the data operations matter
more than immediate visual interaction.
Binding a Dataset
For a non-visual workflow, assign the database adapter to the data layer. The result is still a dataset-backed data engine, but without any renderer or control on screen.
procedure TMyService.LoadProducts;
begin
FData := TTMSFNCDataGridData.Create;
Adapter.DataSource := DataSource1; // TDataSource attached to FDQuery1
Adapter.AutoCreateColumns := True;
FData.Adapter := Adapter;
FDQuery1.Open; // records are loaded into FData
FData.Filter.Clear;
FData.Filter.Add(COL_CATEGORY, gftEqual, 'Meat');
FData.ApplyFilter;
FData.Sort(COL_PRICE, gsdDescending);
FData.SaveToCSVData('filtered-products.csv', ',');
end;
This is the database equivalent of loading JSON or CSV into FData: once the
records are in the data layer, the rest of the processing code can stay the same. For
exports, reports, validation jobs, and tests, that can be cleaner than creating a visual
grid just to reach the data operations.
Choosing Fields and Column Behavior
Automatic columns are convenient during prototyping, but production screens often need a more curated shape: hide technical fields, reorder columns, use friendly headers, or render specific field types with richer UI.
The adapter column collection lets you decide exactly which fields appear and how they are presented:
Adapter.AutoCreateColumns := False; with Adapter.Columns.Add do begin FieldName := 'ProductName'; Header := 'Product'; end; with Adapter.Columns.Add do begin FieldName := 'Price'; Header := 'Unit Price'; end;
Individual adapter columns can also be configured for more specialized rendering:
Adapter.Columns[2].HTMLTemplate := '<b>{Name}</b>';
Adapter.Columns[3].PictureField := True; // BLOB field as image
Adapter.Columns[4].ProgressField := True; // numeric field as progress bar
Adapter.Columns[5].UseLookupEditor := True; // lookup field as drop-down
For conversion logic, the adapter exposes OnFieldToData and
OnDataToField. That gives you a clean place to format database values before
they enter the grid, or to translate edited grid values before they are posted back to
the dataset.
A Complete Processing Flow
A typical headless workflow can be surprisingly small: load records, apply business filters, sort the result, and export it.
FData.ClearData;
FData.FixedRowCount := 1;
FData.Options.IO.StartRow := 1;
FData.LoadFromJSONData(
JSONPath,
'', '',
['id', 'name', 'category', 'price', 'stock']);
FData.Filter.Clear;
FData.Filter.Add(COL_CATEGORY, gftEqual, 'Meat');
FData.ApplyFilter;
FData.Sort(COL_PRICE, gsdDescending);
FData.SaveToCSVData('filtered-products.csv', ',');
No visual control is required, but the data behavior remains consistent with the grid users see elsewhere in the application.
Sample
https://www.tmssoftware.com/download/DataLayerDemo.zip
Conclusion
TTMSFNCDataGridData gives you the data-processing side of
TTMSFNCDataGrid as a standalone layer. You can load JSON or CSV, apply filters,
sort rows, group data, calculate aggregations, read typed values, and export the result
without placing a grid on screen.
For applications that need background exports, automated tests, reporting pipelines, or non-visual data preparation, this is a clean way to reuse the same proven data layer that powers the visual grid. It keeps your logic testable, your UI optional, and your data workflow ready for both desktop and web applications.
Pieter Scheldeman
Related Blog Posts
-
Next Generation Data Grid for Delphi: Getting Started
-
Next Generation Data Grid for Delphi: Adding, Formatting & Converting Data
-
Next Generation Data Grid for Delphi: Filtering & Sorting
-
Next Generation Data Grid for Delphi: Grouping
-
Next Generation Data Grid for Delphi: Webinar Replay Available!
-
Next Generation Data Grid for Delphi: Cell Controls
-
Next Generation Data Grid for Delphi: Master-Detail
-
Next Generation Data Grid for Delphi: Calculations
-
Next Generation Data Grid for Delphi: Import & Export
-
Next Generation Data Grid for Delphi: Template
-
Next Generation Data Grid for Delphi: Filter Row
-
Next Generation Data Grid for C++: Getting Started
-
Freebie Friday: Next Generation Grid Quick Sample Data
-
Next Generation Data Grid for Delphi: Columns Editor
-
Next Generation Data Grid for Delphi: File Drag & Drop
-
Next Generation Data Grid for Delphi: Visual Grouping
-
Next Generation Data Grid for Delphi: FMX Linux Support
-
Next Generation Data Grid for Delphi: Header & Footer Buttons
-
Next Generation Data Grid for Delphi: Paging
-
Next Generation Data Grid for Delphi: Cell Classes
-
Next Generation Data Grid for Delphi: Excel Style Selection
-
Next Generation Data Grid for Delphi: AutoFill
-
Bridging FlexCel and Our Next-Gen Data Grid
-
Next Generation Data Grid for Delphi: Headless Data Layer
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post