C# Bindable Dictionary

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
 
    }

7 thoughts on “C# Bindable Dictionary

  1. it doesn’t compile. seems to be typos or something in the code. can you please clarify the IDictionary region? that’s where the typos seem to be. bool ICollection>.IsReadOnly and public TValue this[TKey key]

  2. One way to get around this would probably be to encapsulate a dictionary, then you can implement notifying interfaces and control the access to the dictionary, i.e. if someone uses the brackets to set a value you can set the value of the internal dictionary and raise the notification.

    • I was going to look into redoing it using the SortedDictionary, but the .NET 3.5 dniameotutcon changed my mind.1. SortedList and SortedDictionary Keyed retreival is the same O(log n)2. Insertion and deletion is faster in the dict, but only when populated randomly, and the dict uses more memory3. Indexed retreival of the Keys[] and Values[] collections is more efficient with the SortedList, and the Keys collection is used by the max and min properties. (The main reason I needed the SortedSet was for the max and min)4. Neither SortedList not SortedDictionary permits null keysI guess it all depends what you are after.

  3. BindingList is not a thread-safe container.The main purpose of creating ThreadSafeBindingList was to protect the binding list from competitive access by multiple application threads.ThreadSafeBindingList is just a wrapper that contains the BindingList and locks its internal method.ThreadSafeBindingList implementation prevents occupation of synchronizing object at the time when notification is fired. It is especially important to avoid potential deadlock problems when two synchronizing objects are taken by the opposite threads visit in dapfor. com

  4. hi, i like your version of this bindabledictionary as i am using something similar. but i’m having one issue with this code. My ListChangedEventHandler is not being set anywhere.

    how should that be set?

    thanks again

    Allen

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload the CAPTCHA.