WPF - 将列表带到树的好方法

Posted

技术标签:

【中文标题】WPF - 将列表带到树的好方法【英文标题】:WPF - Good Way to take a list to a Tree 【发布时间】:2011-01-07 12:51:06 【问题描述】:

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

基础/Level1/Item1 基础/Level1/Item2 基础/Level1/Sub1/Item1 基础/Level2/Item1 基础/Level3/Sub1/Item1

我想要一种简单的方法将其放入 ListView。 (即与此类似)

根据 | +->1 级 | | | +=项目1 | +=项目2 | | | +->子1 | | | +=项目1 | +->2 级 | | | +=项目1 | +->Level3 | +->子1 | +=项目1

是否有一种既定的方法来进行这种转换,还是我只需要滚动我自己的解析器?

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

【问题讨论】:

你是说你没有分层数据结构?只是一个平面列表? 好吧,我将它从一个超出我理解的 XML 结构中提取出来。所以是的,我有一个平面列表。 (把它想象成一个我想在 TreeView 中显示的文件路径列表) 【参考方案1】:

这将获取您的字符串列表并将其转换为适合使用 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 属性。这可以通过指定HierarchicalDataTemplateItemsSource="Binding Children" 和由&lt;TextBlock Text="Binding Name"&gt; 或类似内容组成的内容直接绑定到TreeView

如果您需要额外的成员或语义,您也可以在代码中定义树节点类。例如,给定这个节点类:

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 直接绑定。我通常使用第一种解决方案(匿名类型),除非我想对树节点做一些特殊的事情。

【讨论】:

【参考方案2】:

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

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

class Node

    public string Name  get; set; 
    public List<Node> Children  get; set; 

将您的平面数据解析为带有子节点的Node 对象列表,并在资源中使用以下HierarchicalDataTemplate 创建一个TreeView

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

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

【讨论】:

【参考方案3】:

更通用的实现可能是这个。想象一个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&lt;T&gt; 扩展方法:

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&lt;TItem, TKey&gt; 包装类。

HTH

【讨论】:

以上是关于WPF - 将列表带到树的好方法的主要内容,如果未能解决你的问题,请参考以下文章

将项目从一个列表带到另一个列表的更清洁方法

将 str 连接到列表的好方法?

将列表列表分为两部分的好方法,以内部列表中的值为条件

将许多参数传递给控制器​​的好方法是啥?

使用 MVVM 在 wpf 中使用 Dialogs 的好做法还是坏做法?

什么是处理“打盹”功能的好方法?