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;
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;
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.
Related API
TTMSFNCTreeViewTTMSFNCTreeViewData—TTMSFNCTreeViewNode,TTMSFNCTreeViewVirtualNode
See also
- Nodes — fixed vs variable height, checkboxes, extended nodes
- Interaction — editing, filtering, sorting