Displaying a Hierarchy in a TreeView or a Level Graph

The little TreeView example supplied with Lazarus is based on a tutorial, which we found useful when writing the demonstration shown in action below. The following page shows the representation of the same data in a level graph.

Screenshot of program in action

Screenshot of program in action

Since a TreeView is designed for displaying hierarchical data, we thought it fitting to display it together with some of the related classes in the Lazarus Component Library (LCL). All the classes shown have links to their documentation. (We have excluded interfaces from the hierarchy).

These bullet points might save you some time when getting started with TreeViews.
  • The representation of the TreeView in the source code of the form is not human-friendly;
  • The text file saved and loaded is human-friendly, with a tab representing a child e.g.:
    TObject
      TPersistent
        TComponent
          TLCLComponent
            TControl
              TWinControl
                TCustomControl
                  TCustomTreeView
                    TTreeView
                TCustomEdit
                  TEdit
  • You can construct your initial tree (1) in Pascal code, (2) by using the specialised property editor for the Items property in the Object Inspector or (3) by typing into a text file to be loaded by the program.
  • The property Items (the collection of TreeNodes) has useful routines such as the one we use with the line
    tvClasses.Selected := tvClasses.Items.FindNodeWithText('TCustomTreeView');
    .
  • The possible styles of line for the TreeView's TreeLinePenStyle are psSolid, psDash, psDot, psDashDot, psDashDotDot, psinsideFrame, psPattern and psClear. The display of some of the styles may not meet your expectations.

See the Novel unit of Jerzy Griffith's (Delphi XE4) word processor for impressive use of a TreeView to handle the hierarchical components of a novel.

Pascal Code of Form

unit uTreeViewDemo;

{
  The original demo showing addition and deletion of nodes was written by Andre .v.d. Merwe and marked public domain.
  Quickly converted to Lazarus and FPC by Tom Lisjac <vlx@users.sourceforge.net>
  who gave a link to the original source and an *excellent* tutorial on the TTreeview
  component. Visit the new location:
  http://users.iafrica.com/d/da/dart/zen/Articles/TTreeView/TreeView.html
  Classes tree with URLs, save and load and options for line style added by PPS
}

interface 

{$mode objfpc} {$H+}

uses
  SysUtils, LResources, Classes, LCLProc, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, Buttons, ExtCtrls, ShellAPI, Windows;
type
  TForm1 = class(TForm)
    btnLoad: TButton;
    btnSave: TButton;
    btnLink: TButton;
    edtFilename: TEdit;
    RadioGroup1: TRadioGroup;
    StaticText1: TStaticText;
    tvClasses: TTreeView;
    but_Add: TButton;
    but_Remove: TButton;
    procedure btnLinkClick(Sender: TObject);
    procedure btnLoadClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure but_AddClick(Sender: TObject);
    procedure but_RemoveClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

procedure TForm1.but_AddClick(Sender: TObject);
var
  sText : ansistring;
begin
  if tvClasses.Selected = nil then // If nothing is selected
    begin
      // Does a root node already exist?
      if tvClasses.Items.Count = 0 then
        begin
          // Add the root node.
          with tvClasses.Items.AddFirst(nil, 'Root') do
            begin
              Selected := true;
              debugln('tv_eg1.Selected=', DbgS(tvClasses.Selected));
            end;
        end
      else
        begin
          // There is a root, so user must first select a node.
          ShowMessage('Select a parent node.');
          Exit;
        end;
   end
  else
    begin
      // Get a name for the new node.
      sText := 'New node';
      InputQuery('New Node', 'Caption?', sText);
      //  Add the node as a child of the selected node.
      with tvClasses.Items.AddChild(tvClasses.Selected, sText) do
        MakeVisible;
    end;
end;

procedure TForm1.btnSaveClick(Sender: TObject);
begin
  tvClasses.SaveToFile(edtFilename.Text);
end;

procedure TForm1.btnLoadClick(Sender: TObject);
begin
  if fileExists(edtFilename.Text) then
    begin
      tvClasses.LoadFromFile(edtFilename.Text);
      tvClasses.Items[0].Expand(true); // Recursively expands/unfolds
    end
  else
    ShowMessage('File not found');
