Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to populate a tree view based on a flat list with "levels"?

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?

like image 509
Jerry Dodge Avatar asked Nov 04 '13 16:11

Jerry Dodge


2 Answers

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;
like image 82
Remy Lebeau Avatar answered Sep 28 '22 01:09

Remy Lebeau


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.

like image 42
David Heffernan Avatar answered Sep 28 '22 00:09

David Heffernan