Iterate recursively through nodes in a tree view?
Asked Answered
E

3

6

I have a tree view which is populated already with files/folders from another procedure. I would like to iterate one by one through the items in the tree view, going in exact order from top to bottom. However, unlike a normal list, I cannot use just a simple for statement for this. I have to go into each node, etc.

How do I do this? I'm hoping there's a way I can do it without running a recursive procedure. As I iterate through these items, I don't necessarily care about parent or child nodes of the currently focused one. I just need to be able to read the Data property of each node as I pass them, as well as highlight the current one in the tree view as I go through it. For each item in this tree view, I will be performing some work and want to visually display to the user which one is currently selected during this process.

Eligibility answered 26/12, 2012 at 19:29 Comment(10)
"I cannot use just a simple for statement for this" <- I'm having trouble understanding why? you are supposed to use a recursive function/procedure with a for loop in it's body, after the for loop you act on Node.Data property and the caller will move on to the next node and so on...Cheiron
I said I cannot use a simple for statement for this, because there is no such thing as an item index or total node count in a tree view.Eligibility
The whole reason I'm asking is because I'm trying to avoid actually calling this same procedure recursively, I'd rather have just a straight 0..MAX-1 loop with no recursion, but I don't think it's possible, and I'd like to make sure.Eligibility
it seems that David found your solution and a pretty clean one also...Cheiron
Just a note from documentation about accessing the TTreeView items, Note: Accessing tree view items by index can be time-intensive, particularly when the tree view contains many items. For optimal performance, try to design your application so that it has as few dependencies on the tree view's item index as possible.Toritorie
@LURD The VCL is optimised so that sequential access by index is efficient. Random access is not.Retina
@DavidHeffernan, thanks for the clarification. Without looking at the source code, I would have assumed that the GetFirstNode/GetNext combination was the optimal solution.Toritorie
@LURD I only realised all this quite recently. I'd always used GetNext. Documentation is weak.Retina
I've realized that I should not depend on the Tree View to keep my data in a recursive structure. When I find the files, I should add a record pointer to a master list behind it, and reference the TTreeNode pointer from that record.Eligibility
@JerryDodge: take a look at the Virtual Treeview component which by nature seperates the data from the tree.Extranuclear
R
16

In fact you can use a for loop.

var
  Node: TTreeNode;
....
for Node in TreeView.Items do
  DoSomething(Node);

This is syntactic sugar for:

for i := 0 to TreeView.Items.Count-1 do
  DoSomething(TreeView.Items[i]);

In terms of readability I would recommend the for/in loop.

In older Delphi versions that don't support the node iterator you may prefer to do it with a while loop.

Node := TreeView.Items.GetFirstNode;
while Assigned(Node) do
begin
  DoSomething(Node);
  Node := Node.GetNext;
end;

I expect there are other ways to do it. These are the only ones that I know!


LU RD makes the interesting observation that the documentation states:

Accessing tree view items by index can be time-intensive, particularly when the tree view contains many items. For optimal performance, try to design your application so that it has as few dependencies on the tree view's item index as possible.

This is quite true. For random access the code has to walk the tree, starting at the root, until the ith node is located.

However, there is an optimisation for sequential access. The Delphi tree view wrapper remembers the index of the last node located by index. The next time you ask for a node with index no more than one different from the cached node, the required node can be returned quickly. This is implemented in TTreeNodes.GetNodeFromIndex.

Retina answered 26/12, 2012 at 19:40 Comment(4)
NICE and I never once would have thought that was possible.Eligibility
@bummi Thanks. ;-) Have you ever studied how Items[i] is implemented? It's quite a piece of work!Retina
Would be interesting to see what would happen if someone would call e.g. TreeView_InsertItem directly, bypassing the treeview, respectively calling SendMessage(hwnd, TVM_INSERTITEM .....Hutchins
@Hutchins The cache is invalidated when that happens.Retina
H
3
var
 i:Integer;
begin
  for I := 0 to tv.Items.Count - 1 do
      begin
        Listbox1.Items.Add(tv.Items[i].Text +' SubItems: ' + IntToStr(tv.Items[i].Count))
      end;

end;
Hutchins answered 26/12, 2012 at 19:42 Comment(5)
This only does the first level of hierarchy, not all the subitems.Eligibility
@Jerry No, this enumerates them allRetina
...I'm confused then, sorry for jumping to conclusions.Eligibility
There are more possebilies for iteration on every level if needed.Hutchins
I rarely use any type of tree or hierarchy or anything recursive. This is one of those cases when I need to be careful about it.Eligibility
P
0

After searching myself for way to populate ComboBoxEx with items from TListView in order to be able to filter data I have made recursive function which iterates through all ListView data. It is not 100% answer for yours question, but it may be usefull.

procedure TForm1.btnPopulateClick(Sender: TObject);
var
  lvl: Integer;
  mNode: TTreeNode;
//--
procedure PlaceTreeItem(nTree: TTreeNode; nLvl: Integer);
var
  nIndent, nImg: Integer;
  NextNode, LastNode: TTreeNode;
begin
  nIndent:=nLvl * 2;
  if nTree.HasChildren then nImg:=0 else nImg:=1;
  ComboBoxEx1.ItemsEx.AddItem(nTree.Text, nImg, nImg, nImg, nIndent, nTree.Data);
  if nTree.HasChildren then
    begin
      Inc(lvl);
      NextNode := nTree.getFirstChild;
      LastNode := nTree.GetLastChild;
      while NextNode <> nil do begin
        PlaceTreeItem(NextNode, lvl);
        if NextNode = LastNode then Dec(lvl);
        NextNode := NextNode.getNextSibling;
      end;
    end;
end;
//--
begin
  ComboBoxEx1.Clear;
  lvl:=0;
  mNode := TreeView1.Items.GetFirstNode;
  while Assigned(mNode) do begin
    PlaceTreeItem(mNode, 0);
    mNode := mNode.getNextSibling;
  end;
end;
Pressurecook answered 8/5, 2023 at 9:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.