title: WPF - Observable Sorted Dictionary
body: <p>I have a requirement to push high volumes of data into a DataGrid (I would prefer a DataGrid as it is fit for purpose for the requirement I have).  To make the UI more responsive while the data is being loaded and as the data is coming in after the initial load is complete, something fairly high performance is required.  On top of that the data needs to be sorted (descending order based on date).  As it is only updated from one thread being concurrent (and/or immutable) is not really required (from what I understand being concurrent and/or immutable may slow down the load in any case).  So for this reason I would like to implement an Observable performant collection such as a SortedDictionary</p>
  
  <p>From what I have seen the above isn't readily available - the options I have seen are <a href="http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So" rel="nofollow">http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So</a> but this isn't bindable to a DataGrid (more toward a ListView).  The other is <a href="http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/" rel="nofollow">http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/</a> which is based on a Dictionary and hand sorts (which seems counter-intuitive since there is already a SortedDictionary - and again doesn't seem to bind to a DataGrid easily).</p>
  
  <p>The below is what I have </p>
  
  <pre><code>public class ObservableSortedDictionary&lt;TKey, TValue&gt; : IObservableSortedDictionary&lt;TKey, TValue&gt;
  {
      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&lt;TKey, TValue&gt; _dictionary;
      protected SortedDictionary&lt;TKey, TValue&gt; Dictionary
      {
          get { return _dictionary; }
          private set { _dictionary = value; }
      }
  
      #region Fields
      private readonly SimpleMonitor _monitor;
      #endregion
  
      #region Constructors
      public ObservableSortedDictionary(IComparer&lt;TKey&gt; comparer)
      {
          this._monitor = new SimpleMonitor();
          CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged);
          _dictionary = new SortedDictionary&lt;TKey, TValue&gt;(comparer);
      }
      public ObservableSortedDictionary(int capacity, IComparer&lt;TKey&gt; comparer)
      {
          this._monitor = new SimpleMonitor();
          _capacity = capacity;
          CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged);
          _dictionary = (new SortedDictionary&lt;TKey, TValue&gt;(comparer));         }
      public ObservableSortedDictionary(IDictionary&lt;TKey, TValue&gt; dictionary, IComparer&lt;TKey&gt; comparer)
      {
          if (dictionary == null)
          {
              throw new ArgumentNullException("dictionary");
          }
          CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableSortedDictionary_CollectionChanged);
          this._monitor = new SimpleMonitor();
          _dictionary = new SortedDictionary&lt;TKey, TValue&gt;(dictionary, comparer);
      }
      public ObservableSortedDictionary(IDictionary&lt;TKey, TValue&gt; dictionary, IComparer&lt;TKey&gt; 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&lt;TKey, TValue&gt;(dictionary, comparer);
          }
          catch (Exception ex)
          {
              throw;
          }
      }
      #endregion
  
      #region IDictionary&lt;TKey,TValue&gt; Members
  
      public void Add(TKey key, TValue value)
      {
          Insert(key, value, true);
      }
  
      public bool ContainsKey(TKey key)
      {
          return Dictionary.ContainsKey(key);
      }
  
      public ICollection&lt;TKey&gt; 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&lt;TValue&gt; Values
      {
          get { return Dictionary.Values; }
      }
  
  
      public TValue this[TKey key]
      {
          get
          {
              return Dictionary[key];
          }
          set
          {
              Insert(key, value, false);
          }
      }
      #endregion
  
      #region ICollection&lt;KeyValuePair&lt;TKey,TValue&gt;&gt; Members
      public void Add(KeyValuePair&lt;TKey, TValue&gt; item)
      {
          Insert(item.Key, item.Value, true);
      }
  
      public void Clear()
      {
          if (Dictionary.Count &gt; 0)
          {
              CheckReentrancy(); 
              Dictionary.Clear();
              OnCollectionChanged();
          }
      }
  
      public bool Contains(KeyValuePair&lt;TKey, TValue&gt; item)
      {
          return Dictionary.Contains(item);
      }
  
      public void CopyTo(KeyValuePair&lt;TKey, TValue&gt;[] array, int arrayIndex)
      {
          Dictionary.CopyTo(array, arrayIndex);
      }
  
      public int Count
      {
          get { return Dictionary.Count; }
      }
  
      public bool IsReadOnly
      {
          get { return ((IDictionary&lt;TKey, TValue&gt;)Dictionary).IsReadOnly; }
      }
  
      public bool Remove(KeyValuePair&lt;TKey, TValue&gt; item)
      {
          return Remove(item.Key);
      }
      #endregion
  
  
      #region IEnumerable&lt;KeyValuePair&lt;TKey,TValue&gt;&gt; Members
      public IEnumerator&lt;KeyValuePair&lt;TKey, TValue&gt;&gt; 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&lt;TKey, TValue&gt; items)
      {
          if (items == null) throw new ArgumentNullException("items");
  
          if (items.Count &gt; 0)
          {
              if (items.Keys.Any((k) =&gt; 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&lt;TKey, TValue&gt;(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&lt;TKey, TValue&gt;(key, value), new KeyValuePair&lt;TKey, TValue&gt;(key, item));
          }
          else
          {
              Dictionary[key] = value;
              OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair&lt;TKey, TValue&gt;(key, value));
              if (_capacity &gt; 0 &amp;&amp; Dictionary.Count &gt; _capacity)
              {
                  Dictionary.Remove(Dictionary.Keys.Last());
                  OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair&lt;TKey, TValue&gt;(key, value));
              }
          }
      }
  
      #region SimpleMonitor
      protected IDisposable BlockReentrancy()
      {
          this._monitor.Enter();
          return this._monitor;
      }
  
      protected void CheckReentrancy()
      {
          if ((this._monitor.Busy &amp;&amp; (CollectionChanged != null)) &amp;&amp; (CollectionChanged.GetInvocationList().Length &gt; 1))
          {
              throw new InvalidOperationException("Collection Reentrancy Not Allowed");
          }
      }
  
      [Serializable]
      private class SimpleMonitor : IDisposable
      {
          private int _busyCount;
  
          public bool Busy
          {
              get { return this._busyCount &gt; 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&lt;TKey, TValue&gt; changedItem)
      {
          OnPropertyChanged();
          if (CollectionChanged != null) using (BlockReentrancy()) { CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));}
      }
  
      private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair&lt;TKey, TValue&gt; newItem, KeyValuePair&lt;TKey, TValue&gt; 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&lt;TickerKey, TickerViewModel&gt;)(item)).Value.PropertyChanged += ObservableSortedDictionary_PropertyChanged;                 }
          }
          if (e.OldItems != null)
          {
              foreach (Object item in e.OldItems)
              {
                  ((KeyValuePair&lt;TickerKey, TickerViewModel&gt;)(item)).Value.PropertyChanged -= ObservableSortedDictionary_PropertyChanged;                 }
          }
      }
  
      void ObservableSortedDictionary_PropertyChanged(object sender, PropertyChangedEventArgs e)
      {
          //if (e.PropertyName == "Dictionary")
              OnPropertyChanged("Dictionary");
      }
  }
  
  public interface IObservableSortedDictionary&lt;TKey, TValue&gt; : IDictionary&lt;TKey, TValue&gt;, INotifyCollectionChanged, INotifyPropertyChanged, IEnumerable&lt;KeyValuePair&lt;TKey, TValue&gt;&gt;
  {
  }
  </code></pre>
  
  <p>The ViewModel</p>
  
  <pre><code>    //...
      private ObservableSortedDictionary&lt;TickerKey, TickerViewModel&gt; _tickersData;
      public ObservableSortedDictionary&lt;TickerKey, TickerViewModel&gt; TickersData
      {
          get
          {
              return _tickersData;
          }
          set
          {
              if (value != _tickersData)
              {
                  _tickersData = value;
                  OnPropertyChanged("TickersData");
              }
          }
      }
      //...
      if (TickersData == null)
      {
          TickerComparer comparer = new TickerComparer();
          TickersData = new ObservableSortedDictionary&lt;TickerKey, TickerViewModel&gt;(_tickersInsert, comparer, 50);
      }
      else
      {
          TickersData.AddRange(_tickersInsert);
          foreach (var item in _tickersInsert) TickersData.Add(item.Key, item.Value);
      }
      //...
  </code></pre>
  
  <p>The View (XAML)</p>
  
  <pre><code>//...
  &lt;DataGrid FontSize="9" x:Name="Ticker1Tickers" IsReadOnly="True" ItemsSource="{Binding TickersData.Values}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto"&gt;
      &lt;DataGrid.Columns&gt;
          &lt;DataGridTextColumn Binding="{Binding TickerPrice}" Header="Price" Width="50"/&gt;
          &lt;DataGridTextColumn Binding="{Binding TickerVolume}" Header="Volume" Width="50" /&gt;
          &lt;DataGridTextColumn Binding="{Binding TickerTimeMilliSecondsSinceMidnight, Converter={StaticResource mmSsFormatConverter}, StringFormat=\{0:hh:mm:ss tt\}}" Header="Time" Width="70" /&gt;
      &lt;/DataGrid.Columns&gt;
  &lt;/DataGrid&gt;
  //...
  </code></pre>
  
  <p>A few things of note</p>
  
  <ul>
  <li>The above loads the DataGrid initially with rows from the database but as new data comes in, this is not reflected within the DataGrid (even though the new data is added to the ObservableSortedDictionary.</li>
  <li>The binding of the ObservableSortedDictionary to the XAML is via the Values part of the dictionary (i.e. TickersData.Values rather than simply TickersData)</li>
  <li>the weakness seems to be within the CollectionChanged/PropertyChanged notification.  Specifically between sending the event from a change to the dictionary Values collection down to the DataGrid </li>
  </ul>
  
  <p>Has anyone attempted anything like the above and/or can see where the notification issue may be ?</p>
  
  <p><strong>UPDATE</strong>
  After Pieter's suggestion I have changed the XAML DataGrid definition to</p>
  
  <pre><code>&lt;DataGrid FontSize="9" x:Name="Ticker1Tickers" IsReadOnly="True" ItemsSource="{Binding TickersData}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto"&gt;
      &lt;DataGrid.Columns&gt;
          &lt;DataGridTextColumn Binding="{Binding Value.TickerPrice}" Header="Price" Width="50"/&gt;
          &lt;DataGridTextColumn Binding="{Binding Value.TickerVolume}" Header="Volume" Width="50" /&gt;
          &lt;DataGridTextColumn Binding="{Binding Value.TickerTimeMilliSecondsSinceMidnight, Converter={StaticResource mmSsFormatConverter}, StringFormat=\{0:hh:mm:ss tt\}}" Header="Time" Width="70" /&gt;
      &lt;/DataGrid.Columns&gt;
  &lt;/DataGrid&gt;
  </code></pre>