Table of Contents

Data binding

Bind a TTMSFNCDataGrid to any TDataSet (FireDAC, ClientDataSet, BDE, StellarDS, …) via the dedicated TTMSFNCDataGridDatabaseAdapter component. No code is required for the basic case — drop, wire, open.

Overview

The database adapter is the bridge between the dataset world (records, fields, navigation) and the grid world (columns, cells, layouts). It listens for dataset events and synchronises the grid in both directions: edits in the grid go back to the dataset, and navigation in the dataset moves the selected grid row.

Capability API
Auto-create columns from fields AutoCreateColumns := True
Pick which fields appear Adapter.Columns[i] collection
Buffered (lazy) loading for huge datasets LoadMode := almBuffered
HTML templates per cell Adapter.Columns[i].HTMLTemplate
BLOB fields rendered as images Adapter.Columns[i].PictureField := True
Numeric fields rendered as progress bars Adapter.Columns[i].ProgressField := True
Lookup fields rendered as drop-downs Adapter.Columns[i].UseLookupEditor := True
Custom field-to-cell conversion OnFieldToData / OnDataToField
Dynamic HTML template per cell OnGetHTMLTemplate / OnGetHTMLTemplateData

Quick example

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Wire up the adapter
  Adapter.DataGrid    := Grid;
  Adapter.DataSource  := DataSource1;        // TDataSource attached to FDTable1

  // Auto-create one column per dataset field; remove columns when fields go away.
  Adapter.AutoCreateColumns := True;
  Adapter.AutoRemoveColumns := True;

  // Open the dataset — the grid populates itself.
  FDTable1.Active := True;

  // Optional: shrink the row-number column after the data loads.
  Grid.ColumnWidths[0] := 30;
  Grid.AutoSizeColumns;
end;

Step by step

1. Drop the adapter

Place a TTMSFNCDataGridDatabaseAdapter next to the grid on the form. Set:

Adapter.DataGrid   := Grid;
Adapter.DataSource := DataSource1;

2. Choose how columns are created

Mode What you get
AutoCreateColumns := True (default) One column per dataset field. Headers from DisplayLabel.
Manual: Adapter.Columns.Add Pick exactly which fields to expose, in which order.

3. Open the dataset

FDTable1.Active := True;       // grid populates immediately

4. Customise individual adapter columns

Adapter.Columns[2].HTMLTemplate  := '<b><#Name></b>';
Adapter.Columns[3].PictureField  := True;          // BLOB → embedded image
Adapter.Columns[4].ProgressField := True;          // numeric → progress bar
Adapter.Columns[5].UseLookupEditor := True;        // lookup field → drop-down

5. Dynamic HTML templates

For cell-level HTML where the template depends on the row data, use the event instead of a fixed HTMLTemplate:

procedure TForm1.AdapterGetHTMLTemplate(Sender: TObject;
  ACol, ARow: Integer; var AHTMLTemplate: string; AFields: TFields);
begin
  if ACol = StatusColumn then
    AHTMLTemplate := '<font color="<#StatusColor>"><#Status></font>';
end;

6. Override field/cell conversion (optional)

procedure TForm1.AdapterFieldToData(Sender: TObject; ACell: TTMSFNCDataGridCellCoord;
  AMasterField, AField: TField; var ACellValue: TTMSFNCDataGridCellValue;
  var Handled: Boolean);
begin
  if AField.FieldName = 'Salary' then
  begin
    ACellValue := FormatCurr('$#,##0.00', AField.AsCurrency);
    Handled := True;
  end;
end;

Buffered vs all-at-once loading

LoadMode When to use
almAllRecords (default) Datasets up to ~50k records. Enables full client-side sort/group.
almBuffered Larger datasets. Only the visible window is kept in memory.

In buffered mode, sorting and grouping become server-side concerns — let the underlying SQL handle them.

// All-at-once (default) — full client-side sort and group available
Adapter.LoadMode := almAllRecords;
Adapter.LoadAll;

// Buffered — only the visible window is kept in memory
// Sort and group via the SQL ORDER BY / GROUP BY instead
Adapter.LoadMode := almBuffered;

Overriding the displayed value: OnGetCellDisplayValue

