WPF - Хороший способ вывести список на дерево - PullRequest
4 голосов
/ 21 января 2010

У меня есть список, который выглядит так:

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 ]

5 голосов
/ 21 января 2010

Это возьмет ваш список строк и превратит его в дерево, подходящее для просмотра с 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. Я обычно использую первое решение (анонимные типы), если я не хочу делать что-то особенное с узлами дерева.

2 голосов
/ 21 января 2010

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 для каждого класса

1 голос
/ 14 февраля 2013

Более общей реализацией может быть эта. Представьте себе класс 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>.

НТН

...