将ListView绑定到分层数据结构的有效方法?

时间:2011-02-17 00:23:31

标签: wpf binding

在ListView的GridView中生成平面结构? (相同的集合已经绑定到树视图,这就是它在层次结构中的原因,并且已经有很多方法可以操作此结构中的数据,所以我宁愿保持原样)。

数据如下所示:

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

在顶层,它全部包含在一个集合中:

ObservableCollection<Node> nodes;

现在我希望所有的孩子都在我的列表视图中的某个级别(但可能在许多分支中)...一种方式似乎是维护克隆副本但它看起来非常无效,我只想绑定到相同的集合。

5 个答案:

答案 0 :(得分:3)

你在这里要做的很难。扁平化层次结构并不难 - 构建一个遍历T个对象树并返回IEnumerable<T>的方法非常容易。但是你想要的更难:你希望扁平列表与树保持同步。

可以这样做。原则上,您至少可以让层次结构中的每个节点都知道它在展平列表中的位置,然后将其子节点上的CollectionChanged事件转换为扁平列表可以处理的内容。如果您只处理单项添加和删除操作,那可能会有效。

有一个很多更简单的方法。不要使用ListView。在HeaderedItemsControl中显示您的数据,并使用HierarchicalDataTemplate,如this question的答案中所述。只是不要在ItemsPresenter上设置左边距。这将在单个列中显示所有项目。你会知道有些项目是父母,有些是儿童,但用户不会。

如果需要柱状布局,请使用Grid作为模板,并使用共享大小范围来控制列宽。

答案 1 :(得分:1)

维护所有节点在其ChildrenCollection更改时添加/删除的新集合似乎是最佳的。人们可以捕获Node的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
        }
    }

其中'nodes'是对顶级集合的引用。

然后,您可以将ListView.ItemsSource绑定到AllChildren,如果它是ObervableCollection将保持最新!

注意:如果节点中的属性发生变化,它们将不会反映在AllChildren集合中 - 它只是添加/删除和替换一个ChildrenCollection中的节点,它们将在AllChildren中复制自身集合。

注意II :你必须要小心,只需要替换树中的节点,从而为下面的整个分支进行操作,你现在必须先深度删除所有节点。那个分支所以“镜像”AllChildren集合也被更新了!

答案 2 :(得分:0)

编辑:为了有效展平,CompositeCollection对我来说非常有用。


我会使用value converter,然后您的绑定源可以保持不变。

编辑:转换器可能看起来像这样(未经测试!):

[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指定级别)

答案 3 :(得分:0)

DataContext添加一个返回IEnumerable并将其绑定到ListView的方法。

在新方法中,返回分层数据集合的LINQ查询结果。只要您没有.ToList()结果,您就不会拥有该集合的“影子副本”,只要您的可观察集合或INotifyCollectionChanged,您就会获得最新结果事件得到妥善实施。

答案 4 :(得分:0)

您可以创建一个递归方法,该方法返回一个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属性调用一个方法,该方法以递归方式加载平面列表中的所有节点,然后返回该列表(如果您不想使用yield return):

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

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

        return allNodes;
    }
}