0

У меня есть требование толкнуть большие объемы данных в DataGrid (я бы предпочел DataGrid, поскольку он подходит для цели для моего требования). Чтобы сделать пользовательский интерфейс более отзывчивым во время загрузки данных и по мере того, как данные поступают после завершения начальной загрузки, требуется довольно высокая производительность. Кроме того, данные необходимо сортировать (по убыванию в зависимости от даты). Поскольку он обновляется только из одного потока, являющегося параллельным (и/или неизменным), на самом деле не требуется (из того, что я понимаю, является одновременным и/или неизменным, может в любом случае замедлить загрузку). Поэтому по этой причине я хотел бы реализовать коллекцию Observable, такую ​​как SortedDictionaryWPF - Наблюдаемый Сортированный словарь

Из того, что я видел выше, не всегда доступно - параметры, которые я видел, это http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So, но это не привязывается к DataGrid (больше к ListView). Другой - http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/, который основан на словарях и ручных сортировках (что кажется противоречащим интуиции, поскольку уже есть SortedDictionary - и снова, похоже, не привязывается к DataGrid легко).

Ниже то, что я

public class ObservableSortedDictionary<TKey, TValue> : IObservableSortedDictionary<TKey, TValue> 
{ 
    private const string CountString = "Count"; 
    private const string IndexerName = "Item[]"; 
    private const string KeysName = "Keys"; 
    private const string ValuesName = "Values"; 

    private int _capacity = 0; 

    private SortedDictionary<TKey, TValue> _dictionary; 
    protected SortedDictionary<TKey, TValue> Dictionary 
    { 
     get { return _dictionary; } 
     private set { _dictionary = value; } 
    } 

    #region Fields 
    private readonly SimpleMonitor _monitor; 
    #endregion 

