.NET ObservableDictionary
Asked Answered
N

6

36

I have written the following class which implements(or tries to!) a dictionary with notifications:

public partial class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged
{
    public ObservableDictionary() : base() { }
    public ObservableDictionary(int capacity) : base(capacity) { }
    public ObservableDictionary(IEqualityComparer<TKey> comparer) : base(comparer) { }
    public ObservableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary) { }
    public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer) { }
    public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer) { }

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public new TValue this[TKey key]
    {
        get
        {
            return base[key];
        }
        set
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, key, 0));
            base[key] = value;
        }
    }

    public new void Add(TKey key, TValue value)
    {
        base.Add(key, value);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, key, 0));
    }

    public new bool Remove(TKey key)
    {
        bool x = base.Remove(key);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, key, 0));
        return x;
    }

    public new void Clear()
    {
        base.Clear();
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }


    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
        {
            CollectionChanged(this, e);
        }
    }
}

In another class, I have a listener for the MyObservableDictionary.CollectionChanged event:

The problem I'm having is that the event doesn't fire. How can I fix this?

Niobic answered 14/4, 2011 at 12:42 Comment(5)
does "onCollectionChanged" not fire, or does "collectionChanged" not fire?Galateah
Provide the event listener class ..Britney
In addition to @Tim..'s question, can you show the code in the consumer where you are assigning a delegate method to the event handler?Duologue
@Tim, @Akram : tc.EventHistory.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(EventHistory_CollectionChanged); where tc is the usercontrol containing the ObservableDictionary(callede EventHistory). Also @Tim OnCollectionChanged doesn't fireNiobic
10rem.net/blog/2010/03/08/…Arvid
G
31

I'd suggest that you implement the IDictionary<TKey, TValue> instead of inheriting from Dictionary<TKey, TValue>. Since you have to use new rather than override it's possible that the methods are simply being called on the base class rather than your class. I'd be tempted to use a Dictionary<TKey, TValue> internally to do the actual storing of data.

In fact I found this: http://blogs.microsoft.co.il/blogs/shimmy/archive/2010/12/26/observabledictionary-lt-tkey-tvalue-gt-c.aspx

Groping answered 14/4, 2011 at 12:51 Comment(2)
using xaml how do you bind the SelectedValuePath and SelectedValue?Prent
Unfortunately, the blogpost doesn't seem to specify under which license terms the code may be used.Schottische
D
56

The Microsoft ParallelExtensionsExtras provides this class which is not only observable but is also concurrent:

Now available via Nuget: https://www.nuget.org/packages/MSFT.ParallelExtensionsExtras/

The source code was recently updated for .NET Standard 2.1 on 05/11/2020 and the source code is available at GitHub: https://github.com/dotnet/samples/tree/master/csharp/parallel/ParallelExtensionsExtras

Note that some of the features are now a part of newer .NET frameworks. Are the ParallelExtensions "Extras" still of value?

Microsoft Tour Blog: https://blogs.msdn.microsoft.com/pfxteam/2010/04/04/a-tour-of-parallelextensionsextras/

//--------------------------------------------------------------------------
// 
//  Copyright (c) Microsoft Corporation.  All rights reserved. 
// 
//  File: ObservableConcurrentDictionary.cs
//
//--------------------------------------------------------------------------

using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;

