2011-03-15 58 views
10

摘要定製的ObservableCollection <T>或的BindingList <T>具有用於定期通知

支撐我有一個大的迅速變化的數據集,其我想結合的UI(用數據網格分組)。這些變化分兩個層次。

  • 物品經常添加或刪除從集合(500第二每一路)
  • 每一個項目都有一個4個屬性,這將向上在其一生中更改爲5倍

的特性數據如下;

  • 有收集
  • 可以內的第二,被添加的項目在〜5000項然後有5條屬性的修改,然後被去除。
  • 的項目也可以保留在一些中間狀態的同時,應顯示給用戶。

這我有問題關鍵的要求;

  • 用戶應該能夠通過任何財產數據集進行排序的對象

我想什麼做什麼;

  • 更新UI僅每Ñ
  • 擡起只有相關NotifyPropertyChangedEvents

如果第1項具有屬性State其從A 移動 - >乙 - 「ç - > d在 間隔,我需要/想提高只有一個「國家」的變化 事件,A-> d。

我很欣賞用戶不需要每秒更新數千次UI。如果添加了一個項目,其狀態已更改,並且在UI更新之間N秒的窗口內全部刪除,它不應該擊中DataGrid。

數據網格

數據網格是我使用來顯示數據的組件。我目前使用XCeed DataGrid,因爲它提供了動態分組。我沒有情緒上的投資,如果我可以提供一些動態分組選項(其中包括頻繁更改的屬性),股票DataGrid會很好。

在我的系統的瓶頸是 目前採取的重新排序 的時間,當一個項目的性質發生變化

這需要CPU的98%,在YourKit探查。

一種不同的方式表達出該問題

給定兩個的BindingList /的ObservableCollection其最初相同的情況下 但 第一清單從那以後有一系列 額外的更新(您可以 聽for),生成最小集合 的變更,將其中一個列表變成 其他。

外部讀取

我需要的是由喬治·Tryfonas的這個ArrayMonitor的等價的,但推廣到支持添加和項目的去除(他們將永遠不會被移動)。

注意:如果能想到更好的總結,我會很感激有人編輯問題的標題。

編輯 - 我的解決辦法

的XCeed網格結合細胞直接而排序&分組功能是通過在升高的BindingList的ListChangedEvents從動在網格中的項目。這有點違反直觀性,並排除了下面的MontioredBindingList,因爲行將在組之前更新。

取而代之,我自己包裝物品,捕捉Property改變的事件並按照Daniel的建議將它們存儲在HashSet中。這適用於我,我週期性地迭代這些項目並要求他們通知任何更改。

MonitoredBindingList.cs

這是我在這可以輪詢更新通知的綁定列表嘗試。有可能有一些錯誤,因爲它最終對我毫無用處。

它創建一個添加/刪除事件隊列並通過列表跟蹤更改。 ChangeList與底層列表的順序相同,以便在我們通知添加/刪除操作後,您可以針對正確的索引提出更改。

/// <summary> 
/// A binding list which allows change events to be polled rather than pushed. 
/// </summary> 
[Serializable] 

public class MonitoredBindingList<T> : BindingList<T> 
{ 
    private readonly object publishingLock = new object(); 

    private readonly Queue<ListChangedEventArgs> addRemoveQueue; 
    private readonly LinkedList<HashSet<PropertyDescriptor>> changeList; 
    private readonly Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>> changeListDict; 

    public MonitoredBindingList() 
    { 
     this.addRemoveQueue = new Queue<ListChangedEventArgs>(); 
     this.changeList = new LinkedList<HashSet<PropertyDescriptor>>(); 
     this.changeListDict = new Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>>(); 
    } 

    protected override void OnListChanged(ListChangedEventArgs e) 
    { 
     lock (publishingLock) 
     { 
      switch (e.ListChangedType) 
      { 
       case ListChangedType.ItemAdded: 
        if (e.NewIndex != Count - 1) 
         throw new ApplicationException("Items may only be added to the end of the list"); 

        // Queue this event for notification 
        addRemoveQueue.Enqueue(e); 

        // Add an empty change node for the new entry 
        changeListDict[e.NewIndex] = changeList.AddLast(new HashSet<PropertyDescriptor>()); 
        break; 

       case ListChangedType.ItemDeleted: 
        addRemoveQueue.Enqueue(e); 

        // Remove all changes for this item 
        changeList.Remove(changeListDict[e.NewIndex]); 
        for (int i = e.NewIndex; i < Count; i++) 
        { 
         changeListDict[i] = changeListDict[i + 1]; 
        } 

        if (Count > 0) 
         changeListDict.Remove(Count); 
        break; 

       case ListChangedType.ItemChanged: 
        changeListDict[e.NewIndex].Value.Add(e.PropertyDescriptor); 
        break; 
       default: 
        base.OnListChanged(e); 
        break; 
      } 
     } 
    } 