    #region Constructors 
    public ObservableSortedDictionary(IComparer<TKey> comparer) 
    { 
     this._monitor = new SimpleMonitor(); 
     CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged); 
     _dictionary = new SortedDictionary<TKey, TValue>(comparer); 
    } 
    public ObservableSortedDictionary(int capacity, IComparer<TKey> comparer) 
    { 
     this._monitor = new SimpleMonitor(); 
     _capacity = capacity; 
     CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged); 
     _dictionary = (new SortedDictionary<TKey, TValue>(comparer));   } 
    public ObservableSortedDictionary(IDictionary<TKey, TValue> dictionary, IComparer<TKey> comparer) 
    { 
     if (dictionary == null) 
     { 
      throw new ArgumentNullException("dictionary"); 
     } 
     CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged); 
     this._monitor = new SimpleMonitor(); 
     _dictionary = new SortedDictionary<TKey, TValue>(dictionary, comparer); 
    } 
    public ObservableSortedDictionary(IDictionary<TKey, TValue> dictionary, IComparer<TKey> comparer, int capacity) 
    { 
     if (dictionary == null) 
     { 
      throw new ArgumentNullException("dictionary"); 
     } 
     CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged); 
     this._monitor = new SimpleMonitor(); 
     _capacity = capacity; 
     try 
     { 
      _dictionary = new SortedDictionary<TKey, TValue>(dictionary, comparer); 
     } 
     catch (Exception ex) 
     { 
      throw; 
     } 
    } 
    #endregion 

    #region IDictionary<TKey,TValue> Members 

    public void Add(TKey key, TValue value) 
    { 
     Insert(key, value, true); 
    } 

    public bool ContainsKey(TKey key) 
    { 
     return Dictionary.ContainsKey(key); 
    } 

    public ICollection<TKey> Keys 
    { 
     get { return Dictionary.Keys; } 
    } 

    public bool Remove(TKey key) 
    { 
     if (key == null) throw new ArgumentNullException("key"); 

     CheckReentrancy(); 
     TValue value; 
     Dictionary.TryGetValue(key, out value); 
     var removed = Dictionary.Remove(key); 
     if (removed) 
      OnCollectionChanged(); 

     return removed; 
    } 


    public bool TryGetValue(TKey key, out TValue value) 
    { 
     return Dictionary.TryGetValue(key, out value); 
    } 


    public ICollection<TValue> Values 
    { 
     get { return Dictionary.Values; } 
    } 


    public TValue this[TKey key] 
    { 
     get 
     { 
      return Dictionary[key]; 
     } 
     set 
     { 
      Insert(key, value, false); 
     } 
    } 
    #endregion 

    #region ICollection<KeyValuePair<TKey,TValue>> Members 
    public void Add(KeyValuePair<TKey, TValue> item) 
    { 
     Insert(item.Key, item.Value, true); 
    } 

    public void Clear() 
    { 
     if (Dictionary.Count > 0) 
     { 
      CheckReentrancy(); 
      Dictionary.Clear(); 
      OnCollectionChanged(); 
     } 
    } 

    public bool Contains(KeyValuePair<TKey, TValue> item) 
    { 
     return Dictionary.Contains(item); 
    } 

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) 
    { 
     Dictionary.CopyTo(array, arrayIndex); 
    } 

    public int Count 
    { 
     get { return Dictionary.Count; } 
    } 

    public bool IsReadOnly 
    { 
     get { return ((IDictionary<TKey, TValue>)Dictionary).IsReadOnly; } 
    } 

    public bool Remove(KeyValuePair<TKey, TValue> item) 
    { 
     return Remove(item.Key); 
    } 
    #endregion 


    #region IEnumerable<KeyValuePair<TKey,TValue>> Members 
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() 
    { 
     return Dictionary.GetEnumerator(); 
    } 
    #endregion 

    #region IEnumerable Members 
    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
    #endregion 

    #region INotifyCollectionChanged Members 
    public event NotifyCollectionChangedEventHandler CollectionChanged; 
    #endregion 


    #region INotifyPropertyChanged Members 
    public event PropertyChangedEventHandler PropertyChanged; 
    #endregion 

    public void AddRange(IDictionary<TKey, TValue> items) 
    { 
     if (items == null) throw new ArgumentNullException("items"); 

     if (items.Count > 0) 
     { 
      if (items.Keys.Any((k) => Dictionary.ContainsKey(k))) 
       throw new ArgumentException("An item with the same key has already been added."); 
      else 
      { 
       foreach (var item in items) 
       { 
        Dictionary.Add(item.Key, item.Value); 
        OnPropertyChanged(); 
        OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(item.Key, item.Value)); 
       } 
      } 
     } 
    } 

    private void Insert(TKey key, TValue value, bool add) 
    { 
     if (key == null) throw new ArgumentNullException("key"); 

     CheckReentrancy(); 
     TValue item; 
     if (Dictionary.TryGetValue(key, out item)) 
     { 
      if (add) throw new ArgumentException("An item with the same key has already been added."); 
      if (Equals(item, value)) return; 
      Dictionary[key] = value; 

      OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item)); 
     } 
     else 
     { 
      Dictionary[key] = value; 
      OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value)); 
      if (_capacity > 0 && Dictionary.Count > _capacity) 
      { 
       Dictionary.Remove(Dictionary.Keys.Last()); 
       OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value)); 
      } 
     } 
    } 

    #region SimpleMonitor 
    protected IDisposable BlockReentrancy() 
    { 
     this._monitor.Enter(); 
     return this._monitor; 
    } 

    protected void CheckReentrancy() 
    { 
     if ((this._monitor.Busy && (CollectionChanged != null)) && (CollectionChanged.GetInvocationList().Length > 1)) 
     { 
      throw new InvalidOperationException("Collection Reentrancy Not Allowed"); 
     } 
    } 

    [Serializable] 
    private class SimpleMonitor : IDisposable 
    { 
     private int _busyCount; 

     public bool Busy 
     { 
      get { return this._busyCount > 0; } 
     } 

     public void Enter() 
     { 
      this._busyCount++; 
     } 

     #region Implementation of IDisposable 

     public void Dispose() 
     { 
      this._busyCount--; 
     } 

     #endregion 
    } 
    #endregion 

    private void OnPropertyChanged() 
    { 
     OnPropertyChanged(CountString); 
     OnPropertyChanged(IndexerName); 
     OnPropertyChanged(KeysName); 
     OnPropertyChanged(ValuesName); 
    } 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    private void OnCollectionChanged() 
    { 
     OnPropertyChanged(); 
     if (CollectionChanged != null) using (BlockReentrancy()) { CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));} 
    } 

    private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem) 
    { 
     OnPropertyChanged(); 
     if (CollectionChanged != null) using (BlockReentrancy()) { CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));} 
    } 

    private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem) 
    { 
     OnPropertyChanged(); 
     if (CollectionChanged != null) using (BlockReentrancy()) {CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));} 
    } 


    private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems) 
    { 
     OnPropertyChanged(); 
     if (CollectionChanged != null) using (BlockReentrancy()) {CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));} 
    } 

    void ObservableSortedDictionary_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     if (e.NewItems != null) 
     { 
      foreach (Object item in e.NewItems) 
      { 
       ((KeyValuePair<TickerKey, TickerViewModel>)(item)).Value.PropertyChanged += ObservableSortedDictionary_PropertyChanged;     } 
     } 
     if (e.OldItems != null) 
     { 
      foreach (Object item in e.OldItems) 
      { 
       ((KeyValuePair<TickerKey, TickerViewModel>)(item)).Value.PropertyChanged -= ObservableSortedDictionary_PropertyChanged;     } 
     } 
    } 

    void ObservableSortedDictionary_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     //if (e.PropertyName == "Dictionary") 
      OnPropertyChanged("Dictionary"); 
    } 
} 