namespace System.Collections.Concurrent
{
    /// <summary>
    /// Provides a thread-safe dictionary for use with data binding.
    /// </summary>
    /// <typeparam name="TKey">Specifies the type of the keys in this collection.</typeparam>
    /// <typeparam name="TValue">Specifies the type of the values in this collection.</typeparam>
    [DebuggerDisplay("Count={Count}")]
    public class ObservableConcurrentDictionary<TKey, TValue> :
        ICollection<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>,
        INotifyCollectionChanged, INotifyPropertyChanged
    {
        private readonly SynchronizationContext _context;
        private readonly ConcurrentDictionary<TKey, TValue> _dictionary;

        /// <summary>
        /// Initializes an instance of the ObservableConcurrentDictionary class.
        /// </summary>
        public ObservableConcurrentDictionary()
        {
            _context = AsyncOperationManager.SynchronizationContext;
            _dictionary = new ConcurrentDictionary<TKey, TValue>();
        }

        /// <summary>Event raised when the collection changes.</summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        /// <summary>Event raised when a property on the collection changes.</summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
        /// </summary>
        private void NotifyObserversOfChange()
        {
            var collectionHandler = CollectionChanged;
            var propertyHandler = PropertyChanged;
            if (collectionHandler != null || propertyHandler != null)
            {
                _context.Post(s =>
                {
                    if (collectionHandler != null)
                    {
                        collectionHandler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    }
                    if (propertyHandler != null)
                    {
                        propertyHandler(this, new PropertyChangedEventArgs("Count"));
                        propertyHandler(this, new PropertyChangedEventArgs("Keys"));
                        propertyHandler(this, new PropertyChangedEventArgs("Values"));
                    }
                }, null);
            }
        }

        /// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
        /// <param name="item">The item to be added.</param>
        /// <returns>Whether the add was successful.</returns>
        private bool TryAddWithNotification(KeyValuePair<TKey, TValue> item)
        {
            return TryAddWithNotification(item.Key, item.Value);
        }

        /// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
        /// <param name="key">The key of the item to be added.</param>
        /// <param name="value">The value of the item to be added.</param>
        /// <returns>Whether the add was successful.</returns>
        private bool TryAddWithNotification(TKey key, TValue value)
        {
            bool result = _dictionary.TryAdd(key, value);
            if (result) NotifyObserversOfChange();
            return result;
        }

        /// <summary>Attempts to remove an item from the dictionary, notifying observers of any changes.</summary>
        /// <param name="key">The key of the item to be removed.</param>
        /// <param name="value">The value of the item removed.</param>
        /// <returns>Whether the removal was successful.</returns>
        private bool TryRemoveWithNotification(TKey key, out TValue value)
        {
            bool result = _dictionary.TryRemove(key, out value);
            if (result) NotifyObserversOfChange();
            return result;
        }

        /// <summary>Attempts to add or update an item in the dictionary, notifying observers of any changes.</summary>
        /// <param name="key">The key of the item to be updated.</param>
        /// <param name="value">The new value to set for the item.</param>
        /// <returns>Whether the update was successful.</returns>
        private void UpdateWithNotification(TKey key, TValue value)
        {
            _dictionary[key] = value;
            NotifyObserversOfChange();
        }

        #region ICollection<KeyValuePair<TKey,TValue>> Members
        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            TryAddWithNotification(item);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear()
        {
            ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Clear();
            NotifyObserversOfChange();
        }

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

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

        int ICollection<KeyValuePair<TKey, TValue>>.Count
        {
            get { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Count; }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
        {
            get { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).IsReadOnly; }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            TValue temp;
            return TryRemoveWithNotification(item.Key, out temp);
        }
        #endregion

        #region IEnumerable<KeyValuePair<TKey,TValue>> Members
        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
        {
            return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator();
        }
        #endregion

        #region IDictionary<TKey,TValue> Members
        public void Add(TKey key, TValue value)
        {
            TryAddWithNotification(key, value);
        }

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

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

        public bool Remove(TKey key)
        {
            TValue temp;
            return TryRemoveWithNotification(key, out temp);
        }

        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 { UpdateWithNotification(key, value); }
        }
        #endregion
    }
}
Dative answered 31/10, 2014 at 13:50 Comment(15)
This line occurs error: IEnumerator IEnumerable.GetEnumerator() { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator(); }Offensive
I've used the code and it works. What error message are you getting?Dative
Error message: Using the generic type 'System.Collections.Generic.IEnumerator<T>' requires 1 type argumentsOffensive
replace that with: IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator(); }Gnome
I can't get to call .Clear() because it doesnt exist. Any ideas?Chian
.clear has been part if icollection since DotNET 2.0. It likely is a problem on the icollection type you are using and not this code. Are you using this on a custom icollection class or on one of the built-in types?Dative
A nice addition would be to return the "Keys" and "Values" as an ObservableCollection as well, or some other collection that implements INotifyPropertyChanged/INotifyCollectionChanged.Graz
You can! It's generic, so just specify this during implementation/instantiation. If you want to force this behavior, create a derived/inherited class that narrows the definition.Dative
Depending on the usage, you may want to make _context.Post(...) use _context.Send(...) instead for synchronous event handling. I ran into race conditions trying to use it asynchronously.Fourpence
To notify only Resets does not correctly fulfill the INotifyCollectionChanged interface.Pepper
@Chian To get access to the Clear method you must cast the dictionary to the ICollection<KeyValuePair<TKey, TValue>>. I'd suggest using a following extension method: public static ICollection<KeyValuePair<TKey, TValue>> AsCollection<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) { return dictionary; }Acrobatic
@IgnacioSolerGarcia yeah I second thatMetatarsus
@IgnacioSolerGarcia and Matthew ... correct collection notifications hereGavingavini
I appreciate the commentary. I was pasting the source code that is part of the NuGet package.Dative
thank you @VoteCoffee, I installed ParallelExtensionsExtras, simply use ObservableConcurrentDictionary under using System.Collections.Concurrent; , then bumps, it works =]Diesel
G
31

