LINQ获取TreeView中最深层级的节点

5
假设我有一个WinForms Treeview,如下所示:
Parent1
   Child1
      Sub-Child1
         DeepestNode1
         DeepestNode2
         DeepestNode3
      Sub-Child2
         DeepestNode4
         DeepestNode5
         DeepestNode6
   Child2
      Sub-Child3
      Sub-Child4
      Sub-Child5
      Sub-Child6
   Child3
      (no children)

我想创建一个类似以下函数的功能:
Function GetDeepestChildren(MyNode as Treenode) as List(Of Treenode)

在这里,如果结果看起来像:

GetDeepestChildren(Parent1) = {DeepestNode1, DeepestNode2, DeepestNode3, DeepestNode4, DeepestNode5, DeepestNode6}

GetDeepestChildren(Sub-Child1) = {DeepestNode1, DeepestNode2, DeepestNode3}

GetDeepestChildren(Child2) = {Sub-Child3, Sub-Child4, Sub-Child5, Sub-Child6}

GetDeepestChildren(Child3) = Empty list

换言之,始终从给定节点尽可能深入,并返回子级 - 即使它们分散在不同的父级之间(如Parent1中的情况)。
我已创建一个函数来告诉我节点向下走了多少个层次,如下所示:
    Public Function GetDeepestChildNodeLevel(ByVal ParentNode As TreeNode) As Integer
        Dim subLevel = ParentNode.Nodes.Cast(Of TreeNode).Select(Function(subNode) GetDeepestChildNodeLevel(subNode))
        Return If(subLevel.Count = 0, 0, subLevel.Max() + 1)
    End Function

所以我知道从哪个层级获取孩子,我需要的是一个能够做到这一点的函数 - 大致上是这样的:

Function GetDeepestChildren(MyNode as Treenode) as List(Of Treenode)
       Return All child nodes where level = GetDeepestChildNodeLevel(MyNode)
End function

我希望你能翻译这个意思 - 谢谢!

