Table of Contents

Cells, columns & coordinates

How the grid identifies cells, what fixed and freeze columns mean, and the difference between physical and visible row indices.

Cell coordinates

Every cell has a (Column, Row) pair, both 0-based:

Grid.Cells[0, 0] := 'ID';        // top-left
Grid.Cells[1, 0] := 'Name';
Grid.Cells[2, 0] := 'Country';

Grid.Cells[0, 1] := 1;           // first data row
Grid.Cells[1, 1] := 'John Doe';
Grid.Cells[2, 1] := 'USA';

The grid records use the helper record TTMSFNCDataGridCellCoord:

procedure TForm1.ShowCellCoordinate;
var
  Cell: TTMSFNCDataGridCellCoord;
begin
  Cell := MakeCell(2, 5);          // column 2, row 5
  ShowMessage(Format('%d, %d', [Cell.Column, Cell.Row]));
end;

Cell ranges use TTMSFNCDataGridCellCoordRange:

Grid.Selection := MakeCellRange(0, 1, 3, 4);    // L=0,T=1,R=3,B=4

Fixed columns and rows

Fixed columns and rows are always visible — they don't scroll:

Grid.FixedColumnCount      := 1;     // leftmost column always visible
Grid.FixedRightColumnCount := 1;     // rightmost column always visible
Grid.FixedRowCount         := 1;     // top header row always visible
Grid.FixedBottomRowCount   := 1;     // bottom footer row always visible

Use them for headers, row numbers, and totals.

Freeze columns and rows

Freeze columns and rows behave like fixed ones at first, but they scroll past when the user moves far enough:

Grid.FreezeColumnCount := 2;         // sticky for the first scroll, then scrolls away
Grid.FreezeRowCount    := 1;

Common case: keep the ID and Name columns visible while exploring distant columns; once the user scrolls past them they get out of the way.

Physical vs visible rows

When rows are filtered or hidden, the grid maintains two row numbering systems:

Index What it means
Physical The storage slot (0..RowCount-1). Stable across filtering and hiding.
Visual The sequential rendered position after filter and hidden state are applied. Use RealToVisualRow to obtain it; returns -1 if the row is not rendered.

To find the visual position of a physical row, or to convert back:

var VisualPos := Grid.RealToVisualRow(PhysicalRow);
// VisualPos = -1 → row is filtered or hidden
// VisualPos = n  → it is the n-th rendered row (0-based)

var PhysRow := Grid.VisualToRealRow(VisualPos);

For alternating-row banding use RealToVisualRow inside OnGetCellLayout to determine whether the row occupies an odd or even rendered slot — see Layout priority for a full example.

Hidden vs filtered rows

Both states make a row invisible, but they have different sources:

State API When it's set
Hidden Grid.HiddenRows[ARow] := True Programmatic — you decide.
Filtered Grid.Filter.Add + Grid.ApplyFilter Automatic — based on filter conditions.

Use Grid.IsRowHidden(ARow) to check either case in one call. See the Filtering guide for details.

// Programmatically hide a row
Grid.HiddenRows[5] := True;

// Programmatically show it again
Grid.HiddenRows[5] := False;

// Walk only the rows that are actually displayed (neither hidden nor filtered out)
for var R := Grid.FixedRowCount to Grid.RowCount - 1 do
  if not Grid.IsRowHidden(R) then
    ProcessRow(R);

Hidden vs invisible columns

Same idea on the column axis:

Grid.HideColumn(4);                  // also: Grid.Columns[4].Visible := False
Grid.UnhideColumn(4);
Grid.UnhideAllColumns;

See Column management.

Selection coordinates

Grid.Selection is a TTMSFNCDataGridCellCoordRange covering the user's current selection. Grid.FocusedCell is the single coordinate with keyboard focus inside that range.

When the selection mode allows disjoint selection (e.g. gsmDisjunctRow, gsmDisjunctCellRange), use the indexed properties to enumerate:

for var i := 0 to Grid.RowCount - 1 do
  if Grid.SelectedRows[i] then
    ProcessRow(i);

See Selection for the full picture.

The three coordinate spaces

Every row and column lives in one of three coordinate spaces at any given time:

Space What it reflects Changes when…
Physical Storage index (0..RowCount-1, 0..ColumnCount-1). Rows or columns are added or deleted.
Display Order after sort (rows) and reorder (columns). Fixed rows/columns are excluded and always map 1:1. The user sorts a column or drags a column header.
Visual Display order with filter and hidden state applied. Returns -1 for any row or column that is not rendered. The user filters, hides rows/columns, or scrolls.