I'd suggest that you implement the IDictionary<TKey, TValue> instead of inheriting from Dictionary<TKey, TValue>. Since you have to use new rather than override it's possible that the methods are simply being called on the base class rather than your class. I'd be tempted to use a Dictionary<TKey, TValue> internally to do the actual storing of data.

In fact I found this: http://blogs.microsoft.co.il/blogs/shimmy/archive/2010/12/26/observabledictionary-lt-tkey-tvalue-gt-c.aspx

Groping answered 14/4, 2011 at 12:51 Comment(2)
using xaml how do you bind the SelectedValuePath and SelectedValue?Prent
Unfortunately, the blogpost doesn't seem to specify under which license terms the code may be used.Schottische
I
15

Your solution - Fixed ;)

public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged {
    public ObservableDictionary( ) : base( ) { }
    public ObservableDictionary(int capacity) : base(capacity) { }
    public ObservableDictionary(IEqualityComparer<TKey> comparer) : base(comparer) { }
    public ObservableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary) { }
    public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer) { }
    public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer) { }

    public event NotifyCollectionChangedEventHandler CollectionChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    public new TValue this[TKey key] {
        get {
            return base[key];
        }
        set {
            TValue oldValue;
            bool exist = base.TryGetValue(key, out oldValue);
            var oldItem = new KeyValuePair<TKey, TValue>(key, oldValue);
            base[key] = value;
            var newItem = new KeyValuePair<TKey, TValue>(key, value);
            if (exist) {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem, oldItem, base.Keys.ToList( ).IndexOf(key)));
            } else {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItem, base.Keys.ToList( ).IndexOf(key)));
                this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
            }
        }
    }

    public new void Add(TKey key, TValue value) {
        if (!base.ContainsKey(key)) {
            var item = new KeyValuePair<TKey, TValue>(key, value);
            base.Add(key, value);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, base.Keys.ToList( ).IndexOf(key)));
            this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
        }
    }

    public new bool Remove(TKey key) {
        TValue value;
        if (base.TryGetValue(key, out value)) {
            var item = new KeyValuePair<TKey, TValue>(key, base[key]);
            bool result = base.Remove(key);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, base.Keys.ToList( ).IndexOf(key)));
            this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
            return result;
        }
        return false;
    }

    public new void Clear( ) {
        base.Clear( );
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        this.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
    }

    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
        if (this.CollectionChanged != null) {
            this.CollectionChanged(this, e);
        }
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
        if (this.PropertyChanged != null) {
            this.PropertyChanged(this, e);
        }
    }
}
Intension answered 19/1, 2013 at 1:26 Comment(6)
Any reason why this class is partial?Never
@MasterMastic: you would have to ask the OP that question. Marciej provided the class declaration the same as the OP.Freestyle
@Never there is no reason to use partial.Intension
in your remove function you have to query the key before you remove the item from the dictionary or else the key will always be -1 :)Skatole
Is it working in the case of an initialization? Dic = new ObservableDictionary<string, string>(){ {"1", "First"} }; ?Wolfgang
@Wolfgang It supports all standard dictionary initialization, I tested it is also valid solution for new .NET 5.0. However with new C# we could improve the syntax of the implementation.Intension
Z
3

I rolled out my own: https://www.nuget.org/packages/hellosam.net.collections/

It uses AVL tree so operations are O(log N) instead, where most implementations I have seen using List.indexOf() are O(N).

It can even observe your item INotifyPropertyChanged and convert them to a observable collection event, so to keep the DataGrid sort/group response to the change.

Zelma answered 31/8, 2015 at 18:51 Comment(1)
nuget package missing BinaryTree.cs Also, enumerator enumerates values instead of key value pairs which is atypical for dictionaries.Skatole
G
0

