Modes
TTMSFNCTreeView supports two independent data modes that determine how node content is supplied. Choose the mode that fits your data size and update frequency; both expose the same rendering and interaction features.
| Mode | When to use |
|---|---|
| Collection-based | Default. Nodes are stored in the Nodes collection. Best for moderate data sets where node objects need to persist between interactions. |
| Virtual | Node data is returned on demand via OnGetNumberOfNodes and OnGetNodeText events. No collection is maintained. Best for large or dynamically computed data sets (millions of nodes). |
Virtual mode
The tree retrieves values by firing events. The minimum implementation is two events.
A minimum virtual-mode tree wires OnGetNumberOfNodes and OnGetNodeText against the two columns declared in FormCreate:
procedure TForm1.FormCreate(Sender: TObject);
begin
TreeView1.BeginUpdate;
TreeView1.ClearNodeList;
TreeView1.ClearNodes;
TreeView1.ClearColumns;
TreeView1.Columns.Add.Text := 'Column 1';
TreeView1.Columns.Add.Text := 'Column 2';
TreeView1.EndUpdate;
end;
procedure TForm1.TreeView1GetNumberOfNodes(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; var ANumberOfNodes: Integer);
begin
if ANode.Level = -1 then
ANumberOfNodes := 3
else if (ANode.Level = 0) and (ANode.Index = 0) then
ANumberOfNodes := 5;
end;
procedure TForm1.TreeView1GetNodeText(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; AColumn: Integer;
AMode: TTMSFNCTreeViewNodeTextMode; var AText: string);
begin
if ANode.Level = 0 then
AText := 'Node ' + IntToStr(ANode.Index) + ' for ' + TreeView1.Columns[AColumn].Text
else
AText := 'Child Node ' + IntToStr(ANode.Index);
end;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
TreeView1->BeginUpdate();
TreeView1->ClearNodeList();
TreeView1->ClearNodes();
TreeView1->ClearColumns();
TreeView1->Columns->Add()->Text = "Column 1";
TreeView1->Columns->Add()->Text = "Column 2";
TreeView1->EndUpdate();
}
void __fastcall TForm1::TreeView1GetNumberOfNodes(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int &ANumberOfNodes)
{
if (ANode->Level == -1)
ANumberOfNodes = 3;
else if ((ANode->Level == 0) && (ANode->Index == 0))
ANumberOfNodes = 5;
}
void __fastcall TForm1::TreeView1GetNodeText(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int AColumn,
TTMSFNCTreeViewNodeTextMode AMode, UnicodeString &AText)
{
if (ANode->Level == 0)
AText = "Node " + IntToStr(ANode->Index) + " for " + TreeView1->Columns->Items[AColumn]->Text;
else
AText = "Child Node " + IntToStr(ANode->Index);
}
OnGetNumberOfNodes
This event fires once per parent level during tree construction. Use ANode.Level to identify the level:
- Level
-1— root level (first display). - Level
0and above — child level for the node at that depth.
procedure TForm1.TreeView1GetNumberOfNodes(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; var ANumberOfNodes: Integer);
begin
if ANode.Level = -1 then
ANumberOfNodes := 3
else if (ANode.Level = 0) and (ANode.Index = 0) then
ANumberOfNodes := 5;
end;
void __fastcall TForm1::TreeView1GetNumberOfNodes(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int &ANumberOfNodes)
{
if (ANode->Level == -1)
ANumberOfNodes = 3;
else if ((ANode->Level == 0) && (ANode->Index == 0))
ANumberOfNodes = 5;
}
OnGetNodeText
Returns the text for each node column:
procedure TForm1.TreeView1GetNodeText(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; AColumn: Integer;
AMode: TTMSFNCTreeViewNodeTextMode; var AText: string);
begin
if ANode.Level = 0 then
AText := 'Node ' + IntToStr(ANode.Index) + ' for ' + TreeView1.Columns[AColumn].Text
else
AText := 'Child Node ' + IntToStr(ANode.Index);
end;
void __fastcall TForm1::TreeView1GetNodeText(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int AColumn,
TTMSFNCTreeViewNodeTextMode AMode, UnicodeString &AText)
{
if (ANode->Level == 0)
AText = "Node " + IntToStr(ANode->Index) + " for " + TreeView1->Columns->Items[AColumn]->Text;
else
AText = "Child Node " + IntToStr(ANode->Index);
}
Virtual node identity
Each virtual node carries:
| Property | Type | Description |
|---|---|---|
Index |
Integer |
Position relative to its parent |
Row |
Integer |
Global row index in the tree |
Level |
Integer |
Depth; -1 = root level, 0+ = child depths |
Children |
Integer |
Direct child count |
TotalChildren |
Integer |
Recursive child count |
Virtual tree methods
All virtual-mode operations use the Virtual suffix:
uses
FMX.TMSFNCTreeView;
procedure TForm1.VirtualMethodExamples;
var
pn, n: TTMSFNCTreeViewVirtualNode;
begin
// Add and expand
pn := TreeView1.AddVirtualNode;
n := TreeView1.AddVirtualNode(pn); // add as child of pn
TreeView1.ExpandVirtualNode(pn);
// Remove
TreeView1.RemoveVirtualNode(TreeView1.FocusedVirtualNode);
// Expand / collapse all
TreeView1.ExpandAllVirtual;
TreeView1.CollapseAllVirtual;
end;
void __fastcall TForm1::VirtualMethodExamples()
{
// Add and expand
TTMSFNCTreeViewVirtualNode *pn = TreeView1->AddVirtualNode();
TTMSFNCTreeViewVirtualNode *n = TreeView1->AddVirtualNode(pn); // add as child of pn
TreeView1->ExpandVirtualNode(pn);
// Remove
TreeView1->RemoveVirtualNode(TreeView1->FocusedVirtualNode);
// Expand / collapse all
TreeView1->ExpandAllVirtual();
TreeView1->CollapseAllVirtual();
}
Collection-based mode
Nodes are stored in the Nodes collection. Each TTMSFNCTreeViewNode holds text, icons, check states, and an Extended flag per column.
procedure TForm1.PopulateTree;
var
pn, childn: TTMSFNCTreeViewNode;
I, C: Integer;
begin
TreeView1.BeginUpdate;
TreeView1.ClearNodeList;
TreeView1.ClearNodes;
TreeView1.ClearColumns;
TreeView1.Columns.Add.Text := 'Column 1';
TreeView1.Columns.Add.Text := 'Column 2';
for I := 0 to 2 do
begin
pn := TreeView1.AddNode;
for C := 0 to TreeView1.Columns.Count - 1 do
pn.Text[C] := 'Node ' + IntToStr(I) + ' for ' + TreeView1.Columns[C].Text;
if I = 0 then
begin
for C := 0 to 4 do
begin
childn := TreeView1.AddNode(pn);
childn.Text[0] := 'Child Node ' + IntToStr(C);
end;
end;
end;
TreeView1.EndUpdate;
end;
void __fastcall TForm1::PopulateTree()
{
TreeView1->BeginUpdate();
TreeView1->ClearNodeList();
TreeView1->ClearNodes();
TreeView1->ClearColumns();
TreeView1->Columns->Add()->Text = "Column 1";
TreeView1->Columns->Add()->Text = "Column 2";
for (int I = 0; I < 3; I++)
{
TTMSFNCTreeViewNode *pn = TreeView1->AddNode();
for (int C = 0; C < TreeView1->Columns->Count; C++)
pn->Text[C] = "Node " + IntToStr(I) + " for " + TreeView1->Columns->Items[C]->Text;
if (I == 0)
{
for (int C = 0; C < 5; C++)
{
TTMSFNCTreeViewNode *childn = TreeView1->AddNode(pn);
childn->Text[0] = "Child Node " + IntToStr(C);
}
}
}
TreeView1->EndUpdate();
}
Collection node properties
| Property | Description |
|---|---|
Text[AColumn] |
Text value for the given column |
CheckTypes[AColumn] |
tvntCheckBox or tvntRadioButton |
Checked[AColumn] |
Check state |
Extended |
When True, node spans all columns |
VirtualNode |
Reference to the underlying TTMSFNCTreeViewVirtualNode |
Collection tree methods
uses
FMX.TMSFNCTreeView;
procedure TForm1.CollectionMethodExamples;
var
pn, n: TTMSFNCTreeViewNode;
begin
// Add root and child nodes
pn := TreeView1.AddNode;
pn.Text[0] := 'Parent';
n := TreeView1.AddNode(pn); // child of pn
n.Text[0] := 'Child';
// Expand and collapse
TreeView1.ExpandNode(pn);
TreeView1.ExpandAll;
TreeView1.CollapseAll;
// Remove (either form)
TreeView1.RemoveNode(TreeView1.FocusedNode);
TreeView1.FocusedNode.Free;
end;
void __fastcall TForm1::CollectionMethodExamples()
{
// Add root and child nodes
TTMSFNCTreeViewNode *pn = TreeView1->AddNode();
pn->Text[0] = "Parent";
TTMSFNCTreeViewNode *n = TreeView1->AddNode(pn); // child of pn
n->Text[0] = "Child";
// Expand and collapse
TreeView1->ExpandNode(pn);
TreeView1->ExpandAll();
TreeView1->CollapseAll();
// Remove (either form)
TreeView1->RemoveNode(TreeView1->FocusedNode);
delete TreeView1->FocusedNode;
}
Switching between modes
You cannot mix virtual and collection-based operations in the same tree. If you use AddVirtualNode, call RemoveVirtualNode and the other *Virtual methods throughout. The same applies for the collection API. When using collection-based nodes and responding to events, the ANode.Node property on the virtual node gives back the collection item; the reverse direction is TTMSFNCTreeViewNode.VirtualNode.
Related events
Virtual mode events for customisation beyond text:
| Event | When to implement |
|---|---|
OnGetNumberOfNodes |
Always — defines tree structure |
OnGetNodeText |
Always — provides display text |
OnGetNodeIcon |
Node icons per column |
OnGetNodeCheckType |
Per-node checkbox/radiobutton type |
OnIsNodeExpanded |
Initial expand/collapse state |
OnIsNodeEnabled |
Disabled nodes |
OnGetNodeHeight |
Variable height (HeightMode = tnhmVariable) |
OnGetNumberOfNodes and OnGetNodeText are the two mandatory events; the rest
are optional refinements that the tree consults while it builds each visible
row. Because they are called during layout, keep the handlers cheap and free of
side effects — they run again on every repaint, expand, and scroll.
uses
FMX.TMSFNCTypes, FMX.TMSFNCBitmapContainer, FMX.TMSFNCTreeViewData,
FMX.TMSFNCCustomTreeView, FMX.TMSFNCTreeView;
procedure TForm1.ConfigureVirtualNodeAppearance;
begin
// Icons are resolved from a bitmap container, and variable height must be
// switched on before OnGetNodeHeight is consulted.
TreeView1.BitmapContainer := TMSFNCBitmapContainer1;
TreeView1.NodesAppearance.HeightMode := tnhmVariable;
TreeView1.OnGetNodeIcon := TreeView1GetNodeIcon;
TreeView1.OnGetNodeCheckType := TreeView1GetNodeCheckType;
TreeView1.OnIsNodeExpanded := TreeView1IsNodeExpanded;
TreeView1.OnIsNodeEnabled := TreeView1IsNodeEnabled;
TreeView1.OnGetNodeHeight := TreeView1GetNodeHeight;
end;
// Group rows carry a folder icon in the first column only.
procedure TForm1.TreeView1GetNodeIcon(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; AColumn: Integer; ALarge: Boolean;
var AIcon: TTMSFNCBitmap);
begin
if (AColumn = 0) and (ANode.Level = 0) then
AIcon := TMSFNCBitmapContainer1.FindBitmap('folder');
end;
// Only leaf rows can be ticked; groups show no check control.
procedure TForm1.TreeView1GetNodeCheckType(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; AColumn: Integer;
var ACheckType: TTMSFNCTreeViewNodeCheckType);
begin
if (AColumn = 0) and (ANode.Level > 0) then
ACheckType := tvntCheckBox
else
ACheckType := tvntNone;
end;
// The first group opens automatically the first time the tree is shown.
procedure TForm1.TreeView1IsNodeExpanded(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; var AExpanded: Boolean);
begin
AExpanded := (ANode.Level = 0) and (ANode.Index = 0);
end;
// Detail rows below the second level are shown but not interactive.
procedure TForm1.TreeView1IsNodeEnabled(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; var AEnabled: Boolean);
begin
AEnabled := ANode.Level < 2;
end;
// Group rows are taller than their children.
procedure TForm1.TreeView1GetNodeHeight(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; AColumn: Integer; var AHeight: Double);
begin
if ANode.Level = 0 then
AHeight := 36
else
AHeight := 24;
end;
void __fastcall TForm1::ConfigureVirtualNodeAppearance()
{
// Icons are resolved from a bitmap container, and variable height must be
// switched on before OnGetNodeHeight is consulted.
TreeView1->BitmapContainer = TMSFNCBitmapContainer1;
TreeView1->NodesAppearance->HeightMode = tnhmVariable;
TreeView1->OnGetNodeIcon = TreeView1GetNodeIcon;
TreeView1->OnGetNodeCheckType = TreeView1GetNodeCheckType;
TreeView1->OnIsNodeExpanded = TreeView1IsNodeExpanded;
TreeView1->OnIsNodeEnabled = TreeView1IsNodeEnabled;
TreeView1->OnGetNodeHeight = TreeView1GetNodeHeight;
}
// Group rows carry a folder icon in the first column only.
void __fastcall TForm1::TreeView1GetNodeIcon(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int AColumn, bool ALarge,
TTMSFNCBitmap *&AIcon)
{
if ((AColumn == 0) && (ANode->Level == 0))
AIcon = TMSFNCBitmapContainer1->FindBitmap("folder");
}
// Only leaf rows can be ticked; groups show no check control.
void __fastcall TForm1::TreeView1GetNodeCheckType(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int AColumn,
TTMSFNCTreeViewNodeCheckType &ACheckType)
{
if ((AColumn == 0) && (ANode->Level > 0))
ACheckType = tvntCheckBox;
else
ACheckType = tvntNone;
}
// The first group opens automatically the first time the tree is shown.
void __fastcall TForm1::TreeView1IsNodeExpanded(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, bool &AExpanded)
{
AExpanded = (ANode->Level == 0) && (ANode->Index == 0);
}
// Detail rows below the second level are shown but not interactive.
void __fastcall TForm1::TreeView1IsNodeEnabled(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, bool &AEnabled)
{
AEnabled = ANode->Level < 2;
}
// Group rows are taller than their children.
void __fastcall TForm1::TreeView1GetNodeHeight(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int AColumn, double &AHeight)
{
if (ANode->Level == 0)
AHeight = 36;
else
AHeight = 24;
}
Two ordering constraints are worth remembering: NodesAppearance.HeightMode
must be tnhmVariable before OnGetNodeHeight has any effect, and
BitmapContainer must be assigned before OnGetNodeIcon can resolve a bitmap
by name. OnIsNodeExpanded supplies the initial state only — once the user
expands or collapses a node, the tree keeps its own state and no longer asks.
Combining virtual events and methods
A virtual tree usually needs more than one event to behave well. OnGetNumberOfNodes defines the structure, OnGetNodeText supplies the content from the node identity (Level, Index and Children), and OnIsNodeExtended can turn a node into a section header. Virtual methods such as ExpandAllVirtual then act on the resulting structure. Use this combination when the tree is computed on demand, for instance a grouped report where the number of items per group is derived from the group itself, and the reader should see every level expanded when the tree first appears.
uses
System.SysUtils, FMX.TMSFNCCustomTreeView, FMX.TMSFNCTreeViewData,
FMX.TMSFNCTreeView;
procedure TForm1.SetupCombinedVirtualTree;
begin
TreeView1.BeginUpdate;
TreeView1.ClearNodeList;
TreeView1.ClearNodes;
TreeView1.ClearColumns;
TreeView1.Columns.Add.Text := 'Name';
TreeView1.Columns.Add.Text := 'Children';
TreeView1.OnGetNumberOfNodes := TreeView1GetNumberOfNodesCombined;
TreeView1.OnGetNodeText := TreeView1GetNodeTextCombined;
TreeView1.OnIsNodeExtended := TreeView1IsNodeExtendedCombined;
TreeView1.EndUpdate;
// Virtual-mode method: expand every node once the structure is known
TreeView1.ExpandAllVirtual;
end;
procedure TForm1.TreeView1GetNumberOfNodesCombined(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; var ANumberOfNodes: Integer);
begin
if ANode.Level = -1 then
ANumberOfNodes := 3
else if ANode.Level = 0 then
ANumberOfNodes := ANode.Index + 2;
end;
procedure TForm1.TreeView1GetNodeTextCombined(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; AColumn: Integer;
AMode: TTMSFNCTreeViewNodeTextMode; var AText: string);
begin
if ANode.Level = 0 then
begin
if AColumn = 0 then
AText := 'Group ' + IntToStr(ANode.Index)
else
AText := IntToStr(ANode.Children);
end
else if AColumn = 0 then
AText := 'Item ' + IntToStr(ANode.Index);
end;
// The first group acts as a section header spanning all columns
procedure TForm1.TreeView1IsNodeExtendedCombined(Sender: TObject;
ANode: TTMSFNCTreeViewVirtualNode; var AExtended: Boolean);
begin
AExtended := (ANode.Level = 0) and (ANode.Index = 0);
end;
void __fastcall TForm1::SetupCombinedVirtualTree()
{
TreeView1->BeginUpdate();
TreeView1->ClearNodeList();
TreeView1->ClearNodes();
TreeView1->ClearColumns();
TreeView1->Columns->Add()->Text = "Name";
TreeView1->Columns->Add()->Text = "Children";
TreeView1->OnGetNumberOfNodes = TreeView1GetNumberOfNodesCombined;
TreeView1->OnGetNodeText = TreeView1GetNodeTextCombined;
TreeView1->OnIsNodeExtended = TreeView1IsNodeExtendedCombined;
TreeView1->EndUpdate();
// Virtual-mode method: expand every node once the structure is known
TreeView1->ExpandAllVirtual();
}
void __fastcall TForm1::TreeView1GetNumberOfNodesCombined(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int &ANumberOfNodes)
{
if (ANode->Level == -1)
ANumberOfNodes = 3;
else if (ANode->Level == 0)
ANumberOfNodes = ANode->Index + 2;
}
void __fastcall TForm1::TreeView1GetNodeTextCombined(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, int AColumn,
TTMSFNCTreeViewNodeTextMode AMode, UnicodeString &AText)
{
if (ANode->Level == 0)
{
if (AColumn == 0)
AText = "Group " + IntToStr(ANode->Index);
else
AText = IntToStr(ANode->Children);
}
else if (AColumn == 0)
AText = "Item " + IntToStr(ANode->Index);
}
// The first group acts as a section header spanning all columns
void __fastcall TForm1::TreeView1IsNodeExtendedCombined(TObject *Sender,
TTMSFNCTreeViewVirtualNode *ANode, bool &AExtended)
{
AExtended = (ANode->Level == 0) && (ANode->Index == 0);
}
Related API
TTMSFNCTreeViewTTMSFNCTreeViewData—TTMSFNCTreeViewNode,TTMSFNCTreeViewVirtualNode
See also
- Nodes — fixed vs variable height, checkboxes, extended nodes
- Interaction — editing, filtering, sorting