How to view all TTreeView nodes sequentially under Firemonkey and Delphi XE3?

I need to traverse the elements of a tree without using recursion, for performance reasons.

TTreeview provides GlobalCount and ItemByGlobalIndex methods, but only returns visible items
I searched the root of the class without finding a private list of all nodes, FGlobalItems seems to only contain the items to be displayed

Is there a way to sequentially view all the elements (including invisible and collapsed nodes) of the tree?

This question is specific to Delphi XE3 / FM2

Thank,

[Edit Feb 3]
I accepted the default answer (not possible out of the box), even though I was looking for a way to fix the firemonkey tree in this aspect.
After more analysis, I found out that the FGlobalItems list contains only extended items and is supported in the TCustomTreeView.UpdateGlobalIndexes method; Commenting out line 924 FMX.TreeView (if AItem.IsExpanded then ...) results in a complete index of nodes and allows all nodes to be viewed sequentially using ItemByGlobalIndex (), BUT can lead to other performance issues and errors ...
Without any clue, I will keep my recursive code.

+3


source to share


4 answers


Here are my functions for walking through a tree structure in a non-recursive manner. Easy to use if you have a node and want to go to the next or previous one without having to walk the entire tree.

GetNextItem by looking at it for the first child, or if there are no children, looking at it as a parent for the next child after itself (and, if necessary, through the parents).

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



Note that the code written only for the Extended Nodes traversal, but can be easily modified to traverse all nodes (just remove the 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;

      

+3


source


The question basically asks the question of how to traverse a tree without recursion. There are many ways to cross the tree; the fact that your tree ends up being represented by nodes in visual control doesn't matter.

For some algorithms, it is easier to think of traversal in recursive terms. This way, you let the programming language keep track of where in the tree you are storing the currently active node as an argument on the stack. If you don't want to use recursion, you just need to track the progress yourself. The usual tools for this are stacks and queues.

Preorder traversal means that when you visit a node, you do your action on the node's data before you do the action on the children of the node. This corresponds to visiting each node of the treeview control from top to bottom. You can implement it like this with a stack:

procedure PreorderVisit(Node: TTreeNode; Action: TNodeAction);
var
  Worklist: TStack<TTreeNode>;
  i: Integer;
begin
  Worklist := TStack<TTreeNode>.Create;
  try
    Worklist.Push(Node);
    repeat
      Node := Worklist.Pop;
      for i := Pred(Node.Items.Count) downto 0 do
        Worklist.Push(Node.Items[i]);
      Action(Node);
    until Worklist.Empty;
  finally
    Worklist.Free;
  end;
end;

      

Push the children onto the stack in reverse order so they pop out in the correct order.

In this code, Action

denotes any task that needs to be done with each node. You can use it as indicated in the code as an external function or write a specialized version PreorderVisit

that includes task-specific code.

TTreeView is not actually a tree. It really is a forest (collection of trees). This is because there is no single node that represents the root. You can easily use the function above to process all nodes in the tree:



procedure PreorderVisitTree(Tree: TTreeView; Action: TNodeAction);
var
  i: Integer;
begin
  for i := 0 to Pred(Tree.Items.Count) do
    PreorderVisit(Tree.Items[i], Action);
end;

      


Another way to do order traversal using a specific TTreeView structure is to use the built-in method GetNext

for each node:

procedure PreorderVisitTree(Tree: TTreeView; Action: TNodeAction);
var
  Node: TTreeNode;
begin
  if Tree.Items.Count = 0 then
    exit;
  Node := Tree.Items[0];
  repeat
    Action(Node);
    Node := Node.GetNext;
  until not Assigned(Node);
end;

      


There seems to be no way to get the hidden nodes of the Firemonkey tree tree. You can find the best results by iterating over your internal tree data structure instead of trying to extract information from the GUI.

+3


source


In XE8 this works for me:

function GetNextItem(Item: TTreeViewItem): TTreeViewItem;
var
   Parent: TFMXObject;
   Child: TTreeViewItem;
begin
    Result := nil;
    if Item.Count > 0 then
        Result := Item.Items[0]
    else
    begin
        Parent := Item.ParentItem;
        Child := Item;
        while (Result = nil) and (Parent <> nil) do
        begin
           if Parent is TTreeViewItem then
           begin
               if TTreeViewItem(Parent).Count > (Child.Index + 1) then
                   Result := TTreeViewItem(Parent).Items[Child.Index + 1]
               else
               begin
               Child := TTreeViewItem(Parent);
               if Child.ParentItem <> nil then
                   Parent := Child.ParentItem
               else
                   Parent := Child.TreeView;
               end;
           end
           else
           begin
            if TTreeView(Parent).Count > Child.Index + 1 then
                Result := TTreeView(Parent).Items[Child.Index + 1]
            else
                Parent := nil;
            end;
        end;
    end;
end;

      

+1


source


Item.ParentItem

can also be null! That is why I replaced the line with the Parent := Item.ParentItem

following lines:

  if Item.ParentItem <> nil then
    Parent := Item.ParentItem
  else
    Parent := Item.TreeView;

      

Full function GetNextItem

after fix:

function GetNextItem(Item: TTreeViewItem): TTreeViewItem;
var
  Parent: TFMXObject;
  Child: TTreeViewItem;
begin
  Result := nil;
  if Item.Count > 0 then
    Result := Item.Items[0]
  else begin
    if Item.ParentItem <> nil then
      Parent := Item.ParentItem
    else
      Parent := Item.TreeView;
    Child := Item;
    while (Result = nil) and (Parent <> nil) do
    begin
      if Parent is TTreeViewItem then
      begin
        if TTreeViewItem(Parent).Count > (Child.Index + 1) then
          Result := TTreeViewItem(Parent).Items[Child.Index + 1]
        else begin
          Child := TTreeViewItem(Parent);
          if Child.ParentItem <> nil then
            Parent := Child.ParentItem
          else
            Parent := Child.TreeView;
        end;
      end else begin
        if TTreeView(Parent).Count > Child.Index + 1 then
          Result := TTreeView(Parent).Items[Child.Index + 1]
        else
          Parent := nil;
      end;
    end;
  end;
end;

      

Tested in Delphi 10.3.2

0


source







All Articles