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.
Navigation helpers
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
- Architecture — which layer manages coordinates.
- Selection — programmatic selection API.
- Column management — fix, freeze, hide, reorder.
- Sorting — sort changes the display order.
- Filtering — filter removes rows from the display view.