1
@Pete OP加[C#]标签是有原因的,请不要移除它。 - Sergey Kalinichenko
我看到过很多情况,发帖人在问题中放置了不相关的语言标签。我无法找到原始帖子中与C#有关的任何内容。发帖人应该在帖子中进行更多的联系,而不仅仅是标签。否则像我这样的人可能会将其删除,认为它与问题无关。 - Pete
7个回答

4

在 C# 中,您可以使用 yield return 或递归 Lambda 来实现。以下是第二种方法的示例:

Func<TreeNode,IEnumerable<TreeNode>> getChildren = null;
getChildren = n => {
    if (n.Nodes.Count != 0) {
        var list = new List<TreeNode>(n.Nodes.Where(c => c.Nodes.Count == 0));
        foreach (var c in n.Nodes) {
            // Note the recursive call below:
            list.AddRange(getChildren(c));
        }
        return list;
    } else {
        return new TreeNode[0];
    }
};
var res = getChildren(myTree);

非常感谢您提供的解决方案。但是,“HasChildren”属性 - 它在WinForms中存在吗?因为我一直在收到错误提示。 - John Bustos
@JohnBustos 抱歉,我漏掉了winforms部分。现在应该可以编译了。 - Sergey Kalinichenko
@Hogan 原始问题已经被标记为 C#VB.NET。有人删除了 OP 放置的标签,我重新添加了它。 - Sergey Kalinichenko
@dasblinkenlight - 它完美地运行了 - 谢谢!我稍微修改了一下,并将其转换为VB.Net,作为另一种解决方案发布,以防有人需要VB...再次感谢你!!! - John Bustos
@Hogan, dasblinkenlight - 我最初确实加入了C#标签(并且非常高兴我这样做了)- 将代码从C#转换并不是我的问题,dasblinkenlight的解决方案正是我所需要的!! - John Bustos

1
这里有一个使用XML的版本--翻译应该很容易。我使用linqPad,我推荐这种工具来处理这种东西,你可以在LinqPad中直接运行并查看它的工作。
WalkDeep(tree,getDeep(tree)) returns:

<DeepestNode1 /> 
<DeepestNode2 /> 
<DeepestNode3 /> 
<DeepestNode4 /> 
<DeepestNode5 /> 
<DeepestNode6 /> 

C#代码更好看,因为你可以使用yield。
VB代码。
function getDeep( e as XElement) as integer
  if (e.HasElements)
    return 1 + e.Elements().Select(Function(c) getDeep(c)).Max()
  else
    return 1
  end if  
end function

function WalkDeep(root as XElement,find as integer,optional mylevel as integer = 1) as IEnumerable(of XElement)
  Dim result As New List(Of XElement)

  if find = mylevel 
    result.Add(root)
  else 
    if root.HasElements
      for each c as XElement in root.Elements()
        for each r as XElement in WalkDeep(c,find,mylevel+1)
            result.Add(r)
        next
      next  
    end if
  end if

  return result
end function

Sub Main
  dim tree as XElement = <Parent1>
     <Child1>
        <Sub-Child1>
           <DeepestNode1/>
           <DeepestNode2/>
           <DeepestNode3/>
        </Sub-Child1>   
        <Sub-Child2>
           <DeepestNode4/>
           <DeepestNode5/>
           <DeepestNode6/>
        </Sub-Child2>   
     </Child1>      
     <Child2>
        <Sub-Child3/>
        <Sub-Child4/>
        <Sub-Child5/>
        <Sub-Child6/>
     </Child2>   
     <Child3 />
  </Parent1>   

  WalkDeep(tree,getDeep(tree)).Select(function(x) x.Name.LocalName).Dump()
End Sub

C# 代码:

int getDeep(XElement e)
{
  if (e.HasElements)
    return 1 + e.Elements().Select(c => getDeep(c)).Max();
  else
    return 1;
}

IEnumerable<XElement> WalkDeep(XElement root,int find, int mylevel=1)
{   
  if (find == mylevel) yield return root;

  if (root.HasElements)
  {
    foreach(XElement c in root.Elements())
    {
      foreach(XElement r in WalkDeep(c,find,mylevel+1))
        yield return r;

    }
  }

  yield break;
}

void Main()
{
  XElement tree = XElement.Parse (@"
  <Parent1>
     <Child1>
        <Sub-Child1>
           <DeepestNode1/>
           <DeepestNode2/>
           <DeepestNode3/>
        </Sub-Child1>   
        <Sub-Child2>
           <DeepestNode4/>
           <DeepestNode5/>
           <DeepestNode6/>
        </Sub-Child2>   
     </Child1>      
     <Child2>
        <Sub-Child3/>
        <Sub-Child4/>
        <Sub-Child5/>
        <Sub-Child6/>
     </Child2>   
     <Child3 />
  </Parent1>   
  ");

  WalkDeep(tree,getDeep(tree)).Dump();
} 

除非 VB.NET 有所改变,否则 yield 关键字不存在。 - Fredou
@Fredou,现在有一个VB版本,你的评论只是针对C#版本。 - Hogan

1
这是我用VB.Net重新制作的@dasblinkenlight解决方案 - 它完美地运行,我只是把它放在这里,以防将来有人需要VB中的解决方案。
    Public Function GetDeepestChildNodes(ByVal ParentNode As TreeNode) As List(Of TreeNode)
        Dim RetVal As New List(Of TreeNode)

        If ParentNode.Nodes.Count > 0 Then
            RetVal = (From nd As TreeNode In ParentNode.Nodes
                   Where nd.Nodes.Count = 0
                   Select nd).ToList

            For Each nd In ParentNode.Nodes
                RetVal.AddRange(GetDeepestChildNodes(nd))
            Next
        End If

        Return RetVal
    End Function

再次感谢大家的帮助!


0

尝试过递归函数吗?不确定在VB.Net中怎么写,但在C#中会是这样的

public List<TreeNode> GetDeepestChildren(Treenode MyNode)
{
   if (MyNode.Children.Count > 0)
        GetDeepestChildren(Treenode MyNode);
   else
        return MyNode.Children;
}

这段代码尚未编译或测试,但思路已经在那里,应该能够返回任何给定节点的最深子节点。


这样做不行 - 它总是会返回一个具有零个元素的“Children”列表。你需要两个级别的“间接”。 - Hogan
还有,您可能想要返回 return GetDeepestChildren(Treenode MyNode); - Hogan

0

我不熟悉TreeView控件,但是TreeNode的Level属性对您有用吗?

http://msdn.microsoft.com/en-us/library/system.windows.forms.treenode.level.aspx

如果你了解最深层次,你可以做到这一点:
C#
private List<TreeNode> GetDeepestChildren(int level)
{
    return (from p in treeView1.Nodes.Cast<TreeNode>() where p.Level == level select p).ToList();
}

VB

Private Function GetDeepestChildren(level As Integer) As List(Of TreeNode)
    Return (From p In treeView1.Nodes.Cast(Of TreeNode)() Where p.Level = levelp).ToList()
End Function

格雷格。


谢谢 Greg,这绝对是朝着正确的方向迈出的一步。我的挑战在于确保返回的节点只来自于一个父节点……我在考虑使用 FullPath 属性,并检查其是否包含父节点的名称,但我担心会有两个同名的节点。 - John Bustos

0

这只是对 @dasblinkenlight 的回答进行了轻微的修改,所以请不要点赞!

只是个人风格问题,但我更喜欢将递归调用写成这样:

IEnumerable<TreeNode> WalkNodes(TreeNode root)
{   
    yield return root;
    var children = root.Nodes.Cast<TreeNode>();
    foreach (var child in children)
    {
        foreach(var subChild in WalkNodes(child))
        {
            yield return subChild;
        }
    }
}

并通过以下方式调用:

foreach (var node in treeView.Nodes.Cast<TreeNode>())
{
    var walkedFrom = WalkNodes(node);
    foreach (var subNode in walkedFrom)
    {
        Console.WriteLine(subNode.Text);
    }
}

除非VB.NET有所改变,否则yield不存在。 - Fredou
@Fredou 啊,错过了没有C#标签...奇怪,它一开始是带一个吗?我通常会过滤掉vb.net的问题... - JerKimball

0

这不是 LINQ,因为我认为在这种情况下不应该使用 LINQ。如果这不是你想要的,请原谅。这种方法并不完美,但至少当你遇到一个复杂的树时,你不会遇到堆栈溢出的问题。

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    Dim test1 = GetDeepestChildren(TreeView1.Nodes(0))
    Dim test2 = GetDeepestChildren(TreeView1.Nodes(0).Nodes(0).Nodes(0))
    Dim test3 = GetDeepestChildren(TreeView1.Nodes(0).Nodes(1))
    Dim test4 = GetDeepestChildren(TreeView1.Nodes(0).Nodes(2))
End Sub

Private Function GetDeepestChildren(ByVal node As TreeNode) As List(Of TreeNode)
    Dim deepestList As New List(Of TreeNode)

    If node.Nodes.Count = 0 Then
        Return deepestList
    End If

    Dim nodes As New Stack(Of TreeNode)
    For Each n As TreeNode In node.Nodes
        nodes.Push(n)
    Next

    Dim deepest As Integer = 0
    Do Until nodes.Count = 0
        node = nodes.Pop
        If node.Nodes.Count = 0 Then
            If deepest < node.Level Then
                deepest = node.Level
                deepestList.Clear()
                deepestList.Add(node)
            ElseIf deepest = node.Level Then
                deepestList.Add(node)
            End If
        Else
            For Each n As TreeNode In node.Nodes
                nodes.Push(n)
            Next
        End If
    Loop

    Return deepestList
End Function

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接