出于性能原因,我需要浏览树视图的项目,而不使用递归。
TTreeview提供GlobalCount和ItemByGlobalIndex方法,但它只返回可见项目
我搜索了根类代码而没有找到所有节点的私有列表,FGlobalItems似乎只包含需要呈现的项目
有没有办法顺序浏览树视图的所有项目(包括不可见和折叠的节点)?
此问题适用于Delphi XE3 / FM2
谢谢,
[编辑2月3日]
我接受了默认答案(不可能开箱即用),尽管我正在寻找一种方法来修补这方面的firemonkey树视图。
经过更多分析后,我发现FGlobalItems列表只保存扩展项,并在方法TCustomTreeView.UpdateGlobalIndexes中维护;
评论FMX.TreeView的第924行(如果AItem.IsExpanded然后......)导致构建完整的节点索引,并允许使用ItemByGlobalIndex()顺序浏览所有节点,但是可能会导致其他性能问题和错误......
没有任何线索,我会保留我的递归代码。
答案 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上测试