    public void PublishChanges() 
    { 
     lock (publishingLock) 
      Publish(); 
    } 

    internal void Publish() 
    { 
     while(addRemoveQueue.Count != 0) 
     { 
      base.OnListChanged(addRemoveQueue.Dequeue()); 
     } 

     // The order of the entries in the changeList matches that of the items in 'this' 
     int i = 0; 
     foreach (var changesForItem in changeList) 
     { 
      foreach (var pd in changesForItem) 
      { 
       var lc = new ListChangedEventArgs(ListChangedType.ItemChanged, i, pd); 
       base.OnListChanged(lc); 
      } 
      i++; 
     } 
    } 
} 

回答

5

我們在這裏所說的兩件事情:

  1. 到集合中的變化。這引發了事件INotifyCollectionChanged.CollectionChanged
  2. 項目屬性的更改。這引發了事件INotifyPropertyChanged.PropertyChanged

接口INotifyCollectionChanged需要由您的自定義集合實現。界面INotifyPropertyChanged需要由您的項目實施。此外,PropertyChanged事件只會告訴您某個項目上的哪個屬性發生了更改,而不是之前的值。
這意味着,您的項目需要有一個是這樣的一個實現:

  • 有運行每ň
  • 創建一個HashSet<string>包含已全部屬性的名稱定時器改變。因爲它是一個集合,每個屬性只能被包含一次或零次。
  • 如果屬性發生更改,請將其名稱添加到散列集中(如果它尚未包含在其中)。
  • 當計時器過去時,請提高散列集中所有屬性的PropertyChanged事件,然後再清除它。

您的收藏將具有類似的實現。然而,它有點難度,因爲你需要考慮在計時器事件之間添加和刪除的項目。這意味着,添加項目時,您可以將其添加到散列集「addedItems」中。如果一個項目被刪除,你將它添加到「removedItems」哈希集合,如果它是不是已經在「addedItems」。如果它已經在「addedItems」中,請將其從中刪除。我想你會得到這張照片。

要堅持關注點分離和單一職責的原則,最好讓您的項目以默認方式實現INotifyPropertyChanged並創建一個合併事件的包裝器。那有你的項目不會與不屬於那裏的代碼雜亂的優勢,並在包裝​​可以製成通用的,用於每個實現INotifyPropertyChanged類。
集合也是如此:您可以爲實現INotifyCollectionChanged的所有集合創建通用包裝,並讓包裝完成事件的合併。

+0

喜丹尼爾 - 感謝這樣一個快速和深入的響應。儘管你糾正了我正在犯的一些錯誤,但我一直試圖完全實現你所描述的內容。當我完成課程後,我會將其發佈回來,以便任何人都可以使用或改編它。我還有一個額外的問題,在我的CustomObservableCollection中,當項目發生更改時應該引發什麼事件?或者我該如何傳播'INotifyPropertyChanged'到數據網格? 'NotifyCollectionChangedAction.Replace'似乎不正確。 – CityView 2011-03-15 11:26:15

+0

@CityView:它不是集合的任務通知有關改變其項目性質的數據網格。當你收集綁定到DataGrid,將DataGrid綁定到您的收藏的CollectionChanged事件,並顯示每一個項目的PropertyChanged事件。 – 2011-03-15 11:32:40

+0

@Daniel:有道理 - [XCeed Grid]不是這種情況(http://xceed.com/CS/blogs/dontpanic/archive/2009/04/01/i-notify-we-notify-we -all-wait-no-we-don-t.aspx)這就是爲什麼我感到困惑。 Exceed你需要擴展BindingList。謝謝,我今天會發布我的嘗試。 – CityView 2011-03-15 11:44:40

相關問題