【问题标题】:How to populate a tree view based on a flat list with "levels"?如何根据带有“级别”的平面列表填充树视图?
【发布时间】:2013-11-05 17:27:46
【问题描述】:

我有一个从第三方项目文件填充的对象列表。该文件的设计方式是每个项目都位于层次结构的“级别”上。所以第一个项目在 0 级,它的所有子项目都在 1 级,依此类推。

举个例子:

  1. Node 1     (Level 0)
  2. Node 1.1   (Level 1)
  3. Node 1.2   (Level 1)
  4. Node 1.3   (Level 1)
  5. Node 1.3.1 (Level 2)
  6. Node 1.4   (Level 1)
  7. Node 2     (Level 0)
  8. Node 2.1   (Level 1)
  9. Node 2.1.1 (Level 2)
 10. Node 3     (Level 0)

这会产生这样的层次结构:

- Node 1
--- Node 1.1
--- Node 1.2
--- Node 1.3
----- Node 1.3.1
--- Node 1.4
- Node 2
--- Node 2.1
----- Node 2.1.1
- Node 3

我的问题是弄清楚如何根据每个列出的对象的这些“级别”属性将此结构填充到 VCL TTreeView 中。如果我设计了这个第三方文件结构,我会使用父属性而不是级别属性。

这个列表中的对象可以这样迭代:

var
  I: TMyItem;
  N: TTreeNode;
begin
  for X := 0 to MyList.Count - 1 do begin
    I := MyList[X];
    //TMyItem has property "Level" which specifies hierarchy
    //  as well as "Title" property for the node's caption
    //How to create node based on Level?

    N.Data := I;
  end;  
end;

基于这个结构,我如何在树视图中填充它?

【问题讨论】:

    标签: delphi treeview hierarchy


    【解决方案1】:

    试试这样的:

    var
      Item: TMyItem;
      Node: TTreeNode;
      NodeLevel: Integer;
      X: Integer;
    begin
      Node := nil;
      NodeLevel := 0;
      for X := 0 to MyList.Count-1 do
      begin
        Item := MyList[X];
        if (Node = nil) or (Item.Level <= 0) then
        begin
          Node := TreeView1.Items.AddObject(nil, Item.Text, Item);
          NodeLevel := 0;
        end
        else if Item.Level = NodeLevel then
        begin
          Node := TreeView1.Items.AddObject(Node, Item.Text, Item);
        end else
        begin
          while Item.Level <= NodeLevel do
          begin
            Node := Node.Parent;
            Dec(NodeLevel);
          end;
          Node := TreeView1.Items.AddChildObject(Node, Item.Text, Item);
          Inc(NodeLevel);
        end;
        // set Node properties as needed...
      end;
    end;
    

    【讨论】:

    • 感谢您提供代码,尽管 David 的回答很好,但我还是有点迷茫。
    • 作为记录,在最多 10 层的层次结构中加载大约 600 个节点,此方法在 30 毫秒内填充此树视图。
    【解决方案2】:

    创建一个列表,其中包含每个级别的最新父节点。最初,此列表将为空。

    现在遍历线性列表。每当您将第 N 级的项目添加到列表中时,您都会执行以下操作:

    • 将其添加为 N-1 级最新父级的子级,并且
    • 将最新的 N 级父项设置为您刚刚添加的项。

    您需要处理 N=0 的情况,对于这种情况,上述算法要求您在级别 -1 上找到最新的父级,无论这意味着什么。你的树有一个整体的根节点吗?如果是这样,那么根据定义,级别 -1 的最新父节点是根节点。否则,如果没有根节点,您将需要执行树视图组件所需的任何操作,以在顶层添加节点,而不是作为另一个节点的子节点。

    假设没有根节点,那么代码如下所示:

    var
      Item: TItem;
      LatestParents: TList<TTreeNode>;
      Parent, NewNode: TTreeNode;
    begin
      LatestParents := TList<TTreeNode>.Create;
      try
        LatestParents.Add(nil);
        for Item in Items do
        begin
          Parent := LatestParents[Item.Level];
          NewNode := TreeView.Items.AddChild(Parent, Item.Text);
          LatestParents.Count := Max(LatestParents.Count, Item.Level+2);
          LatestParents[Item.Level+1] := NewNode;
        end;
      finally
        LatestParents.Free;
      end;
    end;
    

    如果您的代码有可能遇到对树的格式错误的描述,您可能需要在代码中添加一些错误检查。例如,如果您遇到的第一个节点没有级别 0,则此代码将失败。

    【讨论】:

    • 谢谢你的帮助,虽然我还是有点失落。雷米的代码回答让我到达了我需要的地方。
    • 好吧,我没有给出代码,因为,1. 不清楚我们在谈论哪个组件(VCL、FMX、Virtual TreeView 等),2. 我不知道您的确切要求.根节点,以及 3. 我认为您可以自己解决细节问题。无论如何,我猜你需要代码。
    • 确实,但考虑到 SO 的工作方式,我不打算要求它 :-)
    • Remy 的解决方案非常好。通过使用Level 属性,它避免了对额外列表的需要。我想我仍然会用列表来做,因为这可能更容易理解,但会牺牲一个额外的对象。
    • TTreeNode.Level 属性是动态计算的,因此一项优化是至少在每次ParentNode 更改时缓存当前的Level 值。
    猜你喜欢
    • 2023-03-12
    • 1970-01-01
    • 2013-01-22
    • 1970-01-01
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 2013-08-09
    • 1970-01-01
    相关资源
    最近更新 更多