end;

procedure TForm1.btnLinkClick(Sender: TObject);
var
  h : HWND;
  url : String;
  prefix: string = 'http://lazarus-ccr.sourceforge.net/docs/';
begin
  if tvClasses.Selected = nil then
    ShowMessage('Please select a node.')
  else
    case tvClasses.Selected.Text of
      'TObject': url:= prefix + 'rtl/system/tobject.html';
      'TPersistent':url:= prefix + 'rtl/classes/tpersistent.html';
      'TComponent': url:= prefix + 'rtl/classes/tcomponent.html';
      'TLCLComponent': url:= prefix + 'lcl/lclclasses/tlclcomponent.html';
      'TControl', 'TWinControl', 'TCustomControl', 'TGraphicControl':
        url := prefix + 'lcl/controls/' + lowerCase(tvClasses.Selected.Text)+ '.html';
      'TCustomTreeView': url:= prefix + 'lcl/comctrls/tcustomtreeview.html';
      'TTreeView': url:= prefix + 'lcl/comctrls/ttreeview.html';
      'TCustomEdit', 'TEdit', 'TMemo', 'TCustomMemo', 'TCustomComboBox','TComboBox', 'TButtonControl',
      'TCustomButton', 'TButton', 'TCustomListBox', 'TListBox', 'TCustomLabel', 'TLabel':
        url:= prefix + 'lcl/stdctrls/' + lowerCase(tvClasses.Selected.Text)+ '.html';
    else
      url := '';
  end;
  if url = '' then
    ShowMessage('url unavailable')
  else
    Shellexecute(h, 'open', pchar(url), nil, nil, sw_show);
end;

procedure TForm1.but_RemoveClick(Sender: TObject);
begin
  // Make sure somthing is selected before trying to delete it.
  if tvClasses.Selected = nil then
    begin
      ShowMessage('Nothing selected');
      Exit;
    end;
  //Don't allow user to delete the root node
  if  tvClasses.Selected.Level = 0   then
    begin
       ShowMessage('You can''t delete the root node.'  );
       Exit;
     end;
  //Delete the selected node
  tvClasses.Selected.Delete;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  if not fileExists('classes.txt') then
    tvClasses.SaveToFile('classes.txt');
  tvClasses.Selected := tvClasses.Items.FindNodeWithText('TCustomTreeView');
end;

procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  case RadioGroup1.ItemIndex of
    0: tvClasses.TreeLinePenStyle := psClear;
    1: tvClasses.TreeLinePenStyle := psDot;
    2: tvClasses.TreeLinePenStyle := psSolid;
  end;
  tvClasses.Invalidate;
end;


{$R *.lfm}

end.

Source Code of Form

