Table of Contents

Grouping

Group rows by one or more columns, with optional summary aggregations (sum, average, count, min, max) and collapsible group headers.

Overview

Grouping splits the rows of the grid into buckets based on the value of a group column. Each bucket is shown under a tree-style group header that the user can expand or collapse. Multi-level grouping (group by Country, then by Brand) is supported by passing an array of column indices.

Aggregations — sum, average, count, min, max — render as a footer row at the bottom of each group (or inside the header when SummaryInHeader is on).

DataGrid grouped by Brand with merged group headers and a per-group Power sum in each group's summary row DataGrid grouped by Brand with per-group summary rows (dark theme)

Quick example

procedure TForm1.GroupButtonClick(Sender: TObject);
begin
  // Visual settings
  Grid.Options.Grouping.MergeHeader := True;
  Grid.Options.Grouping.Summary := True;

  // Group by column 1 (Brand)
  Grid.Group(1);

  // Add aggregations on the grouped data
  Grid.GroupSum(6);          // sum of Kw column per group
  Grid.GroupAverage(5);      // average of Cyl column per group
end;

procedure TForm1.UngroupButtonClick(Sender: TObject);
begin
  Grid.UnGroup;
end;

procedure TForm1.ToggleExpandClick(Sender: TObject);
begin
  if AllExpanded then
    Grid.CollapseAllNodes
  else
    Grid.ExpandAllNodes;
  AllExpanded := not AllExpanded;
end;

Step by step

Group by one column

Grid.Group(1);                               // group by column 1

Group by multiple columns (nested)

Pass an array — the first column is the outer group, the rest are nested:

Grid.Group([8, 1]);                          // by Country, then Brand

Configure visual options

Grid.Options.Grouping.MergeHeader    := True;    // span the group header full-width
Grid.Options.Grouping.Summary        := True;    // show summary footer rows
Grid.Options.Grouping.SummaryInHeader := True;   // embed summary in the header instead
Grid.Options.Grouping.HideColumns    := True;    // hide the grouped column(s)

Add aggregations

Grid.GroupSum(6);                            // sum the "Kw" column per group
Grid.GroupAverage(5);                        // average the "Cyl" column
Grid.GroupCount(1);                          // count rows per group
Grid.GroupMin(4); Grid.GroupMax(4);          // min/max of "Hp"

Aggregation format

Options.Grouping.CalculationFormat sets the display format string for aggregated values. CalculationFormatType determines whether values are treated as gcfNumber (integer) or gcfFloat (floating-point):

Grid.Options.Grouping.CalculationFormat     := '%.2f';
Grid.Options.Grouping.CalculationFormatType := gcfFloat;

Merged group header delimiter

When multiple columns are grouped, the header text is built by joining the grouped values. Use MergeDelimiter to change the separator character:

Grid.Options.Grouping.MergeDelimiter := ' — ';

Auto-sort on group

When AutoSort is True (the default), the grid automatically sorts the data by the group column(s) before building groups. Set it to False if the data arrives pre-sorted and you want to preserve the original order within groups:

Grid.Options.Grouping.AutoSort := True;   // default

Use the first row as a column header

Grid.Options.Grouping.UseFirstRowAsHeader := True;

Expand and collapse

Grid.ExpandAllNodes;
Grid.CollapseAllNodes;
Grid.ExpandNode(ARow);
Grid.CollapseNode(ARow);

Remove grouping

Grid.UnGroup;

Group with explicit sort direction per level

Grid.Group([col]) sorts each level ascending by default (when AutoSort is True). To specify descending sort, or to sort by a different column than the grouping key, use CreateGroupAndSortInfo to build a TTMSFNCDataGridDataGroupInfo record and pass it to the structured Group overload:

// Group by Country (col 8), sorted descending; nested group by Brand (col 1), sorted ascending
var Info := CreateGroupAndSortInfo(
  [8],                // Level 0: group columns
  [8],                // Level 0: sort columns (can differ from group columns)
  [1],                // Level 1: group columns
  [1]                 // Level 1: sort columns
);
Grid.Group(Info);
Grid.Options.Grouping.AutoSort := False;   // we've already described the sort

When you only need to specify the grouping structure without explicit sort columns, use CreateGroupInfo:

