Эффективный способ связать ListView с иерархической структурой данных? - PullRequest
1 голос
/ 17 февраля 2011

Чтобы создать плоскую структуру в GridView ListView?(та же коллекция уже привязана к древовидному представлению, поэтому она находится в иерархической структуре, и уже есть много методов, которые манипулируют данными в этой структуре, поэтому я бы предпочел оставить все как есть).* Данные выглядят так:

class Node
{
  ObservableCollection<Node> Children;
  ...
}

На верхнем уровне все они содержатся в самой коллекции:

ObservableCollection<Node> nodes;

Теперь я хочу, чтобы все дети были на определенном уровнеможет быть во многих ветках) в моем представлении списка ... Один из способов - сохранить клонированную копию, но это выглядит ужасно неэффективно, я просто хочу привязать к той же коллекции.

Ответы [ 5 ]

3 голосов
/ 17 февраля 2011

То, что вы пытаетесь сделать здесь, сложно. Сглаживать иерархию несложно - довольно просто создать метод, который обходит дерево из T объектов и возвращает IEnumerable<T>. Но то, что вы хотите, гораздо сложнее: вы хотите, чтобы плоский список поддерживался синхронно с деревом.

Это возможно сделать. Вы можете, по крайней мере, в принципе, чтобы каждый узел в иерархии знал свою позицию в сглаженном списке, а затем преобразовывать события CollectionChanged на своих дочерних элементах в нечто, с чем может справиться сглаженный список. Это может сработать, если вы выполняете только операции добавления и удаления одного элемента.

Есть намного более простой способ. Не используйте ListView. Выведите свои данные в HeaderedItemsControl и используйте HierarchicalDataTemplate, как описано в ответе на на этот вопрос . Только не устанавливайте левое поле на ItemsPresenter. Это представит все элементы в одном столбце. Вы будете знать, что некоторые предметы - это родители, а некоторые - дети, но пользователь этого не сделает.

Если вам нужен столбчатый макет, используйте Grid в качестве шаблона и используйте области общего размера для управления шириной столбцов.

1 голос
/ 17 февраля 2011

Поддержание новой коллекции, которую все узлы добавляют / удаляют, когда изменения их ChildrenCollection кажутся наилучшими. Можно поймать событие Node's Children's CollectionChanged:

    void ChildrenCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // ASSUMPTION: only one item is ever added/removed so will be at NewItems[0]/OldItems[0]

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add: nodes.AllChildren.Add(e.NewItems[0]);break;
            case NotifyCollectionChangedAction.Remove: nodes.AllChildren.Remove(e.OldItems[0]); break;
            case NotifyCollectionChangedAction.Replace:
                {
                    int i = nodes.AllChildren.IndexOf(e.OldItems[0]);
                    nodes.AllChildren.RemoveAt(i);
                    nodes.AllChildren.Insert(i, e.NewItems[0]);
                }
                break;
            case NotifyCollectionChangedAction.Reset:
                {
                    nodes.AllChildren.Clear();
                    foreach (Node n in this.ChildrenCollection)
                        nodes.AllChildren.Add(n);
                }
                break;
            // NOTE: dont't care if it moved
        }
    }

Где узлы - это ссылка на коллекцию верхнего уровня.

Затем вы можете привязать свой ListView.ItemsSource к AllChildren, который, если это ObervableCollection, останется в актуальном состоянии!

ПРИМЕЧАНИЕ : Если свойства в узле изменятся, они не будут отражены в коллекции AllChildren - только добавление / удаление и замена узлов в одной коллекции ChildrenCollection будут реплицироваться в коллекции AllChildren. .

ПРИМЕЧАНИЕ II : Вы должны быть осторожны, чтобы, прежде чем вы могли просто заменить узел в дереве, таким образом, чтобы выровнять всю ветку ниже, вы должны сначала удалить все глубины в этом узле. ветвь, так что "зеркальная" коллекция AllChildren тоже обновляется!

0 голосов
/ 17 февраля 2011

Добавьте метод к вашему DataContext, который возвращает IEnumerable, и привяжите его к вашему ListView.

В новом методе вернуть результат запроса LINQ иерархического сбора данных.У вас не будет «теневой копии» коллекции, если вы не .ToList() свои результаты, и вы будете получать актуальные результаты, если ваши наблюдаемые коллекции или INotifyCollectionChanged события будут правильно реализованы.

0 голосов
/ 17 февраля 2011

Вы можете создать рекурсивный метод, который yield возвращает IEnumerable, и создать свойство, которое возвращает значение этого метода. Не уверен, что приведенный ниже пример будет работать, но это идея:

public Node MainNode { get; set;}

public IEnumerable<Node> AllNodes
{
    get { return GetChildren(MainNode); }
}

public IEnumerable<Node> GetChildren(Node root)
{
    foreach (Node node in root.Nodes)
    {
        if (node.Nodes.Count > 0)
            yield return GetChildren(node) as Node;

        yield return node;
    }
}

Другой вариант, основанный на той же идее, заключается в том, что свойство AllNodes вызывает метод, который рекурсивно загружает все узлы в плоском списке, а затем возвращает этот список (если вы не хотите использовать возвращаемый выход):

public ObservableCollection<Node> AllNodes
{
    get
    {
        ObservableCollection<Node> allNodes = new ObservableCollection<Node>();

        foreach(Node node in GetChildren(this.MainNode))
            allNodes.Add(node);

        return allNodes;
    }
}
0 голосов
/ 17 февраля 2011

Редактировать: Для эффективного выравнивания CompositeCollection было бы весьма полезным для меня.


Я бы использовал преобразователь значения , тогда ваш источник привязки может остаться прежним.

Редактировать: Преобразователь может выглядеть примерно так (непроверено!):

[ValueConversion(typeof(ObservableCollection<Node>), typeof(List<Node>))]
public class ObservableCollectionToListConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        ObservableCollection<Node> input = (ObservableCollection<Node>)value;
        int targetLevel = int.Parse(parameter as string);
        List<Node> output = new List<Node>();

        foreach (Node node in input)
        {
            List<Node> tempNodes = new List<Node>();
            for (int level = 0; level < targetLevel; level++)
            {
                Node[] tempNodesArray = tempNodes.ToArray();
                tempNodes.Clear();
                foreach (Node subnode in tempNodesArray)
                {
                    if (subnode.Children != null) tempNodes.AddRange(subnode.Children);
                }
            }
            output.AddRange(tempNodes);
        }

        return output;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Выбудет использовать это так в Xaml:

<Window.Resources>
    ...
    <local:ObservableCollectionToListConverter x:Key="ObservableCollectionToListConverter"/>
</Window.Resources>
...
<ListView ItemsSource="{Binding MyNodes, Converter={StaticResource ObservableCollectionToListConverter}, ConverterParameter=3}">

(ConverterParameter указывает уровень)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...