The grid always stores and events data in physical coordinates. The conversion functions let you translate between spaces when you need to build display-order lists, respond to visual positions, or iterate only what the user can see.

Physical vs display indices under sort and filter

When sorting or filtering is active the order in which rows appear to the user differs from the physical storage order. Two families of conversion functions handle this:

Function Direction Notes
RealToDisplayRow(ARow) Physical → display slot Returns -1 if the row is filtered out.
DisplayToRealRow(ARow) Display slot → physical The inverse.
RealToDisplayColumn(ACol) Physical → display slot Accounts for column reordering.
DisplayToRealColumn(ACol) Display slot → physical The inverse.
RealToDisplayCell(ACell) Physical → display Delegates to the row and column variants.
DisplayToRealCell(ACell) Display → physical The inverse.
// Convert a physical row index to its current display slot
// (returns -1 when the row is filtered out or hidden)
var DisplaySlot := Grid.RealToDisplayRow(PhysRow);

// Convert a display-order slot back to the physical row index
var PhysRow := Grid.DisplayToRealRow(DisplaySlot);

// Column reordering uses the same pattern
var PhysCol   := Grid.DisplayToRealColumn(VisualColIndex);
var VisualCol := Grid.RealToDisplayColumn(PhysCol);

When you need these

Iterating over visible rows in display order — after sorting, physical row 0 may not be the first visible row. Iterate display slots and convert back to physical when accessing data:

var i: Integer;
begin
  for i := Grid.FixedRowCount to Grid.RowCount - 1 do
  begin
    if Grid.IsRowFiltered(i) then Continue;
    var DisplaySlot := Grid.RealToDisplayRow(i);
    // DisplaySlot is the visual rank (0-based after the header)
  end;
end;

Responding to a cell click and looking up the physical row — the cell coordinate passed to click events is always the physical coordinate, so no conversion is needed for those. Conversion is needed when you build a display-order list yourself:

// Build a list of visible rows in the order the user sees them
var DisplayList: TArray<Integer>;
SetLength(DisplayList, Grid.VisibleRowCount);
for var i := 0 to Grid.VisibleRowCount - 1 do
  DisplayList[i] := Grid.DisplayToRealRow(i + Grid.FixedRowCount);

After filtering: check whether a row is still visible

if Grid.RealToDisplayRow(PhysicalRow) = -1 then
  ShowMessage('Row is filtered out');

These functions were fixed in v1.8.1.1 to return correct indices when a filter is active. Filtered rows consistently return -1 from RealToDisplayRow.

Visual coordinates — the rendered index

Display indices account for sort and reorder but not for filter or hidden state. Use the Visual family when you need the actual rendered index — for instance, to build a zero-based list of only what is drawn on screen, in order:

Function Direction Notes
RealToVisualRow(ARow) Physical → rendered index Returns -1 if the row is filtered or hidden.
VisualToRealRow(AVisualRow) Rendered index → physical Returns -1 if out of range.
RealToVisualColumn(ACol) Physical → rendered index Returns -1 if the column is hidden.
VisualToRealColumn(AVisualCol) Rendered index → physical Returns -1 if out of range.

Display vs Visual — choosing the right one

// Display order: respects sort, but includes filtered-out rows
// Use when iterating for data processing (filter state may change)
for var i := Grid.FixedRowCount to Grid.RowCount - 1 do
begin
  var Phys := Grid.DisplayToRealRow(i);
  ProcessRow(Phys);
end;

// Visual order: only rows actually rendered, in render order
// Use when building an export of exactly what the user sees
for var v := Grid.FixedRowCount to Grid.VisibleRowCount - 1 do
begin
  var Phys := Grid.VisualToRealRow(v);
  if Phys >= 0 then
    ExportRow(Phys);
end;

Checking whether a column is rendered

// Is physical column 4 currently visible to the user?
if Grid.RealToVisualColumn(4) = -1 then
  ShowMessage('Column 4 is hidden or filtered out');

IsRowDisplayed vs IsRowVisible

Two similarly-named predicates test different things:

Method Returns True when…
IsRowDisplayed(ARow) The row passes the current filter and is not hidden. It may be off-screen (scrolled past).
IsRowVisible(ARow) The row is displayed (above) and currently within the visible scroll viewport — i.e., actually rendered on screen right now.

Use IsRowDisplayed to iterate all filterable/visible rows regardless of scroll position (the common case for data processing). Use IsRowVisible to react only to what the user can actually see — for example, refreshing a status bar that summarises only the on-screen rows.

// All rows that survived the filter (anywhere in the dataset)
for var R := Grid.FixedRowCount to Grid.RowCount - 1 do
  if Grid.IsRowDisplayed(R) then
    ProcessRow(R);