object Form1: TForm1
  Left = 565
  Height = 438
  Top = 132
  Width = 542
  ActiveControl = but_Add
  Caption = 'Classes TreeView'
  ClientHeight = 438
  ClientWidth = 542
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  OnCreate = FormCreate
  LCLVersion = '1.2.4.0'
  object tvClasses: TTreeView
    Left = 0
    Height = 438
    Top = 0
    Width = 376
    Align = alLeft
    DefaultItemHeight = 18
    Font.CharSet = ANSI_CHARSET
    Font.Height = -13
    Font.Name = 'MS Sans Serif'
    Font.Pitch = fpVariable
    Font.Quality = fqDraft
    HideSelection = False
    Indent = 19
    ParentFont = False
    ParentShowHint = False
    ReadOnly = True
    TabOrder = 0
    Options = [tvoAutoItemHeight, tvoKeepCollapsedNodes, tvoReadOnly, tvoShowButtons, tvoShowLines, tvoShowRoot, tvoToolTips]
    Items.Data = {
      F9FFFFFF020001000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF010000000000
      00000107000000544F626A656374FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0100
      000000000000010B0000005450657273697374656E74FFFFFFFFFFFFFFFFFFFF
      FFFFFFFFFFFF0100000000000000010A00000054436F6D706F6E656E74FFFFFF
      FFFFFFFFFFFFFFFFFFFFFFFFFF0100000000000000010D000000544C434C436F
      6D706F6E656E74FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF020000000000000001
      0800000054436F6E74726F6CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05000000
      00000000010B0000005457696E436F6E74726F6CFFFFFFFFFFFFFFFFFFFFFFFF
      FFFFFFFF0100000000000000010E00000054437573746F6D436F6E74726F6CFF
      FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0100000000000000010F00000054437573
      746F6D5472656556696577FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000
      0000000009000000545472656556696577FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
      FF0200000000000000010B00000054437573746F6D45646974FFFFFFFFFFFFFF
      FFFFFFFFFFFFFFFFFF000000000000000000050000005445646974FFFFFFFFFF
      FFFFFFFFFFFFFFFFFFFFFF0100000000000000010B00000054437573746F6D4D
      656D6FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000005000000
      544D656D6FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0100000000000000010F00
      000054437573746F6D436F6D626F426F78FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
      FF0000000000000000000900000054436F6D626F426F78FFFFFFFFFFFFFFFFFF
      FFFFFFFFFFFFFF0100000000000000010E00000054427574746F6E436F6E7472
      6F6CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0100000000000000010D00000054
      437573746F6D427574746F6EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000
      00000000000700000054427574746F6EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
      0100000000000000010E00000054437573746F6D4C697374426F78FFFFFFFFFF
      FFFFFFFFFFFFFFFFFFFFFF00000000000000000008000000544C697374426F78
      FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0100000000000000010F000000544772
      6170686963436F6E74726F6CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01000000
      00000000010C00000054437573746F6D4C6162656CFFFFFFFFFFFFFFFFFFFFFF
      FFFFFFFFFF00000000000000000006000000544C6162656C
    }
    TreeLinePenStyle = psSolid
  end
  object but_Add: TButton
    Left = 400
    Height = 25
    Top = 8
    Width = 96
    Caption = 'Add Descendant'
    OnClick = but_AddClick
    TabOrder = 1
  end
  object but_Remove: TButton
    Left = 400
    Height = 25
    Top = 40
    Width = 96
    Caption = 'Remove'
    OnClick = but_RemoveClick
    TabOrder = 2
  end
  object btnLoad: TButton
    Left = 400
    Height = 25
    Top = 148
    Width = 75
    Caption = 'Load'
    OnClick = btnLoadClick
    TabOrder = 3
  end
  object btnSave: TButton
    Left = 400
    Height = 25
    Top = 180
    Width = 75
    Caption = 'Save'
    OnClick = btnSaveClick
    TabOrder = 4
  end
  object edtFilename: TEdit
    Left = 448
    Height = 21
    Top = 120
    Width = 88
    TabOrder = 5
    Text = 'classes.txt'
  end
  object StaticText1: TStaticText
    Left = 400
    Height = 17
    Top = 124
    Width = 48
    Caption = 'Filename'
    TabOrder = 6
  end
  object RadioGroup1: TRadioGroup
    Left = 400
    Height = 105
    Top = 231
    Width = 100
    AutoFill = True
    Caption = 'Line Style'
    ChildSizing.LeftRightSpacing = 6
    ChildSizing.EnlargeHorizontal = crsHomogenousChildResize
    ChildSizing.EnlargeVertical = crsHomogenousChildResize
    ChildSizing.ShrinkHorizontal = crsScaleChilds
    ChildSizing.ShrinkVertical = crsScaleChilds
    ChildSizing.Layout = cclLeftToRightThenTopToBottom
    ChildSizing.ControlsPerLine = 1
    ClientHeight = 87
    ClientWidth = 96
    ItemIndex = 2
    Items.Strings = (
      'Clear'
      'Dotted'
      'Solid'
    )
    OnClick = RadioGroup1Click
    TabOrder = 7
  end
  object btnLink: TButton
    Left = 400
    Height = 25
    Top = 72
    Width = 96
    Caption = 'Web page'
    OnClick = btnLinkClick
    TabOrder = 8
  end
end
Programming - a skill for life!

Using widgets (such as list boxes, combo boxes, string grids, DBgrids, charts and maps) and drawing on the canvas