6
votes

I have a list of objects populated from a third-party project file. The way this file was designed is so each item is on a "level" of hierarchy. So the very first item is on level 0, all of its child items are on level 1, and so on.

As an example:

  1. Node 1     (Level 0)
  2. Node 1.1   (Level 1)
  3. Node 1.2   (Level 1)
  4. Node 1.3   (Level 1)
  5. Node 1.3.1 (Level 2)
  6. Node 1.4   (Level 1)
  7. Node 2     (Level 0)
  8. Node 2.1   (Level 1)
  9. Node 2.1.1 (Level 2)
 10. Node 3     (Level 0)

This would produce a hierarchy like so:

- Node 1
--- Node 1.1
--- Node 1.2
--- Node 1.3
----- Node 1.3.1
--- Node 1.4
- Node 2
--- Node 2.1
----- Node 2.1.1
- Node 3

My issue is figuring out how to populate this structure into a VCL TTreeView based on these "Level" properties of each listed object. If I had designed this third-party file structure, I would have used a parent property instead of a level property.

Objects in this list can be iterated like this:

var
  I: TMyItem;
  N: TTreeNode;
begin
  for X := 0 to MyList.Count - 1 do begin
    I := MyList[X];
    //TMyItem has property "Level" which specifies hierarchy
    //  as well as "Title" property for the node's caption
    //How to create node based on Level?

    N.Data := I;
  end;  
end;

Based on this structure, how do I populate this in a tree view?

2

2 Answers

9
votes

Try something like this:

var
  Item: TMyItem;
  Node: TTreeNode;
  NodeLevel: Integer;
  X: Integer;
begin
  Node := nil;
  NodeLevel := 0;
  for X := 0 to MyList.Count-1 do
  begin
    Item := MyList[X];
    if (Node = nil) or (Item.Level <= 0) then
    begin
      Node := TreeView1.Items.AddObject(nil, Item.Text, Item);
      NodeLevel := 0;
    end
    else if Item.Level = NodeLevel then
    begin
      Node := TreeView1.Items.AddObject(Node, Item.Text, Item);
    end else
    begin
      while Item.Level <= NodeLevel do
      begin
        Node := Node.Parent;
        Dec(NodeLevel);
      end;
      Node := TreeView1.Items.AddChildObject(Node, Item.Text, Item);
      Inc(NodeLevel);
    end;
    // set Node properties as needed...
  end;
end;
9
votes

Create a list that will contain the latest parent node for each level. Initially this list will be empty.

Now walk the linear list. Whenever you add an item at level N to the list, you do the following:

  • Add it as a child of the latest parent at level N-1, and
  • set the latest parent at level N to be the item you just added.

You'll need to handle the case where N=0 for which the above algo would require you to come up with the latest parent at level -1, whatever that means. Does your tree have an overall root node? If so, then by definition, the latest parent at level -1 is the root node. Otherwise, if there's no root node, you'll need do whatever your tree view component requires to add a node at the top level, as opposed to as a child of another node.

Assuming that there is no root node, then the code would look like this:

var
  Item: TItem;
  LatestParents: TList<TTreeNode>;
  Parent, NewNode: TTreeNode;
begin
  LatestParents := TList<TTreeNode>.Create;
  try
    LatestParents.Add(nil);
    for Item in Items do
    begin
      Parent := LatestParents[Item.Level];
      NewNode := TreeView.Items.AddChild(Parent, Item.Text);
      LatestParents.Count := Max(LatestParents.Count, Item.Level+2);
      LatestParents[Item.Level+1] := NewNode;
    end;
  finally
    LatestParents.Free;
  end;
end;

You may want to put some error checking into the code if there's a possibility of your code encountering a malformed description of the tree. For instance if the first node that you encounter does not have level 0, then this code will fail.