Table of Contents

Custom cells

Take full control of how a cell looks by subclassing TTMSFNCDataGridCell and routing the grid to your class via OnGetCellClass.

Overview

TTMSFNCDataGridCell is the runtime cell object the renderer creates for each visible cell on every paint cycle. It carries the cell's value, layout, state, and rectangle, and exposes a set of Draw... methods (DrawBackground, DrawBorders, DrawText, DrawBitmap, …) that you can override.

Use a custom cell class when:

  • You need owner-drawn rendering (sparkline, star rating, traffic-light badge, …).
  • You need cell-level event handling (mouse-enter highlight, custom hit testing) without writing a per-cell control.
  • A OnGetCellLayout event handler isn't enough — you need a different shape, not just a different colour.

For pure cosmetic changes (font, fill, alignment), prefer Appearance & theming. For embedded live controls (buttons, progress bars), prefer Cell controls.

DataGrid with a colour-coded column and an emphasised cell DataGrid with a colour-coded column and an emphasised cell

Quick example

type
  TStarRatingCell = class(TTMSFNCDataGridCell)
  protected
    procedure DrawText(AGraphics: TTMSFNCGraphics; ARect: TRectF;
      AText: string); override;
  end;

procedure TStarRatingCell.DrawText(AGraphics: TTMSFNCGraphics; ARect: TRectF;
  AText: string);
var
  Stars: Integer;
  Display: string;
begin
  // Render the integer value as that many stars
  if TryStrToInt(AText, Stars) then
    Display := StringOfChar('*', Max(0, Min(5, Stars)))
  else
    Display := AText;
  inherited DrawText(AGraphics, ARect, Display);
end;

procedure TForm1.GridGetCellClass(Sender: TObject; AColumn, ARow: Integer;
  var ACellClass: TTMSFNCDataGridCellClass);
begin
  if AColumn = 4 then               // the rating column
    ACellClass := TStarRatingCell;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Grid.OnGetCellClass := GridGetCellClass;
  Grid.Cells[4, 1] := 5;
  Grid.Cells[4, 2] := 3;
  Grid.Cells[4, 3] := 4;
end;

This example renders integers in a "Rating" column as ***** instead of the literal number. The trick: subclass TTMSFNCDataGridCell, override DrawText, and tell the grid to instantiate your class for that column via OnGetCellClass.

Step by step

1. Subclass TTMSFNCDataGridCell

type
  TBadgeCell = class(TTMSFNCDataGridCell)
  protected
    procedure DrawText(AGraphics: TTMSFNCGraphics); override;
    procedure DrawBackground(AGraphics: TTMSFNCGraphics); override;
  end;

Override any combination of: Draw, DrawBackground, DrawCommentIndicator, DrawContent, DrawControl, DrawFilterButton, DrawSortIndicator, DrawText — or the top-level Draw method that calls them in order.

2. Wire it up

procedure TForm1.GridGetCellClass(Sender: TObject; AColumn, ARow: Integer;
  var ACellClass: TTMSFNCDataGridCellClass);
begin
  if AColumn = RatingColumn then
    ACellClass := TBadgeCell;
end;

The grid creates a fresh instance of your class for every visible cell on every paint. Keep the class lightweight — no expensive constructors.

3. Use the cell's kind and state

Inside the override, check Self.Kind (TTMSFNCDataGridCellKind: gckCell, gckFixedRowCell, gckFixedColumnCell, gckFixedRootCell, …) to distinguish structural cell roles. For selection and focus state, Self.CellState (TTMSFNCDataGridCellState enum: gcsNormal, gcsFocused, gcsFixed, gcsFixedSelected, gcsSelected) tells you the current interaction state.

4. Read the cell value

Self.Data.Value holds the underlying TTMSFNCDataGridCellValue. Convert via the TTMSFNCDataGridData helpers: ValueToString, ValueToInteger, ValueToFloat, etc.

Combining custom cell rendering with per-class selection and data display

This example registers a custom cell class for one column, draws a colored badge based on the cell value, and uses OnGetCellLayout for the remaining columns — showing the two customization layers working side by side:

// Custom cell class: draws a colored priority badge
type
  TPriorityCell = class(TTMSFNCDataGridCell)
  protected
    procedure DrawContent(AGraphics: TTMSFNCGraphics); override;
  end;

procedure TPriorityCell.DrawContent(AGraphics: TTMSFNCGraphics);
var
  Priority: Integer;
  BadgeColor: TAlphaColor;
  ContentRect: TRectF;
begin
  inherited;
  Priority := StrToIntDef(Data.Value.ToString, 0);
  case Priority of
    1: BadgeColor := $FF00AA00;   // green — low
    2: BadgeColor := $FFDDAA00;   // amber — medium
    3: BadgeColor := $FFDD0000;   // red   — high
    else BadgeColor := $FF888888;
  end;
  ContentRect := Rect;
  AGraphics.Fill.Color := BadgeColor;
  AGraphics.DrawEllipse(RectF(ContentRect.Left + 4, ContentRect.Top + 4,
                              ContentRect.Left + 20, ContentRect.Bottom - 4));
end;

// Register the class only for the Priority column (Step by step: OnGetCellClass)
procedure TForm1.GridGetCellClass(Sender: TObject; AColumn, ARow: Integer;
  var ACellClass: TTMSFNCDataGridCellClass);
begin
  if AColumn = PriorityColumn then
    ACellClass := TPriorityCell;
end;

// Light-touch styling for all other columns via OnGetCellLayout
procedure TForm1.GridGetCellLayout(Sender: TObject; ACell: TTMSFNCDataGridCell);
begin
  if (ACell.Row > 0) and (ACell.Column = NameColumn) then
    ACell.Layout.Font.Style := [fsBold];
end;

Where to look in the source

The base cell lives in FMX.TMSFNCDataGridCell.pas. The built-in variants (TTMSFNCDataGridCheckBoxCell, TTMSFNCDataGridRadioButtonCell, TTMSFNCDataGridProgressBarCell, …) in the same file are good models for your own subclasses.

  • TTMSFNCDataGridCell — base class
  • TTMSFNCDataGrid.OnGetCellClass — pick the class per cell/column
  • TTMSFNCDataGrid.OnGetCellLayout — light-touch styling without subclassing
  • Cell.Draw, DrawBackground, DrawText, DrawControl, DrawContent — overridable methods
  • Cell.Kind — structural role (TTMSFNCDataGridCellKind)
  • Cell.CellState — interaction state (TTMSFNCDataGridCellState)
  • Cell.Data.Value — the cell value (TTMSFNCDataGridCellValue)

See also