Understanding automatic layout management with child managers in TeeTree
Child managers are classes that control the automatic positioning of nodes and the drawing of connections in TeeTree. They implement different layout algorithms for organizing hierarchical structures.
The TChildManager base class and its descendants determine how parent and children nodes are arranged on screen when nodes have their AutoPosition property enabled.
// Set the global child managerTree.GlobalFormat.ChildManager := TTreeExplorerAlignChild.Create;
Child managers are assigned to Tree.GlobalFormat.ChildManager and control the layout for all auto-positioned nodes in the tree.
The TChildManager class provides the foundation for all layout algorithms:
type TChildManager = class(TPersistent) public // Calculate X position for a node function XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; virtual; abstract; // Calculate Y position for a node function YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; virtual; abstract; // Calculate CrossBox position function CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint; virtual; abstract; // Draw connection between nodes function DrawConnection(const AConnection: TTreeConnection): Boolean; virtual; end;
All child manager classes must implement XPosition, YPosition, and CalcXYCross methods to define their layout algorithm.
property HorizMargin: Integer; // Horizontal spacing between parent and childproperty VertMargin: Integer; // Vertical spacing between siblingsproperty CrossMargin: Integer; // Margin around CrossBoxproperty ArrowToAngle: Integer; // Arrow angle (default: 0)
Layout Behavior
Children are positioned to the right of their parent
Siblings are stacked vertically
CrossBox appears to the left of nodes with children
Connections use multi-segment paths (csSides style)
Example Usage
with TTreeExplorerAlignChild(Tree.GlobalFormat.ChildManager) dobegin HorizMargin := 25; // More space between levels VertMargin := 5; // More space between siblings CrossMargin := 8; // Larger CrossBox marginend;
function TTreeTopBottomAlignChild.DrawConnection( const AConnection: TTreeConnection): Boolean;begin // Creates 4-point connection path: // 1. Start at bottom-center of parent (50%, 100%) // 2. Move down by VertMargin/2 // 3. Move horizontally to child center // 4. Move down to child top with AConnection.Points do begin Add(cpsFromPercent, 50, cpsFromPercent, 100); Add(cpsPrevious, 0, cpsPrevious, VertMargin div 2); Add(cpsToPercent, 50, cpsPrevious, 0); Add(cpsPrevious, 0, cpsToRel, ToShapeOffset); end;end;
// Nodes are positioned using trigonometryfunction TTreeCircularAlignChild.AngleNode( const ANode: TTreeNodeShape): Double;begin // Calculate angle for this node Result := TeePiStep * (AngleOffset + (TotalAngle * ANode.BrotherIndex / ANode.Parent.Count));end;// X position: parent center + radius * sin(angle)// Y position: parent center + radius * cos(angle)
Circular layouts work best with a small number of children (typically under 12) to avoid overlap.
You can create custom layout algorithms by deriving from TChildManager:
type TMyCustomManager = class(TChildManager) public function XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override; function YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer; override; function CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint; override; function DrawConnection(const AConnection: TTreeConnection): Boolean; override; end;implementationfunction TMyCustomManager.XPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;begin if Assigned(ANode.Parent) then // Calculate X based on parent position Result := ANode.Parent.X1 + 50 else // Root node Result := ABrotherIndex * 100;end;function TMyCustomManager.YPosition(const ANode: TTreeNodeShape; ABrotherIndex: Integer): Integer;begin if Assigned(ANode.Parent) then // Calculate Y based on siblings Result := ANode.Parent.Y0 + (ABrotherIndex * 30) else Result := 10;end;function TMyCustomManager.CalcXYCross(const ANode, AParent: TTreeNodeShape): TPoint;begin // Position CrossBox to the left of node Result.X := ANode.X0 - 20; Result.Y := ANode.YCenter;end;function TMyCustomManager.DrawConnection( const AConnection: TTreeConnection): Boolean;begin Result := True; AConnection.Style := csLine; // Simple straight lineend;
Always call Tree.Invalidate after changing child manager properties to refresh the layout.
// Global applicationTree.GlobalFormat.ChildManager := TTreeTopBottomAlignChild.Create;// All auto-positioned nodes use this managerNode1.AutoPosition.Left := True;Node1.AutoPosition.Top := True;
The child manager is stored in Tree.GlobalFormat and applies to all nodes with AutoPosition enabled.