Custom cells
Take full control of how a cell looks by subclassing
TTMSFNCDataGridCelland routing the grid to your class viaOnGetCellClass.
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
OnGetCellLayoutevent 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.
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.
Related API
TTMSFNCDataGridCell— base classTTMSFNCDataGrid.OnGetCellClass— pick the class per cell/columnTTMSFNCDataGrid.OnGetCellLayout— light-touch styling without subclassingCell.Draw,DrawBackground,DrawText,DrawControl,DrawContent— overridable methodsCell.Kind— structural role (TTMSFNCDataGridCellKind)Cell.CellState— interaction state (TTMSFNCDataGridCellState)Cell.Data.Value— the cell value (TTMSFNCDataGridCellValue)
See also
- Cell controls — embed live controls instead of owner-drawing.
- Appearance & theming — when colour/font is enough.