user1769184
user1769184

Reputation: 1621

How to create a tree data structure and be able to view it with a TreeView

I want to write a program that works on a hierarchically organized tree data structure. I need to have a root node and then be able to add child nodes and to move these node across the complete tree.

Is there a data structure in Delphi I can use for my algorithms and then later simply "copy" it to the GUI using a TTreeview component?

I suppose I need such a class:

type
  TMyElement = class(...)
    ElementName: String; 
    ...
    ChildElementList: TMyElementList;
    HasChildElement: Boolean; 
    ...
  end;

At the dots, there will be more fields and routines specific for the element. How to build up this class? And how can I conveniently add these elements to a TreeView, using the ElementName field as caption for the tree nodes?

Upvotes: 1

Views: 2516

Answers (2)

NGLN
NGLN

Reputation: 43669

So, you have two questions:

How to build up this class?

Well, not yet focusing on the viewing-in-a-tree-view-ability, you could descent your class from TObjectList. Doing so, every element is capable of holding more of these object lists = elements. This approach is for example used for TMenuItem too.

And how can I conveniently add these elements to a TreeView, using the ElementName field as caption for the tree nodes?

You could add an AssignTo method to the class so that you can "assign" the root node to the TreeView.

Here you have an example implementation of the whole class. Add own properties as needed.

type
  TElement = class(TObjectList)
  private
    FName: String;
    FParent: TElement;
    function GetItem(Index: Integer): TElement;
    function GetLevel: Integer;
    procedure SetItem(Index: Integer; Value: TElement);
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    function Add(AElement: TElement): Integer;
    procedure AssignTo(Dest: TPersistent);
    constructor Create; overload;
    constructor Create(AName: String); overload;
    constructor Create(AParent: TElement); overload;
    constructor Create(AParent: TElement; AName: String); overload;
    destructor Destroy; override;
    function HasChilds: Boolean;
    procedure Insert(Index: Integer; AElement: TObject);
    property Items[Index: Integer]: TElement read GetItem write SetItem;
      default;
    property Level: Integer read GetLevel;
    property Name: String read FName write FName;
  end;

{ TElement }

function TElement.Add(AElement: TElement): Integer;
begin
  if AElement = nil then
    Result := -1
  else
    Result := inherited Add(AElement);
end;

procedure TElement.AssignTo(Dest: TPersistent);
var
  Nodes: TTreeNodes;
  Node: TTreeNode;
  I: Integer;
begin
  if Dest is TTreeView then
    AssignTo(TTreeView(Dest).Items)
  else if Dest is TTreeNodes then
  begin
    Nodes := TTreeNodes(Dest);
    Nodes.BeginUpdate;
    Nodes.Clear;
    for I := 0 to Count - 1 do
    begin
      Node := Nodes.AddNode(nil, nil, Items[I].Name, Items[I], naAdd);
      Items[I].AssignTo(Node);
    end;
    Nodes.EndUpdate;
  end
  else if Dest is TTreeNode then
  begin
    Node := TTreeNode(Dest);
    Nodes := Node.Owner;
    for I := 0 to Count - 1 do
      Items[I].AssignTo(
        Nodes.AddNode(nil, Node, Items[I].Name, Items[I], naAddChild));
  end;
end;

constructor TElement.Create;
begin
  Create(nil, '');
end;

constructor TElement.Create(AName: String);
begin
  Create(nil, AName);
end;

constructor TElement.Create(AParent: TElement);
begin
  Create(AParent, '');
end;

constructor TElement.Create(AParent: TElement; AName: String);
begin
  inherited Create(True);
  FName := AName;
  if AParent <> nil then
    AParent.Add(Self);
end;

destructor TElement.Destroy;
begin
  if FParent <> nil then
    FParent.Extract(Self);
  inherited Destroy;
end;

function TElement.GetItem(Index: Integer): TElement;
begin
  Result := TElement(inherited Items[Index]);
end;

function TElement.GetLevel: Integer;
begin
  if FParent = nil then
    Result := 0
  else
    Result := FParent.Level + 1;
end;

function TElement.HasChilds: Boolean;
begin
  Result := Count > 0;
end;

procedure TElement.Insert(Index: Integer; AElement: TObject);
begin
  if AElement <> nil then
    inherited Insert(Index, AElement);
end;

procedure TElement.Notify(Ptr: Pointer; Action: TListNotification);
begin
  inherited Notify(Ptr, Action);
  case Action of
    lnAdded:
      TElement(Ptr).FParent := Self;
    lnExtracted,
    lnDeleted:
      TElement(Ptr).FParent := nil;
  end;
end;

procedure TElement.SetItem(Index: Integer; Value: TElement);
begin
  inherited Items[Index] := Value;
end;

Example usage:

procedure TForm2.FormCreate(Sender: TObject);
begin
  FRoot := TElement.Create;
  TElement.Create(FRoot, '1');
  TElement.Create(FRoot, '2');
  TElement.Create(FRoot[1], '2.1');
  TElement.Create(FRoot[1], '2.2');
  TElement.Create(FRoot[1][0], '2.1.1');
  TElement.Create(FRoot[1][0][0], '2.1.1.1');
  TElement.Create(FRoot[1][0][0], '2.1.1.2');
  TElement.Create(FRoot[1], '2.3');
  TElement.Create(FRoot, '3');
  TElement.Create(FRoot, '4');
  TElement.Create(FRoot[3], '4.1');
  FRoot.AssignTo(TreeView1);
end;

enter image description here

Filling a TTreeView at runtime is great fun!

Upvotes: 2

Mark Wilsdorf
Mark Wilsdorf

Reputation: 769

Google 'rmtreenonview'. It is a single unit which implements a non-visual TTreeView-like class--most of the methods and properties are fully compatible with TTreeView.

rmtreenonview is quick and lightweight...we use it for manipulating a bunch of tree-structured data, but write the results to a grid control for display. But, you could copy results to a TTReeView just as easily.

Oh, and it also has hash-based tree searching capability and a couple other additons.

Upvotes: -2

Related Questions