如何在Firemonkey和Delphi XE3下依次浏览TTreeView的所有节点?

时间:2013-02-01 13:51:39

标签: delphi firemonkey

出于性能原因,我需要浏览树视图的项目,而不使用递归。

TTreeview提供GlobalCount和ItemByGlobalIndex方法,但它只返回可见项目
我搜索了根类代码而没有找到所有节点的私有列表,FGlobalItems似乎只包含需要呈现的项目

有没有办法顺序浏览树视图的所有项目(包括不可见和折叠的节点)?

此问题适用于Delphi XE3 / FM2

谢谢,

[编辑2月3日]
我接受了默认答案(不可能开箱即用),尽管我正在寻找一种方法来修补这方面的firemonkey树视图。
经过更多分析后,我发现FGlobalItems列表只保存扩展项,并在方法TCustomTreeView.UpdateGlobalIndexes中维护;
评论FMX.TreeView的第924行(如果AItem.IsExpanded然后......)导致构建完整的节点索引,并允许使用ItemByGlobalIndex()顺序浏览所有节点,但是可能会导致其他性能问题和错误......
没有任何线索,我会保留我的递归代码。

4 个答案:

答案 0 :(得分:3)

问题主要是询问如何在没有递归的情况下遍历树。穿越树的方法有很多种;您的树恰好用视觉控件中的节点表示的事实是无关紧要的。

对于某些算法,更容易用递归术语来考虑遍历。这样,您可以让编程语言通过将当前活动节点作为堆栈中的参数来跟踪树中的位置。如果您不想使用递归,那么您只需要自己跟踪进度。其常用工具包括堆栈和队列。

前序遍历意味着当您访问节点时,在对节点的子节点执行操作之前,您将对该节点的数据执行操作。它对应于从上到下访问树视图控件的每个节点。您可以使用堆栈实现它:

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;

以相反的顺序将孩子推入堆叠,这样他们就会按照所需的顺序弹出。

在该代码中,Action代表您需要对每个节点执行的任何任务。您可以按照代码中的指定使用它作为外部函数,也可以编写包含特定于任务的代码的PreorderVisit的专用版本。

但TTreeView实际上并不代表。它实际上是 forest (树木的集合)。那是因为没有单个节点代表根。您可以轻松地使用上述函数来处理树中的所有节点:

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;

利用TTreeView的特定结构进行前序遍历的另一种方法是使用每个节点的内置GetNext方法:

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;

似乎无法获取Firemonkey树视图的隐藏节点。通过迭代内部树数据结构而不是尝试从GUI中提取信息,您可能会发现更好的结果。

答案 1 :(得分:3)

以下是以非递归方式遍历树视图的函数。如果你有一个节点并想要移动到下一个或前一个节点而不必走遍整个树,那么就很容易使用。

GetNextItem函数通过查看它的第一个孩子,或者如果没有孩子,看着它的父母为下一个孩子自己(并在必要时进一步通过父母)。

GetPrevItem查看父项以查找上一项,并使用GetLastChild查找该项的最后一个子项(使用递归,BTW)。

请注意,编写的代码只会遍历Expanded节点,但可以轻松修改以遍历所有节点(只需删除对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;

答案 2 :(得分:0)

在XE8中,这对我有用:

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;

答案 3 :(得分:0)

Item.ParentItem也可以为零!这就是为什么我用以下几行替换了Parent := Item.ParentItem行:

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

更正后的完整功能GetNextItem

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;

在Delphi 10.3.2上测试

相关问题