I came across this BindableDictionary at StackOverflow and I thought I would improve on it and here is the result:
public class BindableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IBindingList, IRaiseItemChangedEvents { public SortedList<TKey, TValue> _Source = null; private ListChangedEventHandler listChanged; private bool raiseItemChangedEvents = false; private bool raiseListChangedEvents = true; [NonSerialized()] private PropertyDescriptorCollection itemTypeProperties = null; [NonSerialized()] private PropertyChangedEventHandler propertyChangedEventHandler = null; [NonSerialized()] private int lastChangeIndex = -1; #region Properties #region IBindingList //Gets whether you can update items in the list. bool IBindingList.AllowEdit { get { return true; } } //Gets whether you can add items to the list using AddNew. bool IBindingList.AllowNew { get { return false; } } //Gets whether you can remove items from the list, using Remove or RemoveAt. bool IBindingList.AllowRemove { get { return true; } } //Gets a value indicating whether the IList has a fixed size. (Inherited from IList.) bool System.Collections.IList.IsFixedSize { get { return false; } } //Gets a value indicating whether the IList is read-only. (Inherited from IList.) bool System.Collections.IList.IsReadOnly { get { return true; } } //Gets whether the items in the list are sorted. bool IBindingList.IsSorted { get { return false; } } //Gets a value indicating whether access to the ICollection is synchronized (thread safe). (Inherited from ICollection.) bool ICollection.IsSynchronized { get { return false; } } //Gets or sets the element at the specified index. (Inherited from IList.) object System.Collections.IList.this[int index] { get { return _Source[_Source.Keys[index]]; } set { _Source[_Source.Keys[index]] = (TValue)value; } } //Gets the direction of the sort. ListSortDirection IBindingList.SortDirection { get { return ListSortDirection.Ascending; } } //Gets the PropertyDescriptor that is being used for sorting. PropertyDescriptor IBindingList.SortProperty { get { return null; } } //Gets whether a ListChanged event is raised when the list changes or an item in the list changes. bool IBindingList.SupportsChangeNotification { get { return true; } } //Gets whether the list supports searching using the Find method. bool IBindingList.SupportsSearching { get { return false; } } //Gets whether the list supports sorting. bool IBindingList.SupportsSorting { get { return false; } } //Gets an object that can be used to synchronize access to the ICollection. (Inherited from ICollection.) object ICollection.SyncRoot { get { return null; } } #endregion #region IDictionary //Gets a value indicating whether the ICollection<(Of <(T>)>) is read-only. (Inherited from ICollection<(Of <(T>)>).) bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly { get { return ((ICollection<KeyValuePair<TKey, TValue>>)_Source).IsReadOnly; } } //Gets or sets the element with the specified key. public TValue this[TKey key] { get { return _Source[key]; } set { bool bAdded = false; if (_Source.ContainsKey(key)) { bAdded = true; } _Source[key] = value; if (bAdded) { if (this.raiseItemChangedEvents) { HookPropertyChanged(value); OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, _Source.IndexOfKey(key))); } } } } //Gets an ICollection<(Of <(T>)>) containing the keys of the IDictionary<(Of <(TKey, TValue>)>). public ICollection<TKey> Keys { get { return _Source.Keys; } } //Gets an ICollection<(Of <(T>)>) containing the values in the IDictionary<(Of <(TKey, TValue>)>). public ICollection<TValue> Values { get { return _Source.Values; } } #endregion //Gets the number of elements contained in the ICollection. (Inherited from ICollection.) //Gets the number of elements contained in the ICollection<(Of <(T>)>). (Inherited from ICollection<(Of <(T>)>).) public int Count { get { return _Source.Count; } } #endregion #region Methods #region IBindingList //Add function not implemented use other Add function instead //Adds an item to the IList. (Inherited from IList.) int System.Collections.IList.Add(object value) { throw new NotImplementedException(); } //Adds the PropertyDescriptor to the indexes used for searching. void IBindingList.AddIndex(PropertyDescriptor property) { } //Adds a new item to the list. object IBindingList.AddNew() { return null; } //Sorts the list based on a PropertyDescriptor and a ListSortDirection. void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction) { } //Determines whether the IList contains a specific value. (Inherited from IList.) bool System.Collections.IList.Contains(object value) { if (value is TKey) { return _Source.ContainsKey((TKey)value); } else if (value is TValue) { return _Source.ContainsValue((TValue)value); } return false; } //Copies the elements of the ICollection to an Array, starting at a particular Array index. (Inherited from ICollection.) void ICollection.CopyTo(Array array, int arrayIndex) { ((ICollection)_Source).CopyTo(array, arrayIndex); } //Returns the index of the row that has the given PropertyDescriptor. int IBindingList.Find(PropertyDescriptor property, object key) { throw new NotImplementedException(); } //Returns an enumerator that iterates through a collection. (Inherited from IEnumerable.) System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } //Determines the index of a specific item in the IList. (Inherited from IList.) int System.Collections.IList.IndexOf(object value) { if (value is TKey) { return _Source.IndexOfKey((TKey)value); } else if (value is TValue) { return _Source.IndexOfValue((TValue)value); } return -1; } //Inserts an item to the IList at the specified index. (Inherited from IList.) void System.Collections.IList.Insert(int index, object value) { throw new NotImplementedException(); } //Removes the first occurrence of a specific object from the IList. (Inherited from IList.) void System.Collections.IList.Remove(object value) { if (value is TKey) { Remove((TKey)value); } } //Removes the IList item at the specified index. (Inherited from IList.) void System.Collections.IList.RemoveAt(int index) { _Source.RemoveAt(index); } //Removes the PropertyDescriptor from the indexes used for searching. void IBindingList.RemoveIndex(PropertyDescriptor property) { } //Removes any sort applied using ApplySort. void IBindingList.RemoveSort() { } #endregion #region IDictionary //Adds an item to the ICollection<(Of <(T>)>). (Inherited from ICollection<(Of <(T>)>).) void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) { ((ICollection<KeyValuePair<TKey, TValue>>)_Source).Add(item); if (this.raiseItemChangedEvents) { HookPropertyChanged(item.Value); OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, _Source.IndexOfKey(item.Key))); } } //Adds an element with the provided key and value to the IDictionary<(Of <(TKey, TValue>)>). public void Add(TKey key, TValue value) { _Source.Add(key, value); if (this.raiseItemChangedEvents) { HookPropertyChanged(value); OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, _Source.IndexOfKey(key))); } } //Determines whether the ICollection<(Of <(T>)>) contains a specific value. (Inherited from ICollection<(Of <(T>)>).) bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { return ((ICollection<KeyValuePair<TKey, TValue>>)_Source).Contains(item); } //Determines whether the IDictionary<(Of <(TKey, TValue>)>) contains an element with the specified key. public bool ContainsKey(TKey key) { return _Source.ContainsKey(key); } //Determines whether the IDictionary<(Of <(TKey, TValue>)>) contains an element with the specified key. void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { ((ICollection<KeyValuePair<TKey, TValue>>)_Source).CopyTo(array, arrayIndex); } //Returns an enumerator that iterates through the collection. (Inherited from IEnumerable<(Of <(T>)>).) public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return _Source.GetEnumerator(); } //Removes the first occurrence of a specific object from the ICollection<(Of <(T>)>). (Inherited from ICollection<(Of <(T>)>).) bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { int index = _Source.IndexOfKey(item.Key); if (index != -1) { if (this.raiseItemChangedEvents) { UnhookPropertyChanged(item.Value); } ((ICollection<KeyValuePair<TKey, TValue>>)_Source).Remove(item); OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index)); return true; } return false; } //Removes the element with the specified key from the IDictionary<(Of <(TKey, TValue>)>). public bool Remove(TKey key) { int index = _Source.IndexOfKey(key); if (index != -1) { if (this.raiseItemChangedEvents) { UnhookPropertyChanged(_Source[key]); } _Source.Remove(key); OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index)); return true; } return false; } //Gets the value associated with the specified key. public bool TryGetValue(TKey key, out TValue value) { return _Source.TryGetValue(key, out value); } #endregion //Removes all items from the IList. (Inherited from IList.) //Removes all items from the ICollection<(Of <(T>)>). (Inherited from ICollection<(Of <(T>)>).) public void Clear() { if (this.raiseItemChangedEvents) { foreach (TValue item in _Source.Values) { UnhookPropertyChanged(item); } } _Source.Clear(); OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); } private void Initialize() { // Check for INotifyPropertyChanged if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(TValue))) { // Supports INotifyPropertyChanged this.raiseItemChangedEvents = true; // Loop thru the items already in the collection and hook their change notification. foreach (TValue item in _Source.Values) { HookPropertyChanged(item); } } } //Send change notification protected virtual void OnListChanged(ListChangedEventArgs e) { var evt = listChanged; if (evt != null) evt(this, e); } public void ResetBindings() { OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); } public bool RaiseListChangedEvents { get { return this.raiseListChangedEvents; } set { if (this.raiseListChangedEvents != value) { this.raiseListChangedEvents = value; } } } #endregion #region Events #region IBindingList event ListChangedEventHandler IBindingList.ListChanged { add { listChanged += value; } remove { listChanged -= value; } } #endregion #endregion #region constructor public BindableDictionary() { _Source = new SortedList<TKey, TValue>(); Initialize(); } public BindableDictionary(IComparer<TKey> comparer) { _Source = new SortedList<TKey, TValue>(comparer); Initialize(); } #endregion #region Property Change Support private void HookPropertyChanged(TValue item) { INotifyPropertyChanged inpc = (item as INotifyPropertyChanged); // Note: inpc may be null if item is null, so always check. if (null != inpc) { if (propertyChangedEventHandler == null) { propertyChangedEventHandler = new PropertyChangedEventHandler(Child_PropertyChanged); } inpc.PropertyChanged += propertyChangedEventHandler; } } private void UnhookPropertyChanged(TValue item) { INotifyPropertyChanged inpc = (item as INotifyPropertyChanged); // Note: inpc may be null if item is null, so always check. if (null != inpc && null != propertyChangedEventHandler) { inpc.PropertyChanged -= propertyChangedEventHandler; } } void Child_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (this.RaiseListChangedEvents) { if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName)) { // Fire reset event (per INotifyPropertyChanged spec) ResetBindings(); } else { TValue item; try { item = (TValue)sender; } catch (InvalidCastException) { ResetBindings(); return; } // Find the position of the item. This should never be -1. If it is, // somehow the item has been removed from our list without our knowledge. int pos = lastChangeIndex; if (pos < 0 || pos >= Count || !_Source.Values[pos].Equals(item)) { pos = _Source.IndexOfValue(item); lastChangeIndex = pos; } if (pos == -1) { UnhookPropertyChanged(item); ResetBindings(); } else { // Get the property descriptor if (null == this.itemTypeProperties) { // Get Shape itemTypeProperties = TypeDescriptor.GetProperties(typeof(TValue)); } PropertyDescriptor pd = itemTypeProperties.Find(e.PropertyName, true); // Create event args. If there was no matching property descriptor, // we raise the list changed anyway. ListChangedEventArgs args = new ListChangedEventArgs(ListChangedType.ItemChanged, pos, pd); // Fire the ItemChanged event OnListChanged(args); } } } } #endregion #region IRaiseItemChangedEvents interface /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.RaisesItemChangedEvents"]/*' /> /// <devdoc> /// Returns false to indicate that BindingList<T> does NOT raise ListChanged events /// of type ItemChanged as a result of property changes on individual list items /// unless those items support INotifyPropertyChanged /// </devdoc> bool IRaiseItemChangedEvents.RaisesItemChangedEvents { get { return this.raiseItemChangedEvents; } } #endregion } |