2011-02-17 146 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作爲模板,並使用共享尺寸範圍控制列寬。