WPF - 将名单列入树的好方法

时间:2010-01-20 23:43:03

标签: c# wpf list listview tree

我有一个如下所示的列表:

Base/Level1/Item1
Base/Level1/Item2
Base/Level1/Sub1/Item1
Base/Level2/Item1
Base/Level3/Sub1/Item1

我想要一个简单的方法将它放入ListView。 (即类似于此)

Base
  |
  +->Level1
  |    |
  |    +=Item1
  |    +=Item2
  |    |
  |    +->Sub1
  |        |
  |        +=Item1
  |
  +->Level2
  |    |
  |    +=Item1

  |
  +->Level3
       |
       +->Sub1
           |
           +=Item1

有没有一种既定的方法来进行这种转换,还是只需要自己解析一下?

(如果它可能是相关的,我的代码中的实际项目是TFS迭代路径。)

3 个答案:

答案 0 :(得分:5)

这将获取您的字符串列表并将其转换为适合使用TreeView查看的树:

public IList BuildTree(IEnumerable<string> strings)
{
  return
    from s in strings
    let split = s.Split("/")
    group s by s.Split("/")[0] into g  // Group by first component (before /)
    select new
    {
      Name = g.Key,
      Children = BuildTree(            // Recursively build children
        from s in grp
        where s.Length > g.Key.Length+1
        select s.Substring(g.Key.Length+1)) // Select remaining components
    };
}

这将返回一个匿名类型的树,每个树包含一个Name属性和一个Children属性。这可以直接绑定到TreeView,方法是指定HierarchicalDataTemplate ItemsSource="{Binding Children}",内容包含<TextBlock Text="{Binding Name}">或类似内容。

或者,如果您需要其他成员或语义,可以在代码中定义树节点类。例如,给定此节点类:

public class Node 
{
  public string Name { get; set; } 
  public List<Node> Children { get; set; } 
}

你的BuildTree功能会略有不同:

public List<Node> BuildTree(IEnumerable<string> strings)
{
  return (
    from s in strings
    let split = s.Split("/")
    group s by s.Split("/")[0] into g  // Group by first component (before /)
    select new Node
    {
      Value = g.Key,
      Children = BuildTree(            // Recursively build children
        from s in grp
        where s.Length > g.Key.Length+1
        select s.Substring(g.Key.Length+1)) // Select remaining components
    }
    ).ToList();
}

同样可以使用HierarchicalDataTemplate直接绑定。我通常使用第一个解决方案(匿名类型),除非我想对树节点做一些特别的事情。

答案 1 :(得分:2)

WPF TreeView可以使用HierarchicalDataTemplates显示分层数据。但是,目前您的数据是平的,因此您必须将其转换为分层数据结构。没有内置的方法可以帮助你......

例如,您可以创建一个类:

class Node
{
    public string Name { get; set; }
    public List<Node> Children { get; set; }
}

将您的单位数据解析为包含子节点的Node个对象列表,并在资源中创建一个TreeView,其中包含以下HierarchicalDataTemplate

<TreeView ItemsSource="{Binding ListOfNodes}">
  <TreeView.Resources>
    <HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Children}">
      <TextBlock Text="{Binding Name}" />
    </HierarchicalDataTemplate>
  </TreeView.Resources>
</TreeView>

将根据您的数据自动生成树节点。如果您需要为层次结构的不同级别使用不同的类,请为每个类创建不同的HierarchicalDataTemplate

答案 2 :(得分:1)

更通用的实现可能就是这个。想象一下Node类定义为:

public class Node<TItem, TKey>
{
    public TKey Key { get; set; }
    public int Level { get; set; }
    public IEnumerable<TItem> Data { get; set; }
    public List<Node<TItem, TKey>> Children { get; set; }
}

和两个通用IEnumerable<T>扩展方法:

public static List<Node<TItem, TKey>> ToTree<TItem, TKey>(this IEnumerable<TItem> list, params Func<TItem, TKey>[] keySelectors)
{
    return list.ToTree(0, keySelectors);
}

public static List<Node<TItem, TKey>> ToTree<TItem, TKey>(this IEnumerable<TItem> list, int nestingLevel, params Func<TItem, TKey>[] keySelectors)
{
    Stack<Func<TItem, TKey>> stackSelectors = new Stack<Func<TItem, TKey>>(keySelectors.Reverse());
    if (stackSelectors.Any())
    {
        return list
            .GroupBy(stackSelectors.Pop())
            .Select(x => new Node<TItem, TKey>()
            {
                Key = x.Key,
                Level = nestingLevel,
                Data = x.ToList(),
                Children = x.ToList().ToTree(nestingLevel + 1, stackSelectors.ToArray())
            })
            .ToList();
        }
        else
        {
            return null;
        }

您可以使用这些方法将用户对象的平面列表聚合到树中,具有任意聚合级别和更优雅的语法。唯一的限制是聚合键必须是相同的类型。

示例:

class A
{
    public int a { get;set; }
    public int b { get;set; }
    public int c { get;set; }
    public int d { get;set; }
    public string s { get;set; }

    public A(int _a, int _b, int _c, int _d, string _s)
    {
        a = _a;
        b = _b;
        c = _c;
        d = _d;
        s = _s;     
    }
}

void Main()
{
    A[] ls = {
        new A(0,2,1,10,"one"),
        new A(0,1,1,11,"two"),
        new A(0,0,2,12,"three"),
        new A(0,2,2,13,"four"),
        new A(0,0,3,14,"five"),
        new A(1,0,3,15,"six"),
        new A(1,1,4,16,"se7en"),
        new A(1,0,4,17,"eight"),
        new A(1,1,5,18,"nine"),
        new A(1,2,5,19,"dunno")
    };

    var tree = ls.ToTree(x => x.a, x => x.b, x => x.c, x => x.d);

}

注意:此实现不是“真正的”树,因为没有单个根节点,但您可以非常轻松地实现Tree<TItem, TKey>包装类。

HTH