Search code examples
delphifiremonkey

How to sequentially browse all nodes of a TTreeView under Firemonkey and Delphi XE3?


I need to browse items of a treeview, without using recursion, for performance reasons.

TTreeview provides GlobalCount and ItemByGlobalIndex methods, but it only returns visible items
I searched the root class code without finding a private list of all nodes, FGlobalItems seems to only holds items that need to be rendered

Is there a way to sequentially browse all items (including not visible and collapsed nodes) of a treeview?

This question applies to Delphi XE3 / FM2

Thanks,

[Edit Feb 3]
I accepted the default answer (not possible out of the box), despite I was looking for a way to patch the firemonkey treeview on this aspect.
After more analysis, I found out that the FGlobalItems list only holds expanded items and is maintained in the method TCustomTreeView.UpdateGlobalIndexes;
Commenting line 924 of FMX.TreeView (if AItem.IsExpanded then...) leads to building a full index of nodes, and allows to browse all nodes sequentially using ItemByGlobalIndex(), BUT could lead to other performance issues and bugs...
Without any more clue, I'll keep my recursive code.


Solution

  • Here are my functions for walking a treeview in a non-recursive manner. Simple to use if you have a node and want to move to the next or previous one without having to walk the entire tree.

    GetNextItem functions by looking at it's first child, or if no children, looking at it's parent for the next child after itself (and going further through parents as necessary).

    GetPrevItem looks at the parent to find the previous item, and uses GetLastChild to find the last child of that item (which does use recursion, BTW).

    Note that the code as written only walk Expanded nodes, but can easily be modified to walk all nodes (just remove references to IsExpanded).

    function GetLastChild(Item: TTreeViewItem): TTreeViewItem;
    begin
      if (Item.IsExpanded) and (Item.Count > 0) then
        Result := GetLastChild(Item.Items[Item.Count-1])
      else
        Result := Item;
    end;
    
    function GetNextItem(Item: TTreeViewItem): TTreeViewItem;
    var ItemParent: TTreeViewItem;
      I: Integer;
      TreeViewParent: TTreeView;
      Parent: TFMXObject;
      Child: TFMXObject;
    begin
      if Item = nil then
        Result := nil
      else if (Item.IsExpanded) and (Item.Count > 0) then
        Result := Item.Items[0]
      else
      begin
        Parent := Item.Parent;
        Child := Item;
        while (Parent <> nil) and not (Parent is TTreeView) do
        begin
          while (Parent <> nil) and not (Parent is TTreeView) and not (Parent is TTreeViewItem) do
            Parent := Parent.Parent;
    
          if (Parent <> nil) and (Parent is TTreeViewItem) then
          begin
            ItemParent := TTreeViewItem(Parent);
            I := 0;
            while (I < ItemParent.Count) and (ItemParent.Items[I] <> Child) do
              inc(I);
            inc(I);
            if I < ItemParent.Count then
            begin
              Result := ItemParent.Items[I];
              EXIT;
            end;
            Child := Parent;
            Parent := Parent.Parent
          end;
        end;
    
        if (Parent <> nil) and (Parent is TTreeView) then
        begin
          TreeViewParent := TTreeView(Parent);
          I := 0;
          while (I < TreeViewParent.Count) and (TreeViewParent.Items[I] <> Item) do
            inc(I);
          inc(I);
          if I < TreeViewParent.Count then
            Result := TreeViewParent.Items[I]
          else
          begin
            Result := Item;
            EXIT;
          end;
        end
        else
          Result := Item
      end
    end;
    
    function GetPrevItem(Item: TTreeViewItem): TTreeViewItem;
    var Parent: TFMXObject;
      ItemParent: TTreeViewItem;
      TreeViewParent: TTreeView;
      I: Integer;
    begin
      if Item = nil then
        Result := nil
      else
      begin
        Parent := Item.Parent;
        while (Parent <> nil) and not (Parent is TTreeViewItem) and not (Parent is TTreeView) do
          Parent := Parent.Parent;
    
        if (Parent <> nil) and (Parent is TTreeViewItem) then
        begin
          ItemParent := TTreeViewItem(Parent);
          I := 0;
          while (I < ItemParent.Count) and (ItemParent.Items[I] <> Item) do
            inc(I);
          dec(I);
          if I >= 0 then
            Result := GetLastChild(ItemParent.Items[I])
          else
            Result := ItemParent;
        end
        else if (Parent <> nil) and (Parent is TTreeView) then
        begin
          TreeViewParent := TTreeView(Parent);
          I := 0;
          while (I < TreeViewParent.Count) and (TreeViewParent.Items[I] <> Item) do
            inc(I);
          dec(I);
          if I >= 0 then
            Result := GetLastChild(TreeViewParent.Items[I])
          else
            Result := Item
        end
        else
          Result := Item;
      end;
    end;