// Two-level grouping: Country → Brand
var Info := CreateGroupInfo([8], [1]);
Grid.Group(Info);

Both helpers also have a three-level overload — pass a third column array pair for Level2.

Interactive (visual) grouping

Visual grouping lets the user build the group structure at runtime by dragging column header chips into a dedicated drop zone above the column headers. Dropping a chip groups by that column; dropping it at a second level nests within the existing group. Each chip has a close button to remove the level and, optionally, a click handler to re-sort.

// Minimum setup to enable the visual grouping drop zone
Grid.Header.Visible := True;
Grid.Header.VisualGrouping.Visible := True;
Grid.Options.Mouse.ColumnDragging  := True;
// ColumnDragModes defaults to [gcdmReorder, gcdmVisualGrouping] — no change needed
Visual grouping Visual grouping (dark theme)

Enable the visual grouping zone

Grid.Header.Visible := True;               // show the header area
Grid.Header.VisualGrouping.Visible := True; // show the drop zone
Grid.Header.VisualGrouping.AutoSize := True; // grow the zone as chips accumulate

Grid.Options.Mouse.ColumnDragging := True; // allow column header dragging
// ColumnDragModes defaults to [gcdmReorder, gcdmVisualGrouping] — no change needed

The user can now drag any column header chip into the zone. Dropping it groups by that column. Dropping a second chip onto the zone at a different vertical position creates a nested group level.

Close and sort chips

Grid.Header.VisualGrouping.CloseIndicator := True;  // show × on each chip
Grid.Header.VisualGrouping.SortOnClick    := True;  // click chip to sort by that column
Grid.Header.VisualGrouping.ConnectionLines := True;  // draw lines linking nested levels

The user can click the × to remove a group level without calling UnGroup from code.

Add header bar action buttons

A button bar (Header.Bar) provides a natural home for Collapse All / Expand All / Ungroup commands:

Grid.Header.Bar.Visible := True;
Grid.Header.Bar.Size    := 35;

var Btn := Grid.Header.Bar.Buttons.Add;
Btn.Text     := 'Collapse';
Btn.Width    := 100;
Btn.Callback := procedure begin Grid.CollapseAllNodes end;

Btn := Grid.Header.Bar.Buttons.Add;
Btn.Text     := 'Expand';
Btn.Width    := 100;
Btn.Callback := procedure begin Grid.ExpandAllNodes end;

Btn := Grid.Header.Bar.Buttons.Add;
Btn.Text     := 'Ungroup';
Btn.Width    := 100;
Btn.Callback := procedure begin Grid.Ungroup end;

Custom group key via OnGetCustomGroup

By default the group chip and header text show the raw cell value. Override this with OnGetCustomGroup to build a bucketed or formatted key — for example age bands from a numeric age column:

procedure TForm1.GridGetCustomGroup(Sender: TObject;
  ACell: TTMSFNCDataGridCellCoord; AData: TTMSFNCDataGridCellValue;
  ALevel: Integer; var AGroup: string);
var
  Age, Decade: Integer;
begin
  if ACell.Column = AgeColumn then
  begin
    Age    := TTMSFNCDataGridData.ValueToInteger(AData);
    Decade := (Age div 10) * 10;
    AGroup := Format('Age %d – %d', [Decade, Decade + 9]);
  end;
end;

AGroup is pre-filled with the default key string; change it to whatever display text you want. The event fires for every cell during Group(), so keep it fast.

Disable drag-to-group while keeping column reorder

ColumnDragModes is a set — remove gcdmVisualGrouping to disable the drop zone without disabling column reorder:

Grid.Options.Mouse.ColumnDragModes := [gcdmReorder];

Control the chips with events

Each grouped column shows as a chip in the visual-grouping zone (the API calls a chip a header group). A set of events lets you control the chip lifecycle: relabel or restyle a chip before it draws, stop the user removing a mandatory grouping level, lock the structure for some roles, and react to chip clicks. Each var ACan… guard is pre-set to the grid's normal behaviour — only override it when you want to change the outcome.

