7
votes

First of all I am answering my own question Q/A style, so I don't necessarily need anyone to answer this. It's something I've learned and many can make use of this.

I have a tree view which consists of many different nodes. Each node has an object behind it in its Data property, and the objects refer to different levels of hierarchy from one master list of objects, which is quite large (many thousands of items). One node represents a specific property on this main listed object, where the tree allows the user to select a node to view those items which fall under that specific selected category.

When the tree is being populated, it becomes extremely time consuming (in some cases 2 minutes), because each node needs to iterate through every item in this large list and find each item in this list which falls under any given node. Therefore, if there are to be 500 nodes in this tree, then it iterates through this large list 500 times. There are a total of 3 levels of hierarchy - the performance choke comes in when loading the second and third levels, but the first level is simple and quick.

Now there aren't any options to improve the performance of iterating through this list hundreds of times. What I'm wondering is are there any known tricks to improve the performance of populating the tree view?

Here's how it currently works:

var
  X: Integer;
  N: TTreeNode;
  O: TMyObject;
begin
  for X := 0 to MyObjectList.Count - 1 do begin
    O:= TMyObject(MyObjectList[X]); //Object which Node represents
    N:= TreeView.Items.AddChild(nil, O.Caption);
    N.Data:= O;
    LoadNextLevel(N); //Populates child nodes by iterating through master list again
  end;
end;

And a similar method for each additional level.

PS - The first level of hierarchy is populated from a separate list of its own (about 50 objects) whereas the second and third levels are populated from the properties of these many thousands of objects in a master list. This is why the first level loads quickly, and the rest is slow.

2
How about pre-sorting the data so it can be looped through sequentially so you only have to scan through it once? Like the data in your other question: How to populate a tree view based on a flat list with “levels”?.Remy Lebeau
@RemyLebeau That question was a completely separate topic from a different project, this project is quite a bit different.Jerry Dodge
@JerryDodge: that does not negate my question. If sorting the tree data is an option, that could help you load the data into the tree easier.Remy Lebeau
@RemyLebeau Honestly, the data in this massive list is SKU's of a Vendor. and I'm sorting it already by the Vendor's SKU code. Most of the time, the SKU code is structured in a way to be automatically sorted in its hierarchy, but not always.Jerry Dodge

2 Answers

15
votes

If you really care about speed of populating a massive treeview, you should look into virtualTreeView (http://code.google.com/p/virtual-treeview/).
It's an open source treeview specifically designed to be virtual and maximize speed/memory for large treeviews.
It's an amazing component.

6
votes

There is a common trick in tree views to improve the performance in this type of situation. When you refresh this tree view, only load the first level of hierarchy, and don't worry about any further levels. Instead, you can load each additional level at the time of expanding each node. Here's how to do it.

When you populate the first level, rather than continuing with loading each of its child nodes, instead just create 1 "dummy" child node with a nil pointer in its Data property - since each node is expected to have an object in the Data property anyway. Then, monitor the OnExpanding event of the tree view. When a node is expanded, it will check to see if this "dummy" child node exists or not. If so, then it knows it needs to load the child nodes.

When the first level of hierarchy is loaded...

var
  X: Integer;
  N, N2: TTreeNode;
  O: TMyObject;
begin
  for X := 0 to MyObjectList.Count - 1 do begin
    O:= TMyObject(MyObjectList[X]); //Object which Node represents
    N:= TreeView.Items.AddChild(nil, O.Caption);
    N.Data:= O;
    N2:= TreeView.Items.AddChild(N, '');
    N2.Data:= nil; //To emphasize that there is no object on this node
  end;
end;

Then, create an event handler for OnExpanding...

procedure TForm1.TreeViewExpanding(Sender: TObject; Node: TTreeNode;
  var AllowExpansion: Boolean);
var
  N: TTreeNode;
begin
  N:= Node.getFirstChild;
  if N.Data = nil then begin
    //Now we know this is a "dummy" node and needs to be populated with child nodes
    N.Delete; //Delete this dummy node
    LoadNextLevel(N); //Populates child nodes by iterating through master list
  end;
end;

The only disadvantage to this trick is that all nodes which have not yet been expanded will have a + next to them, even though there may not be any child nodes. If this is the case, then when the user clicks the + to expand a node, the child node gets deleted and the + disappears so the user knows there are no child nodes within that node.

Also, using BeginUpdate and EndUpdate in TreeView.Items improves performance by not performing GUI updates until it's all done...

TreeView.Items.BeginUpdate;
try
  //Refresh the tree
finally
  TreeView.Items.EndUpdate;
end;