The adapter writes the field value into the cell (via OnFieldToData or the default mapping). Sometimes you want the grid to display something different without changing what is stored — for example, combining fields, applying custom formatting, or injecting HTML.

Hook OnGetCellDisplayValue on the grid (not the adapter):

procedure TForm1.GridGetCellDisplayValue(Sender: TObject;
  ACell: TTMSFNCDataGridCellCoord;
  AData: TTMSFNCDataGridCellValue;          // stored value
  var AValue: string);                     // override what is rendered
begin
  if ACell.Row <= 0 then Exit;             // skip header row

  if ACell.Column = 2 then
    // Combine first name (col 0) and last name (col 1) into column 2
    AValue := Grid.Strings[0, ACell.Row] + ' ' + Grid.Strings[1, ACell.Row];
end;

OnGetCellDisplayValue also enables HTML in adapter-connected cells. The stored value remains the raw field text; the displayed value can be an HTML string:

// Display a date field as bold HTML without storing HTML in the dataset
if ACell.Column = DateColumn then
  AValue := '<b>' + FormatDateTime('dd-mmm-yyyy',
              Grid.Floats[ACell.Column, ACell.Row]) + '</b>';

The overridden display value is used for rendering only. The stored value (AData) and the data the adapter writes back to the dataset are not affected.

DynamicRecordCount

For server-side paging where the exact record count is not known upfront, set DynamicRecordCount on the database adapter:

Adapter.DynamicRecordCount := True;

When enabled, the adapter does not call RecordCount on the dataset. This avoids a potentially expensive full-table scan on databases that don't have a cheap record-count operation. Combine with LoadMode := almBuffered and Grid.Paging for a full server-side paging setup.

Combining adapter loading, conversion, and display overrides

Real database grids often need more than a straight field-to-column mapping. This setup keeps the adapter in buffered mode for a large query, formats the stored currency value before it reaches the cell, and still uses OnGetCellDisplayValue for render-only markup that should not be written back to the dataset.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Adapter.DataGrid := Grid;
  Adapter.DataSource := DataSource1;
  Adapter.AutoCreateColumns := True;
  Adapter.LoadMode := almBuffered;
  Adapter.DynamicRecordCount := True;
  Adapter.OnFieldToData := AdapterFieldToData;

  Grid.OnGetCellDisplayValue := GridGetCellDisplayValue;

  FDQuery1.Active := True;
end;

procedure TForm1.AdapterFieldToData(Sender: TObject;
  ACell: TTMSFNCDataGridCellCoord; AMasterField, AField: TField;
  var ACellValue: TTMSFNCDataGridCellValue; var Handled: Boolean);
begin
  if AField.FieldName = 'Amount' then
  begin
    ACellValue := FormatCurr('#,##0.00', AField.AsCurrency);
    Handled := True;
  end;
end;

procedure TForm1.GridGetCellDisplayValue(Sender: TObject;
  ACell: TTMSFNCDataGridCellCoord; AData: TTMSFNCDataGridCellValue;
  var AValue: string);
begin
  if (ACell.Row > 0) and (ACell.Column = StatusColumn) then
    AValue := '<b>' + AValue + '</b>';
end;
  • TTMSFNCDataGridDatabaseAdapter
  • Adapter.DataSource, Adapter.DataGrid, Adapter.AutoCreateColumns
  • Adapter.Columns[i]FieldName, Header, HTMLTemplate, PictureField, CheckBoxField, ProgressField, UseLookupEditor
  • Adapter.LoadMode (almBuffered / almAllRecords)
  • Adapter.DynamicRecordCount — skip RecordCount call for server-side paging
  • OnGetCellDisplayValue — override the rendered text per cell without changing stored data
  • OnFieldToData / OnDataToField
  • OnGetHTMLTemplate / OnGetHTMLTemplateData

See also

  • Master-detail — link two grids via the dataset hierarchy.
  • Large datasets — buffered loading patterns.
  • Demo: Demo/FMX/DataGrid/Database/Customers_FireDac_SQLite
  • Demo: Demo/FMX/DataGrid/Database/Biolife_ClientDataSet
  • Demo: Demo/FMX/DataGrid/Database/StellarDS