Skip to main content

Overview

You can create completely custom layout algorithms for TeeTree by implementing the TChildManager abstract base class. This allows you to define exactly how child nodes are positioned relative to their parents.

Base Class: TChildManager

All child managers inherit from the abstract TChildManager class, which defines the interface for layout algorithms.

Abstract Methods

You must implement these four abstract methods:
CalcXYCross
Function
Signature: Function CalcXYCross(const ANode, AParent: TTreeNodeShape): TPointCalculates the position of the cross-box (expand/collapse control) for the given node.Parameters:
  • ANode - The node for which to calculate cross-box position
  • AParent - The parent node (may be nil for root nodes)
Returns: TPoint with X and Y coordinates for the cross-box
DrawConnection
Function
Signature: Function DrawConnection(const AConnection: TTreeConnection): BooleanDraws the connection line between parent and child nodes.Parameters:
  • AConnection - The connection object to draw
Returns: True to continue drawing nodes, False to skip
XPosition
Function
Signature: Function XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): IntegerCalculates the horizontal (X) position for a child node.Parameters:
  • ANode - The node to position
  • ABrotherIndex - Zero-based index among siblings
Returns: X coordinate in pixels
YPosition
Function
Signature: Function YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): IntegerCalculates the vertical (Y) position for a child node.Parameters:
  • ANode - The node to position
  • ABrotherIndex - Zero-based index among siblings
Returns: Y coordinate in pixels

Creating a Custom Child Manager

Basic Template

type
  TMyCustomAlignChild = class(TChildManager)
  private
    FSpacing: Integer;
  public
    Constructor Create;
    
    Function CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint; override;
    Function DrawConnection(const AConnection: TTreeConnection): Boolean; override;
    Function XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override;
    Function YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override;
    
    property Spacing: Integer read FSpacing write FSpacing default 20;
  end;

implementation

Constructor TMyCustomAlignChild.Create;
begin
  inherited;
  FSpacing := 20;
end;

Function TMyCustomAlignChild.CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint;
begin
  // Calculate cross-box position
  result.X := ANode.X0 - 10;
  result.Y := ANode.Y0 + (ANode.Height div 2);
end;

Function TMyCustomAlignChild.DrawConnection(const AConnection: TTreeConnection): Boolean;
begin
  // Set up connection points and draw
  with AConnection do
  begin
    Style := csLine;
    // Add custom connection point logic here
  end;
  result := True;
end;

Function TMyCustomAlignChild.XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;
begin
  // Calculate X position based on your algorithm
  if Assigned(ANode.Parent) then
    result := ANode.Parent.X0 + FSpacing
  else
    result := ANode.X0; // Root node keeps its position
end;

Function TMyCustomAlignChild.YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;
begin
  // Calculate Y position based on your algorithm  
  if Assigned(ANode.Parent) then
    result := ANode.Parent.Y0 + (ABrotherIndex * FSpacing)
  else
    result := ANode.Y0; // Root node keeps its position
end;

Using Your Custom Manager

var
  MyManager: TMyCustomAlignChild;
begin
  // Free the default manager
  Tree1.GlobalFormat.ChildManager.Free;
  
  // Create and assign your custom manager
  MyManager := TMyCustomAlignChild.Create;
  MyManager.Spacing := 30;
  
  Tree1.GlobalFormat.ChildManager := MyManager;
end;

Example: Diagonal Layout

Here’s a complete example of a diagonal child manager:
type
  TTreeDiagonalAlignChild = class(TChildManager)
  private
    FXOffset: Integer;
    FYOffset: Integer;
  public
    Constructor Create;
    
    Function CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint; override;
    Function DrawConnection(const AConnection: TTreeConnection): Boolean; override;
    Function XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override;
    Function YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override;
    
    property XOffset: Integer read FXOffset write FXOffset default 40;
    property YOffset: Integer read FYOffset write FYOffset default 40;
  end;

implementation

Constructor TTreeDiagonalAlignChild.Create;
begin
  inherited;
  FXOffset := 40;
  FYOffset := 40;
end;

Function TTreeDiagonalAlignChild.CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint;
begin
  result.X := ANode.X0 - 8;
  result.Y := ANode.Y0 + (ANode.Height div 2);
end;

Function TTreeDiagonalAlignChild.DrawConnection(const AConnection: TTreeConnection): Boolean;
begin
  result := True;
  AConnection.Style := csLine; // Simple straight line
end;

Function TTreeDiagonalAlignChild.XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;
begin
  with ANode do
  if Assigned(Parent) then
    // Diagonal: each sibling offset both X and Y
    result := Parent.X1 + FXOffset
  else
    result := X0; // Root
end;