procedure TForm1.WireChipEvents;
begin
  Grid.OnCustomizeHeaderGroup   := GridCustomizeHeaderGroup;
  Grid.OnBeforeCloseHeaderGroup := GridBeforeCloseHeaderGroup;
  Grid.OnCanDragHeaderGroup     := GridCanDragHeaderGroup;
  Grid.OnHeaderGroupClick       := GridHeaderGroupClick;
end;

procedure TForm1.GridCustomizeHeaderGroup(Sender: TObject;
  AHeaderGroup: TTMSFNCDataGridHeaderGroup);
begin
  // Adjust the chip just before it renders: relabel and tint the outer level.
  AHeaderGroup.Text := 'Grouped by: ' + AHeaderGroup.Text;
  if AHeaderGroup.Level = 0 then
    AHeaderGroup.Layout.Fill.Color := $00E8F0FF;
end;

procedure TForm1.GridBeforeCloseHeaderGroup(Sender: TObject; AColumn: Integer;
  var ACanClose: Boolean);
begin
  // Keep the mandatory primary grouping level; the user cannot remove it.
  ACanClose := AColumn <> FPrimaryGroupColumn;
end;

procedure TForm1.GridCanDragHeaderGroup(Sender: TObject; AColumn: Integer;
  var ACanDrag: Boolean);
begin
  // Lock the group structure for non-editor roles.
  ACanDrag := FAllowRegroup;
end;

procedure TForm1.GridHeaderGroupClick(Sender: TObject; AColumn: Integer);
begin
  StatusBar1.SimpleText := Format('Grouped by column %d', [AColumn]);
end;

OnCustomizeHeaderGroup fires for every chip just before it renders, so keep it light; OnBeforeCloseHeaderGroup and OnCanDragHeaderGroup gate the user's close (×) and drag gestures; OnHeaderGroupClick fires when a chip is clicked (for example with SortOnClick enabled).

Per-level appearance with OnGetGroupOptions

OnGetGroupOptions fires once per group level each time Group() runs. It lets you customise the display options for each level independently — for example to give the outer group a taller header and hide its summary row while the inner group uses the defaults.

procedure TForm1.GridGetGroupOptions(Sender: TObject;
  AColumns: TArray<Integer>; ALevel: Integer;
  var AOptions: TTMSFNCDataGridDataGroupOptions);
begin
  case ALevel of
    0: begin
         AOptions.HeaderHeight    := 36;
         AOptions.MergeHeader     := True;
         AOptions.Summary         := False;   // no summary row for the outer level
       end;
    1: begin
         AOptions.HeaderHeight    := 24;
         AOptions.Summary         := True;
         AOptions.SummaryInHeader := True;    // embed summary inside the header
       end;
  end;
end;

Assign the event handler and set Grid.OnGetGroupOptions := GridGetGroupOptions before calling Grid.Group(...). The event fires after each group level is built, so AColumns contains the column index array for that level.

TTMSFNCDataGridDataGroupOptions fields:

Field Effect
AutoSort Sort the level before grouping.
HeaderHeight Height of auto-inserted group header rows (0 = default row height).
HideColumns Hide the grouped column(s) for this level.
MergeDelimiter Separator used when joining multiple group key values in the header text.
MergeHeader Span the group header cell across all columns.
MergeSummary Span the summary cell across all columns.
Summary Add a summary row at the bottom of each group.
SummaryHeight Height of auto-inserted summary rows.
SummaryInHeader Embed summary values inside the header row instead of a separate summary row.
UseFirstRowAsHeader Promote the first data row as the group header.

Custom group headers

Hook OnGetCellLayout for the group rows to customise the look — change the fill colour, font, or render computed text. The grid identifies group cells by their state flag gcsGroup.

procedure TForm1.GridGetCellLayout(Sender: TObject; ACell: TTMSFNCDataGridCell);
var
  G: TTMSFNCDataGrid;
begin
  G := Sender as TTMSFNCDataGrid;
  if G.IsRowNode(ACell.Row) then
  begin
    ACell.Layout.Fill.Kind    := gfkSolid;
    ACell.Layout.Fill.Color   := $00D8EDF8;   // light blue background
    ACell.Layout.Font.Style   := [fsBold];
    ACell.Layout.Font.Color   := $00003A6B;   // dark blue text
    ACell.Layout.Font.Size    := 11;
    ACell.Layout.Stroke.Color := $00A8C8E0;
    ACell.Layout.TextAlign    := gtaLeading;
  end;
