Skip to main content

Overview

Virtual mode, also called “on-demand node creation,” allows you to create child nodes only when a parent node is expanded for the first time. This approach dramatically improves performance when working with large tree hierarchies by deferring node creation until absolutely necessary.
Virtual mode is ideal for:
  • Large datasets (thousands of nodes)
  • Database-driven trees
  • File system browsers
  • Any hierarchical data where children are expensive to load

How It Works

Instead of creating all nodes upfront, you:
  1. Create only the root level nodes initially
  2. Set ShowCross to scAlways so expand/collapse icons appear
  3. Listen to the OnExpandingCollapsing event
  4. Create child nodes dynamically when a node is first expanded
1

Initialize Root Nodes

Create only the top-level nodes when your tree loads:
// Add first level "Root" example nodes only
for t := 1 to 10 do
    Tree1.Add('Root ' + IntToStr(t));
2

Configure ShowCross Property

Set nodes to always show the expand/collapse box, even when they have no children yet:
// Important: Set ShowCross property to scAlways
// so the +/- box is always visible
for t := 0 to Tree1.Roots.Count-1 do
    Tree1.Roots[t].ShowCross := scAlways;
Without setting ShowCross := scAlways, users won’t see any indication that nodes can be expanded.
3

Implement OnExpandingCollapsing Event

Create children only when a node is expanded for the first time:
procedure TForm1.Tree1ExpandingCollapsing(
  Sender: TTreeNodeShape; var Expand: Boolean);
var
  t: Integer;
  NumChildren: Integer;
begin
  if Expand then
    if Sender.Children.Count = 0 then  // First time expanded!
    begin
      // Add children nodes
      NumChildren := 1 + Random(5);  // How many? (random example)
      
      for t := 1 to NumChildren do
        Sender.AddChild('Child ' + IntToStr(t));
      
      // Set ShowCross for children too
      for t := 1 to NumChildren do
        Sender.Children[t-1].ShowCross := scAlways;
    end;
end;

Complete Example

Here’s a full working example from the TeeTree source:
unit CreateOnDemand;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  TeeTree, ExtCtrls, StdCtrls;

type
  TCreateOnDemandForm = class(TForm)
    Tree1: TTree;
    procedure FormCreate(Sender: TObject);
    procedure Tree1ExpandingCollapsing(Sender: TTreeNodeShape;
      var Expand: Boolean);
  end;

implementation

procedure TCreateOnDemandForm.FormCreate(Sender: TObject);
var
  t: Integer;
begin
  // Add first level "Root" example nodes only
  for t := 1 to 10 do
      Tree1.Add('Root ' + IntToStr(t));
  
  // Set nodes ShowCross property to "scAlways"
  for t := 0 to Tree1.Roots.Count-1 do
      Tree1.Roots[t].ShowCross := scAlways;
  
  // Remove border
  Tree1.GlobalFormat.Border.Visible := False;
end;

procedure TCreateOnDemandForm.Tree1ExpandingCollapsing(
  Sender: TTreeNodeShape; var Expand: Boolean);
var
  t: Integer;
  NumChildren: Integer;
begin
  if Expand then
    if Sender.Children.Count = 0 then  // Very first time expanded!
    begin
      // Add children nodes
      NumChildren := 1 + Random(5);
      
      for t := 1 to NumChildren do
        Sender.AddChild('Child ' + IntToStr(t));
      
      // Set ShowCross for children
      for t := 1 to NumChildren do
        Sender.Children[t-1].ShowCross := scAlways;
    end;
end;

end.

Database Integration

Virtual mode is perfect for database-driven trees:
procedure TForm1.TreeExpandingCollapsing(
  Sender: TTreeNodeShape; var Expand: Boolean);
var
  Query: TQuery;
  ParentID: Integer;
begin
  if Expand and (Sender.Children.Count = 0) then
  begin
    ParentID := Integer(Sender.Data);  // Store DB ID in node Data
    
    Query := TQuery.Create(nil);
    try
      Query.SQL.Text := 'SELECT * FROM Items WHERE ParentID = :PID';
      Query.Params.ParamByName('PID').AsInteger := ParentID;
      Query.Open;
      
      while not Query.Eof do
      begin
        with Sender.AddChild(Query.FieldByName('Name').AsString) do
        begin
          Data := Pointer(Query.FieldByName('ID').AsInteger);
          ShowCross := scAlways;  // May have children
        end;
        Query.Next;
      end;
    finally
      Query.Free;
    end;
  end;
end;

ShowCross Options

The ShowCross property controls when the expand/collapse box appears:
ValueDescription
scNeverNever show expand/collapse box
scAlwaysAlways show the box (required for virtual mode)
scAutoShow only when node has children (default)

Performance Benefits

Creating 10,000 nodes upfront:
// Creates all 10,000 nodes immediately
for i := 1 to 100 do
begin
  Root := Tree1.Add('Root ' + IntToStr(i));
  for j := 1 to 100 do
    Root.AddChild('Child ' + IntToStr(j));
end;
// Result: Slow startup, high memory usage

Advanced: Canceling Expansion

You can prevent a node from expanding by setting the Expand parameter to False:
procedure TForm1.TreeExpandingCollapsing(
  Sender: TTreeNodeShape; var Expand: Boolean);
begin
  if Expand then
  begin
    // Check if user has permission
    if not UserHasPermission(Sender) then
    begin
      ShowMessage('Access denied');
      Expand := False;  // Cancel the expansion
      Exit;
    end;
    
    // Create children...
  end;
end;

Best Practices

Check Children Count

Always check Sender.Children.Count = 0 to avoid creating duplicates when re-expanding.

Set ShowCross

Always set ShowCross := scAlways for nodes that may have children.

Store Node Data

Use the Data property to store database IDs or other context needed to load children.

Handle Errors

Wrap database queries in try-finally blocks to ensure proper cleanup.
  • OnExpandingCollapsing - Fired when a node is about to expand or collapse (see TreeEd.pas:2227)
  • OnClickShape - Fired when a node is clicked
  • OnSelectShape - Fired when a node is selected
The virtual mode pattern is demonstrated in the TeeTree demo application under “Create On Demand” example.

Build docs developers (and LLMs) love