public interface IObservableSortedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged, IEnumerable<KeyValuePair<TKey, TValue>> 
{ 
} 

ViewModel

//... 
    private ObservableSortedDictionary<TickerKey, TickerViewModel> _tickersData; 
    public ObservableSortedDictionary<TickerKey, TickerViewModel> TickersData 
    { 
     get 
     { 
      return _tickersData; 
     } 
     set 
     { 
      if (value != _tickersData) 
      { 
       _tickersData = value; 
       OnPropertyChanged("TickersData"); 
      } 
     } 
    } 
    //... 
    if (TickersData == null) 
    { 
     TickerComparer comparer = new TickerComparer(); 
     TickersData = new ObservableSortedDictionary<TickerKey, TickerViewModel>(_tickersInsert, comparer, 50); 
    } 
    else 
    { 
     TickersData.AddRange(_tickersInsert); 
     foreach (var item in _tickersInsert) TickersData.Add(item.Key, item.Value); 
    } 
    //... 

The View (XAML)

//... 
<DataGrid FontSize="9" x:Name="Ticker1Tickers" IsReadOnly="True" ItemsSource="{Binding TickersData.Values}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto"> 
    <DataGrid.Columns> 
     <DataGridTextColumn Binding="{Binding TickerPrice}" Header="Price" Width="50"/> 
     <DataGridTextColumn Binding="{Binding TickerVolume}" Header="Volume" Width="50" /> 
     <DataGridTextColumn Binding="{Binding TickerTimeMilliSecondsSinceMidnight, Converter={StaticResource mmSsFormatConverter}, StringFormat=\{0:hh:mm:ss tt\}}" Header="Time" Width="70" /> 
    </DataGrid.Columns> 
</DataGrid> 
//... 

Несколько вещей примечания

  • Вышеуказанное загружает DataGrid изначально из строк из базы данных, но по мере поступления новых данных это не отражается в DataGrid (даже если новые данные добавляются в ObservableSortedDictionary.
  • Связывание ObservableSortedDictionary к XAML осуществляется с помощью значений части словаря (т.е. TickersData.Values, а не просто TickersData)
  • слабость, как представляется, в уведомлении CollectionChanged/PropertyChanged. В частности, между отправкой события из изменения в словарь. Сбор значений до DataGrid.
  • . .
. . .

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

UPDATE После внушения Питера, я изменил определение XAML DataGrid к

<DataGrid FontSize="9" x:Name="Ticker1Tickers" IsReadOnly="True" ItemsSource="{Binding TickersData}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto"> 
    <DataGrid.Columns> 
     <DataGridTextColumn Binding="{Binding Value.TickerPrice}" Header="Price" Width="50"/> 
     <DataGridTextColumn Binding="{Binding Value.TickerVolume}" Header="Volume" Width="50" /> 
     <DataGridTextColumn Binding="{Binding Value.TickerTimeMilliSecondsSinceMidnight, Converter={StaticResource mmSsFormatConverter}, StringFormat=\{0:hh:mm:ss tt\}}" Header="Time" Width="70" /> 
    </DataGrid.Columns> 
</DataGrid> 
+0

Почему бы просто не использовать ObservableCollection, а затем вставить в соответствующее положение, чтобы поддержать своего рода? И если вам нужна производительность, то почему DataGrid? – Paparazzi

+1

Вы привязываетесь к 'TickersData.Values' - свойству' Values' вашего отсортированного словаря, а не самому сортированному словарю. Попытайтесь напрямую привязать к TickersData. –

+0

Спасибо за комментарий Frisbee - я понимаю, что вы говорите вокруг ObservableCollection, но я не хочу создавать собственный собственный алгоритм сортировки (который не будет таким же результативным, как сказать из сортированной коллекции). И причина для DataGrid (а не ListView) заключается в том, что из-за этого у него есть необходимая функциональность (сортировка столбцов и т. Д.) – TerrorBight

ответ

1

Спасибо за вашу помощь по пути на этом Питеру. Для обновления/сортировки в DataGrid из ObservableSortedDictionary я нашел решение (после прочтения одного из комментариев в http://programmer.wrighton.org/2009/01/wpf-datagrid-items-refresh.html) был

CollectionViewSource.GetDefaultView(TickersData).Refresh(); 

Будем надеяться, что вышеприведенное утверждение не слишком много накладных расходов ресурсов

я имел еще одна проблема, связанная с OnCollectionChanged - NotifyCollectionChangedEventArgs, требовала индекса (в отличие от элемента) SortedDictionary, который не получается из коробки (это проще с SortedList, но так или иначе).

Но все ОК сейчас - работает хорошо