end;

To render computed text inside group header cells, handle OnGetCellProperties and modify ACell.Text:

procedure TForm1.GridGetCellProperties(Sender: TObject; ACell: TTMSFNCDataGridCell);
var
  G: TTMSFNCDataGrid;
begin
  G := Sender as TTMSFNCDataGrid;
  if G.IsRowNode(ACell.Row) and (ACell.Column = G.FixedColumnCount) then
    ACell.Text := ACell.Text + ' — click to expand';
end;

Combining grouping, aggregations, visual grouping, and custom appearance

This example wires all grouping features together: two-level programmatic grouping with per-group aggregations, visual drag-to-group enabled, group header styling via OnGetCellLayout, and a button bar for expand/collapse control.

procedure TForm1.FormCreate(Sender: TObject);
begin
  // ---- Two-level programmatic group with aggregations ----
  Grid.Group([CountryColumn, BrandColumn]);  // Country → Brand
  Grid.GroupSum(HpColumn);                  // horsepower total per group
  Grid.GroupAverage(CylColumn);             // average cylinders per group
  Grid.GroupCount(BrandColumn);             // row count per group

  Grid.Options.Grouping.MergeHeader     := True;  // full-width header cell
  Grid.Options.Grouping.Summary         := True;  // show summary footer per group
  Grid.Options.Grouping.HideColumns     := True;  // hide the grouped columns

  // ---- Visual drag-to-group ----
  Grid.Header.Visible                       := True;
  Grid.Header.VisualGrouping.Visible        := True;
  Grid.Header.VisualGrouping.AutoSize       := True;
  Grid.Header.VisualGrouping.CloseIndicator := True;
  Grid.Header.VisualGrouping.SortOnClick    := True;
  Grid.Options.Mouse.ColumnDragging         := True;

  // ---- Action bar: Collapse All / Expand All / Ungroup ----
  Grid.Header.Bar.Visible := True;
  Grid.Header.Bar.Size    := 35;
  with Grid.Header.Bar.Buttons.Add do begin
    Text := 'Collapse All'; Width := 110;
    Callback := procedure begin Grid.CollapseAllNodes end;
  end;
  with Grid.Header.Bar.Buttons.Add do begin
    Text := 'Expand All'; Width := 100;
    Callback := procedure begin Grid.ExpandAllNodes end;
  end;
  with Grid.Header.Bar.Buttons.Add do begin
    Text := 'Ungroup'; Width := 90;
    Callback := procedure begin Grid.Ungroup end;
  end;

  // ---- Custom header appearance via event ----
  Grid.OnGetCellLayout := GridGetCellLayout;
end;

procedure TForm1.GridGetCellLayout(Sender: TObject; ACell: TTMSFNCDataGridCell);
var
  G: TTMSFNCDataGrid;
begin
  G := Sender as TTMSFNCDataGrid;
  if G.IsRowNode(ACell.Row) then
  begin
    ACell.Layout.Fill.Kind  := gfkSolid;
    ACell.Layout.Fill.Color := $00DDEEFF;
    ACell.Layout.Font.Style := [fsBold];
  end
  else if G.IsRowSummary(ACell.Row) then
  begin
    ACell.Layout.Fill.Color := $00EEEEFF;
    ACell.Layout.Font.Style := [fsItalic];
  end;
end;

Inspecting group and summary rows in code

After grouping, use these predicates to distinguish data rows from group rows when iterating:

for var R := Grid.FixedRowCount to Grid.RowCount - 1 do
begin
  if Grid.IsRowNode(R) then
    // this is a collapsible group header row
  else if Grid.IsRowSummary(R) then
    // this is an aggregation footer row (count, sum, …)
  else if Grid.IsRowDisplayed(R) then
    // this is a regular data row currently visible
