Skip to main content

Overview

TDBTree (and its base class TCustomDBTree) is a database-aware tree component that automatically populates a tree structure from dataset records. It supports parent-code relationships, grouping, and master-detail scenarios.

Class Hierarchy

TCustomTree
  └─ TCustomDBTree
      └─ TDBTree

Key Features

  • Automatic tree population from datasets
  • Parent-code hierarchical relationships
  • Field grouping for tree levels
  • Master-detail dataset support
  • Multiple layout configurations
  • Custom node formatting per level
  • Grid display mode support

Properties

Basic Database Connection

DataSet
TDataSet
The main dataset (table or query) to display in the tree.
CodeField
String
Field name containing unique record codes. Used for parent-code relationships (Situation 1).
ParentField
String
Field name containing parent codes or grouping field. Determines the hierarchical structure.
TextFields
String
Semicolon-separated list of field names to display as node text.Example: 'Name;Address;Phone'

Master-Detail Support

Detail
TDataSet
Detail dataset for master-detail relationships (Situation 3).
DetailFields
String
Semicolon-separated list of field names from the detail dataset to display.

Display Options

MultiLineText
Boolean
default:"False"
When True, displays each field on a separate line. When False, joins all fields into a single line.

Advanced Layout

Layout
TDBTreeLayout
Collection of layout items for complex multi-dataset trees. Each layout item can represent a different dataset level.

Layout Collection

The Layout property is a collection of TDBLayout items, each representing a dataset level:

TDBLayout Properties

DataSet
TDataSet
Dataset for this layout level.
Fields
String
Semicolon-separated field names to display.
CodeField
String
Unique identifier field name.
ParentField
String
Parent or grouping field name.
DisplayMode
TDBLayoutDisplay
default:"ldSingle"
How to display fields:
  • ldSingle - All fields on one line
  • ldMultiLine - Each field on separate line
  • ldGrid - Fields displayed in grid columns
Format
TTreeNodeShape
Template node defining visual appearance for nodes at this level.
HeaderFormat
TTreeNodeShape
Optional header node displayed before this level’s data.

Methods

Refresh
procedure
Rebuilds the entire tree from the dataset(s).
procedure Refresh;
Call this after changing dataset properties or when data changes.
FindNodeCode
function
Finds a node by its code value.
function FindNodeCode(ACode: Integer): TTreeNodeShape;
Parameters:
  • ACode - The code value to search for (from CodeField)
Returns: Node with matching Tag property, or nil

Usage Scenarios

Situation 1: Parent-Code Relationship

When your dataset has Code and Parent fields: Dataset Example:
Code   Parent   Text
----   ------   ------------
1      0        Continents
2      1          America
3      1          Africa
4      1          Australia
5      2            USA
6      5              California
7      4            Sydney
Code:
with DBTree1 do
begin
  DataSet := Table1;
  CodeField := 'Code';
  ParentField := 'Parent';
  TextFields := 'Text';
  Refresh;
end;
Result:
Continents
  → America
      → USA
          → California
  → Africa
  → Australia
      → Sydney

Situation 2: Grouping (No Code Field)

When grouping by a field without explicit parent-child codes: Dataset Example:
Country      City
----------   ---------------
AUSTRALIA    Sydney
USA          New York
UK           London
AUSTRALIA    Canberra
UK           Manchester
USA          Chicago
Code:
with DBTree1 do
begin
  DataSet := Table1;
  CodeField := '';  // No code field
  ParentField := 'Country';  // Group by country
  TextFields := 'City';
  Refresh;
end;
Result:
AUSTRALIA
  → Sydney
  → Canberra
UK
  → London
  → Manchester
USA
  → New York
  → Chicago

Situation 3: Master-Detail Relationship

For master-detail dataset relationships: Master Dataset:
Person   Department
-------  --------------
John     Accounting
Chris    Management
Anne     Sales
Detail Dataset:
Person  Month  Salary
------  -----  -------
John    April  $1000
John    May    $1100
Chris   March  $900
Code:
with DBTree1 do
begin
  DataSet := MasterTable;      // Master
  CodeField := '';
  ParentField := 'Department';
  TextFields := 'Person';
  
  Detail := DetailTable;       // Detail
  DetailFields := 'Month;Salary';
  
  Refresh;
end;
Result:
Accounting
  → John
      → April $1000
      → May $1100
Management
  → Chris
      → March $900
Sales
  → Anne

Advanced Layout Examples

Multiple Layouts

