2011-02-17 147 views
1

要在ListView的GridView中生成一个扁平结构? (同一个集合已经绑定到一个treeview,这就是为什么它在一个Hierarchical Structure中,并且已经有很多方法来处理这个结构中的数据,所以我宁愿保留它)。将ListView绑定到分层数据结构的有效方法?

的数据是这样的:

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

在顶层它包含的所有集合本身:

ObservableCollection<Node> nodes; 

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

回答

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 
     } 
    } 

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

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

注意:如若属性中的一个节点的变化,他们将不会反映在AllChildren集合中 - 它是一个节点在一个只有添加/移除和更换ChildrenCollection的,将自我复制的AllChildren收藏。

NOTE II:你要小心,你可以只更换一个节点之前在树中,从而forfieting下的整个分支,你现在要做的深度第一去除所有节点中的一个分支,所以“镜像”AllChildren集合也被更新!

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

+0

与其他两个相同的问题,通过创建副本,我怎么知道何时创建副本,每次添加或删除节点?那么当添加1000个节点时,绑定将被更新1000次,调用转换器1000次,创建所有节点的新副本999不必要的时间 – markmnl 2011-02-17 01:07:21

+0

那么,我个人不会处理这些问题,直到它实际上*成为一个问题的表现。你可以添加一个布尔属性到转换器打开或关闭。我怀疑有没有更简单的方法来做这样的事情,你可能可以改进某些领域,但最终你的产品将永远是一个由合并列表组成的列表。 – 2011-02-17 01:34:52

+0

对不起,但是创建了一个999次列表的新副本,并且仅当添加1000个项目并且仅使用创建的第1000个副本时才会处理该副本是不可接受的 - 我可以有超过1000个项目。打开和关闭转换器不是一种选择,因为我不知道可能要添加多少物品。顺便说一下,我倾向于仅使用转换器设计的转换器:在不同类型的目标和源之间转换对象,如果从方法填充,最好使用ObjectDataProvider。 – markmnl 2011-02-17 02:07:41

0

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

在新方法中,返回分层数据集合的LINQ查询的结果。只要您不会收到结果的“影子副本”,只要您的可观察集合或INotifyCollectionChanged事件得到正确实施,就会获得最新结果。

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中属性调用递归加载所有节点在一个平面列表的方法,然后返回列表(如果你不希望使用产返程):

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

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

     return allNodes; 
    } 
} 
2

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

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

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

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