Function TTreeDiagonalAlignChild.YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;
begin
  with ANode do
  if Assigned(Parent) then
    // Diagonal: offset Y based on sibling index
    result := Parent.Y0 + (ABrotherIndex * FYOffset)
  else
    result := Y0; // Root
end;

Visual Result

Root
    \│
     Child 1
         \│
          Child 2
              \│
               Child 3

Example: Grid Layout

A child manager that arranges nodes in a grid:
type
  TTreeGridAlignChild = class(TChildManager)
  private
    FColumns: Integer;
    FSpacingX: Integer;
    FSpacingY: Integer;
  public
    Constructor Create;
    
    Function CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint; override;
    Function DrawConnection(const AConnection: TTreeConnection): Boolean; override;
    Function XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override;
    Function YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override;
    
    property Columns: Integer read FColumns write FColumns default 3;
    property SpacingX: Integer read FSpacingX write FSpacingX default 80;
    property SpacingY: Integer read FSpacingY write FSpacingY default 60;
  end;

implementation

Constructor TTreeGridAlignChild.Create;
begin
  inherited;
  FColumns := 3;
  FSpacingX := 80;
  FSpacingY := 60;
end;

Function TTreeGridAlignChild.XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;
var
  Col: Integer;
begin
  if Assigned(ANode.Parent) then
  begin
    Col := ABrotherIndex mod FColumns;
    result := ANode.Parent.X0 + (Col * FSpacingX);
  end
  else
    result := ANode.X0;
end;

Function TTreeGridAlignChild.YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;
var
  Row: Integer;
begin
  if Assigned(ANode.Parent) then
  begin
    Row := ABrotherIndex div FColumns;
    result := ANode.Parent.Y1 + 20 + (Row * FSpacingY);
  end
  else
    result := ANode.Y0;
end;

Function TTreeGridAlignChild.CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint;
begin
  result.X := ANode.XCenter;
  result.Y := ANode.Y1 + 5;
end;

Function TTreeGridAlignChild.DrawConnection(const AConnection: TTreeConnection): Boolean;
begin
  result := True;
  AConnection.Style := csLine;
end;

Visual Result

         Parent

    ┌──────┼──────┐
    │      │      │
  Child1 Child2 Child3
    │      │      │
  Child4 Child5 Child6

Built-in Helper Classes

Several built-in classes provide useful foundations:

TTreeSideAlignChild

Base class with common properties for side-based layouts:
TTreeSideAlignChild = class(TChildManager)
public
  property CrossMargin: Integer;
  property HorizMargin: Integer;
  property ToShapeOffset: Integer;
  property VertMargin: Integer;
end;
Used by TTreeTopBottomAlignChild and TTreeLeftRightAlignChild.

Tips for Custom Layouts

Access node properties like ANode.Parent, ANode.Count, ANode.Children[i] to make layout decisions based on tree structure.
Use ANode.Tree.ChartBounds to access the tree’s drawing area boundaries.
For complex connection drawing, use AConnection.Points.Add() to create multi-segment paths.
Always check if ANode.Parent is assigned before accessing parent properties, as root nodes have no parent.

Connection Drawing Techniques

Simple Line Connection

Function TMyManager.DrawConnection(const AConnection: TTreeConnection): Boolean;
begin
  result := True;
  AConnection.Style := csLine;
  // TeeTree draws a line from FromShape to ToShape
end;

Custom Connection Points

Function TMyManager.DrawConnection(const AConnection: TTreeConnection): Boolean;
begin
  result := True;
  
  with AConnection do
  begin
    Style := csLine;
    
    with Points do
    begin
      Clear;
      // Start point: bottom of parent
      Add(cpsFromPercent, 50, cpsFromPercent, 100);
      // Midpoint
      Add(cpsPrevious, 0, cpsPrevious, 20);
      // End point: top of child
      Add(cpsToPercent, 50, cpsToRel, -10);
    end;
  end;
end;

Testing Your Custom Manager

procedure TestCustomManager;
var
  Root, Child: TTreeNodeShape;
begin
  // Apply your manager
  Tree1.GlobalFormat.ChildManager.Free;
  Tree1.GlobalFormat.ChildManager := TMyCustomAlignChild.Create;
  
  // Create test nodes
  Root := Tree1.AddRoot('Root');
  Root.Expanded := True;
  
  Child := Root.AddChild('Child 1');
  Root.AddChild('Child 2');
  Root.AddChild('Child 3');
  
  // Test nested levels
  Child.Expanded := True;
  Child.AddChild('Grandchild 1');
  Child.AddChild('Grandchild 2');
  
  // Force refresh
  Tree1.Invalidate;
end;

Build docs developers (and LLMs) love