with DBTree1 do
begin
  // First layout - Countries
  with Layout.Add do
  begin
    DataSet := CountryTable;
    ParentField := 'Region';
    Fields := 'CountryName';
    DisplayMode := ldSingle;
    
    // Custom format
    Format.Color := clYellow;
    Format.Font.Style := [fsBold];
  end;
  
  // Second layout - Cities
  with Layout.Add do
  begin
    DataSet := CityTable;
    ParentField := 'Country';
    Fields := 'CityName;Population';
    DisplayMode := ldMultiLine;
    
    Format.Color := clWhite;
  end;
  
  Refresh;
end;

Grid Display Mode

with DBTree1.Layout.Add do
begin
  DataSet := Table1;
  Fields := 'Name;Age;Email';
  DisplayMode := ldGrid;  // Display as grid
  
  Format.Border.Visible := True;
  Format.AutoSize := True;
end;

DBTree1.Refresh;

Using Header Format

with DBTree1.Layout.Add do
begin
  DataSet := EmployeeTable;
  Fields := 'Name;Position';
  
  // Header node
  HeaderFormat.Text.Add('EMPLOYEES');
  HeaderFormat.Color := clNavy;
  HeaderFormat.Font.Color := clWhite;
  HeaderFormat.Font.Style := [fsBold];
  
  // Data nodes
  Format.Color := clSilver;
end;

DBTree1.Refresh;

Conditional Formatting

DBTree1.DataSet := Table1;
DBTree1.CodeField := 'ID';
DBTree1.ParentField := 'ParentID';
DBTree1.TextFields := 'Name';
DBTree1.Refresh;

// Format nodes after refresh
var
  i: Integer;
  Node: TTreeNodeShape;
begin
  for i := 0 to DBTree1.Shapes.Count - 1 do
  begin
    Node := DBTree1.Shapes[i];
    
    // Color by level
    case Node.Level of
      0: Node.Color := clYellow;
      1: Node.Color := clSkyBlue;
      2: Node.Color := clLime;
    end;
    
    // Bold root nodes
    if Node.Parent = nil then
      Node.Font.Style := [fsBold];
  end;
end;

Common Patterns

Refreshing on Dataset Changes

procedure TForm1.Table1AfterPost(DataSet: TDataSet);
begin
  DBTree1.Refresh;
end;

procedure TForm1.Table1AfterDelete(DataSet: TDataSet);
begin
  DBTree1.Refresh;
end;

Finding and Selecting a Node

var
  Node: TTreeNodeShape;
  SearchCode: Integer;
begin
  SearchCode := 42;
  Node := DBTree1.FindNodeCode(SearchCode);
  
  if Assigned(Node) then
  begin
    Node.Selected := True;
    DBTree1.CenterInView(Node);
  end
  else
    ShowMessage('Node not found');
end;

Multi-Field Display

// Single line with multiple fields
DBTree1.TextFields := 'LastName;FirstName;Email';
DBTree1.MultiLineText := False;  // "Smith John [email protected]"

// Multi-line
DBTree1.MultiLineText := True;
// Result:
// Smith
// John
// [email protected]

Performance Optimization

procedure TForm1.LoadLargeDataset;
begin
  DBTree1.BeginUpdate;
  try
    DBTree1.DataSet := LargeTable;
    DBTree1.CodeField := 'ID';
    DBTree1.ParentField := 'ParentID';
    DBTree1.TextFields := 'Description';
    DBTree1.Refresh;
  finally
    DBTree1.EndUpdate;
  end;
end;

Combining with Manual Nodes

// Load from database
DBTree1.Refresh;

// Add manual nodes
var
  ManualNode: TTreeNodeShape;
begin
  ManualNode := DBTree1.AddRoot('Manual Entry');
  ManualNode.Color := clAqua;
  ManualNode.AddChild('Custom Child 1');
  ManualNode.AddChild('Custom Child 2');
end;

Events

DBTree inherits all events from TCustomTree:
procedure TForm1.DBTree1SelectShape(Sender: TTreeNodeShape);
var
  RecordCode: Integer;
begin
  // Get the database record code
  RecordCode := Sender.Tag;
  
  // Locate in dataset
  if Table1.Locate('ID', RecordCode, []) then
  begin
    // Display record details
    ShowMessage(Table1.FieldByName('Name').AsString);
  end;
end;

Tips and Best Practices

  1. Always call Refresh after changing dataset or field properties
  2. Use BeginUpdate/EndUpdate for better performance with large datasets
  3. Set CodeField to empty string when using grouping without codes
  4. Use Layout collection for complex multi-level trees
  5. Format nodes after Refresh for conditional styling
  6. Store record IDs in node Tag property for quick lookups
  7. Handle dataset events (AfterPost, AfterDelete) to keep tree synchronized

See Also

Build docs developers (and LLMs) love