// Only the rows currently rendered on screen
for var R := Grid.FixedRowCount to Grid.RowCount - 1 do
  if Grid.IsRowVisible(R) then
    UpdateStatusForRow(R);

Column and cell visibility predicates

The same displayed/visible distinction applies to columns and individual cells:

Method Axis Returns True when…
IsColumnDisplayed(ACol) Column Column is not hidden. May be scrolled off-screen.
IsColumnVisible(ACol) Column Column is displayed and in the scroll viewport.
IsColumnHidden(ACol) Column Column is explicitly hidden.
IsCellDisplayed(ACell) Cell Both the cell's column and row pass IsColumnDisplayed / IsRowDisplayed.
IsCellVisible(ACell) Cell Both the cell's column and row pass IsColumnVisible / IsRowVisible.
IsCellHidden(ACell) Cell Either the cell's column or its row is hidden.
// Iterate all rendered cells in the current column
var C := MakeCell(2, 0);
for var R := Grid.FixedRowCount to Grid.RowCount - 1 do
begin
  C.Row := R;
  if Grid.IsCellDisplayed(C) then
    ProcessCell(C);
end;

HiddenColumns and HiddenRows let you get or set hidden state in bulk:

Grid.HiddenColumns[3] := True;   // hide column 3
Grid.HiddenRows[10]   := True;   // hide row 10

HiddenColumnCount and HiddenRowCount report how many are currently hidden.

Pixel coordinates — XY to cell

All XY… methods accept pixel coordinates relative to the control's client area and return logical cell or area information. They are useful in custom mouse-event handlers.

Method Returns Notes
XYToCell(X, Y) TTMSFNCDataGridCellCoord The cell under the pointer. Column = -1 or Row = -1 if outside the grid.
XYToColumn(X, Y) Integer Column index under X, or -1. Equivalent to XYToCell(X, Y).Column.
XYToRow(X, Y) Integer Row index under Y, or -1. Equivalent to XYToCell(X, Y).Row.
XYToHeader(X, Y) Boolean True if the point is inside the header area.
XYToFooter(X, Y) Boolean True if the point is inside the footer area.
XYToHeaderGroup(X, Y) TTMSFNCDataGridHeaderGroup The header-group element under the pointer.
XYToRenderer(X, Y) TTMSFNCDataGridRenderer The nested renderer (sub-grid) under the pointer, or Self.
procedure TForm1.TMSFNCDataGrid1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  Cell: TTMSFNCDataGridCellCoord;
begin
  Cell := TMSFNCDataGrid1.XYToCell(X, Y);
  if (Cell.Column >= 0) and (Cell.Row >= 0) then
    ShowMessage(Format('Clicked cell (%d, %d)', [Cell.Column, Cell.Row]));
end;

XYToCell returns physical coordinates — the same space that Cells[], events, and all other APIs use. No conversion is needed after calling it.

Cell and column sizing

GetCellSize returns the pixel dimensions of a cell, including any merged span:

var S := Grid.GetCellSize(MakeCell(2, 3));
// S.cx = pixel width  (covers merged columns if the cell spans multiple)
// S.cy = pixel height (covers merged rows)

For individual column widths and row heights, use the indexed properties directly:

Grid.ColumnWidths[2] := 120;   // set column 2 to 120 px
Grid.RowHeights[0]   := 32;    // set header row height

These are the same values the renderer uses when laying out cells. Summing them from column 0 gives you the left edge of any column, and from row 0 gives you the top edge of any row.

GoToCell scrolls the grid so the target cell is visible, then optionally selects it:

Grid.GoToCell(MakeCell(5, 20));             // scroll to cell, keep selection
Grid.GoToCell(MakeCell(5, 20), True);       // scroll and select

When you need to move relative to the current position — skipping hidden or filtered rows and columns — use the NextVisible* / PreviousVisible* helpers. They each return a TTMSFNCDataGridCellCoord:

// Step forward one visible row from the focused cell
var Next := Grid.NextVisibleRow(
  Grid.FocusedCell.Column,
  Grid.FocusedCell.Row
);
if Next.Row >= 0 then
  Grid.GoToCell(Next);
Method Moves
NextVisibleRow(ACol, ARow) Down — next non-hidden, non-filtered row.
PreviousVisibleRow(ACol, ARow) Up — previous non-hidden, non-filtered row.
NextVisibleColumn(ACol, ARow) Right — next non-hidden column.
PreviousVisibleColumn(ACol, ARow) Left — previous non-hidden column.

All four return a cell with Row = -1 or Column = -1 when no further visible row or column exists.

See also