As Ignatio and Matthew pointed out in this answer, only raising the Reset collection change notification is incorrect, and not very useful if the caller needs to know what actually changed. Fortunately it is easily corrected. Note this version uses Send instead of Post as Nathan mentioned in the earlier answer, because WPF is sensitive to reporting back the correct index upon removal, and getting it wrong yields this confusing exception. (Caveat emptor: I'm still not completely convinced the reported index will be fully reliable if there are many overlapping changes, especially given that Dictionaries should be treated as un-ordered.)

//--------------------------------------------------------------------------
// 
//  Copyright (c) Microsoft Corporation.  All rights reserved. 
// 
//  File: ObservableConcurrentDictionary.cs
//
//--------------------------------------------------------------------------

using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;

namespace System.Collections.Concurrent
{
    /// <summary>
    /// Provides a thread-safe dictionary for use with data binding.
    /// </summary>
    /// <typeparam name="TKey">Specifies the type of the keys in this collection.</typeparam>
    /// <typeparam name="TValue">Specifies the type of the values in this collection.</typeparam>
    [DebuggerDisplay("Count={Count}")]
    public class ObservableConcurrentDictionary<TKey, TValue> :
        ICollection<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>,
        INotifyCollectionChanged, INotifyPropertyChanged
    {
        private readonly SynchronizationContext _context;
        private readonly ConcurrentDictionary<TKey, TValue> _dictionary;

        /// <summary>
        /// Initializes an instance of the ObservableConcurrentDictionary class.
        /// </summary>
        public ObservableConcurrentDictionary()
        {
            _context = AsyncOperationManager.SynchronizationContext;
            _dictionary = new ConcurrentDictionary<TKey, TValue>();
        }

        /// <summary>Event raised when the collection changes.</summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        /// <summary>Event raised when a property on the collection changes.</summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
        /// </summary>
        private void NotifyObserversOfChange()
        {
            var collectionHandler = CollectionChanged;
            var propertyHandler = PropertyChanged;
            if (collectionHandler != null || propertyHandler != null)
            {
                _context.Send(s =>
                {
                    collectionHandler?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Count"));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Keys"));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Values"));
                }, null);
            }
        }

        /// <summary>
        /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
        /// </summary>
        /// <param name="actionType">Add or Update action</param>
        /// <param name="changedItem">The item involved with the change</param>
        private void NotifyObserversOfChange(NotifyCollectionChangedAction actionType, object changedItem)
        {
            var collectionHandler = CollectionChanged;
            var propertyHandler = PropertyChanged;
            if (collectionHandler != null || propertyHandler != null)
            {
                _context.Send(s =>
                {
                    collectionHandler?.Invoke(this, new NotifyCollectionChangedEventArgs(actionType, changedItem));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Count"));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Keys"));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Values"));
                }, null);
            }
        }

        /// <summary>
        /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
        /// </summary>
        /// <param name="actionType">Remove action or optionally an Add action</param>
        /// <param name="item">The item in question</param>
        /// <param name="index">The position of the item in the collection</param>
        private void NotifyObserversOfChange(NotifyCollectionChangedAction actionType, object item, int index)
        {
            var collectionHandler = CollectionChanged;
            var propertyHandler = PropertyChanged;
            if (collectionHandler != null || propertyHandler != null)
            {
                _context.Send(s =>
                {
                    collectionHandler?.Invoke(this, new NotifyCollectionChangedEventArgs(actionType, item, index));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Count"));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Keys"));
                    propertyHandler?.Invoke(this, new PropertyChangedEventArgs("Values"));
                }, null);
            }
        }

        /// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
        /// <param name="item">The item to be added.</param>
        /// <returns>Whether the add was successful.</returns>
        private bool TryAddWithNotification(KeyValuePair<TKey, TValue> item)
            => TryAddWithNotification(item.Key, item.Value);

        /// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
        /// <param name="key">The key of the item to be added.</param>
        /// <param name="value">The value of the item to be added.</param>
        /// <returns>Whether the add was successful.</returns>
        private bool TryAddWithNotification(TKey key, TValue value)
        {
            bool result = _dictionary.TryAdd(key, value);
            int index = IndexOf(key);
            if (result) NotifyObserversOfChange(NotifyCollectionChangedAction.Add, value, index);
            return result;
        }

        /// <summary>Attempts to remove an item from the dictionary, notifying observers of any changes.</summary>
        /// <param name="key">The key of the item to be removed.</param>
        /// <param name="value">The value of the item removed.</param>
        /// <returns>Whether the removal was successful.</returns>
        private bool TryRemoveWithNotification(TKey key, out TValue value)
        {
            int index = IndexOf(key);
            bool result = _dictionary.TryRemove(key, out value);
            if (result) NotifyObserversOfChange(NotifyCollectionChangedAction.Remove, value, index);
            return result;
        }

        /// <summary>Attempts to add or update an item in the dictionary, notifying observers of any changes.</summary>
        /// <param name="key">The key of the item to be updated.</param>
        /// <param name="value">The new value to set for the item.</param>
        /// <returns>Whether the update was successful.</returns>
        private void UpdateWithNotification(TKey key, TValue value)
        {
            _dictionary[key] = value;
            NotifyObserversOfChange(NotifyCollectionChangedAction.Replace, value);
        }

        /// <summary>
        /// WPF requires that the reported index for Add/Remove events are correct/reliable. With a dictionary there
        /// is no choice but to brute-force search through the key list. Ugly.
        /// </summary>
        private int IndexOf(TKey key)
        {
            var keys = _dictionary.Keys;
            int index = -1;
            foreach(TKey k in keys)
            {
                index++;
                if (k.Equals(key)) return index;
            }
            return -1;
        }

        // ICollection<KeyValuePair<TKey,TValue>> Members


        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
            => TryAddWithNotification(item);

        void ICollection<KeyValuePair<TKey, TValue>>.Clear()
        {
            _dictionary.Clear();
            NotifyObserversOfChange();
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
            => ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Contains(item);

        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
            => ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);

        int ICollection<KeyValuePair<TKey, TValue>>.Count
        {
            get => _dictionary.Count;
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
        {
            get => ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).IsReadOnly;
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
            => TryRemoveWithNotification(item.Key, out TValue temp);


        // IEnumerable<KeyValuePair<TKey,TValue>> Members


        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
            => _dictionary.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator()
            => _dictionary.GetEnumerator();


        // IDictionary<TKey,TValue> Members


        public void Add(TKey key, TValue value)
            => TryAddWithNotification(key, value);

        public bool ContainsKey(TKey key)
            => _dictionary.ContainsKey(key);

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

        public bool Remove(TKey key)
            => TryRemoveWithNotification(key, out TValue temp);

        public bool TryGetValue(TKey key, out TValue value)
            => _dictionary.TryGetValue(key, out value);

        public ICollection<TValue> Values
        {
            get => _dictionary.Values;
        }

        public TValue this[TKey key]
        {
            get => _dictionary[key];
            set => UpdateWithNotification(key, value);
        }
    }
}
Gavingavini answered 3/2, 2019 at 13:12 Comment(1)
These collections are probably a better option: codeproject.com/Articles/64936/…Gavingavini
N
-3