end;
  • TTMSFNCDataGrid.Group(AColumn) / Group(AColumns: array of Integer) / Group(AGroupInfo: TTMSFNCDataGridDataGroupInfo)
  • CreateGroupInfo(AColumnsLevel0) / CreateGroupInfo(L0, L1) / CreateGroupInfo(L0, L1, L2) — build group structure records
  • CreateGroupAndSortInfo(AColumnsLevel0, ASortColumnsLevel0) / two- and three-level overloads — include sort column lists
  • TTMSFNCDataGrid.UnGroup
  • TTMSFNCDataGrid.IsGrouped — True when grouping is currently applied
  • TTMSFNCDataGrid.GroupSum / GroupAverage / GroupCount / GroupMin / GroupMax
  • TTMSFNCDataGrid.IsRowNode(ARow) — True for collapsible group header rows
  • TTMSFNCDataGrid.IsRowSummary(ARow) — True for aggregation footer rows
  • TTMSFNCDataGrid.IsRowNormal(ARow) — True for plain data rows only (not node, summary, or filter rows)
  • TTMSFNCDataGrid.IsRowDisplayed(ARow) — True for visible data rows
  • TTMSFNCDataGrid.ExpandAllNodes / CollapseAllNodes / ExpandNode / CollapseNode
  • TTMSFNCDataGrid.Options.Grouping.MergeHeader / Summary / SummaryInHeader
  • TTMSFNCDataGrid.Options.Grouping.HideColumns — hide the grouped column(s)
  • TTMSFNCDataGrid.Options.Grouping.AutoSort — sort before grouping
  • TTMSFNCDataGrid.Options.Grouping.MergeDelimiter — separator between joined header values
  • TTMSFNCDataGrid.Options.Grouping.CalculationFormat / CalculationFormatType
  • TTMSFNCDataGrid.Options.Grouping.HeaderHeight / SummaryHeight
  • TTMSFNCDataGrid.Options.Grouping.UseFirstRowAsHeader
  • TTMSFNCDataGrid.Options.Grouping.HideEmptyGroups
  • TTMSFNCDataGridCellAppearance.GroupLayout / SummaryLayout
  • TTMSFNCDataGrid.Header.Visible — show the header area
  • TTMSFNCDataGrid.Header.VisualGrouping.Visible — show the drag-to-group drop zone
  • TTMSFNCDataGrid.Header.VisualGrouping.AutoSize — grow zone automatically as chips accumulate
  • TTMSFNCDataGrid.Header.VisualGrouping.CloseIndicator — show × button on each chip
  • TTMSFNCDataGrid.Header.VisualGrouping.SortOnClick — click chip to sort by that column
  • TTMSFNCDataGrid.Header.VisualGrouping.ConnectionLines / ConnectionStroke
  • TTMSFNCDataGrid.Header.VisualGrouping.Layout — fill/font/stroke of the chip area
  • TTMSFNCDataGrid.Header.VisualGrouping.LevelIndicationFill / LevelActiveIndicationFill
  • TTMSFNCDataGrid.Header.Bar.Visible / Size — toolbar strip below the visual grouping zone
  • TTMSFNCDataGrid.Header.Bar.Buttons.Add — add action buttons to the bar
  • TTMSFNCDataGrid.Options.Mouse.ColumnDragging — enable column header dragging
  • TTMSFNCDataGrid.Options.Mouse.ColumnDragModesgcdmReorder, gcdmVisualGrouping
  • OnGetCustomGroup — override the group key string per cell (bucketing, formatting)
  • OnGetGroupOptions — per-level grouping display options (header height, merge mode, summary visibility)
  • OnCustomizeHeaderGroup — adjust a chip (TTMSFNCDataGridHeaderGroup: Text, Layout, Level) before it renders
  • OnBeforeCloseHeaderGroup — veto removing a grouping level via the chip × (var ACanClose)
  • OnCanDragHeaderGroup — veto dragging a chip to re-group (var ACanDrag)
  • OnHeaderGroupClick — fired when the user clicks a chip in the visual grouping zone

See also

  • Calculations — per-group aggregations together with a grand-total footer row.
  • Sorting — group order follows sort order.
  • Filtering — empty groups can be hidden via Options.Filtering.HideEmptyGroups.
  • Headers, footers & buttons — full reference for Header.Bar and Footer.Bar.
  • Demo: Demo/FMX/DataGrid/Basic/Grouping
  • Demo: Demo/FMX/DataGrid/Advanced/Multi-Column Grouping
  • Demo: Demo/FMX/DataGrid/Advanced/Visual Grouping