I have managed to find a solution - workaround

public delegate void CollectionAlteredEventHander( object sender , EventArgs e);

public partial class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{

    /*Class contructors*/

    public event CollectionAlteredEventHander CollectionAltered;

    public new TValue this[TKey key]
    {
        get
        {
            return base[key];
        }
        set
        {
            OnCollectionAltered(new EventArgs());
            base[key] = value;
        }
    }

    public new void Add(TKey key, TValue value)
    {
        int idx = 0;
        if (!TryGetKeyIndex(this, key, ref idx))
        {
            base.Add(key, value);
            OnCollectionAltered(new EventArgs());
        }
    }

    public new bool Remove(TKey key)
    {
        int idx = 0; 
        if( TryGetKeyIndex( this ,key, ref idx))
        {
            OnCollectionAltered(new EventArgs());
            return base.Remove(key);
        }
        return false;
    }

    private bool TryGetKeyIndex(ObservableDictionary<TKey, TValue> observableDictionary, TKey key , ref int idx)
    {
        foreach (KeyValuePair<TKey, TValue> pair in observableDictionary) 
        {
            if (pair.Key.Equals(key)) 
            {
                return true;
            }
            idx++;
        }
        return false;
    }

    public new void Clear()
    {
        OnCollectionAltered(new EventArgs());
        base.Clear();            
    }

    protected virtual void OnCollectionAltered(EventArgs e)
    {
        if (CollectionAltered != null)
        {
            CollectionAltered(this, e);
        }
    }

}

I've no longer implement the INotifyCollectionChanged interface though.

Niobic answered 15/4, 2011 at 13:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.