General Observable Dictionary Class for DataBinding/WPF C#

I'm trying to create a Observable Dictionary Class for WPF DataBinding in C#. I found a nice example from Andy here: Two Way Data Binding With a Dictionary in WPF

According to that, I tried to change the code to following:

class ObservableDictionary : ViewModelBase
{
    public ObservableDictionary(Dictionary<TKey, TValue> dictionary)
    {
        _data = dictionary;
    }

    private Dictionary<TKey, TValue> _data;

    public Dictionary<TKey, TValue> Data
    {
        get { return this._data; }
    }

    private KeyValuePair<TKey, TValue>? _selectedKey = null;
    public KeyValuePair<TKey, TValue>? SelectedKey
    {
        get { return _selectedKey; }
        set
        {
            _selectedKey = value;
            RaisePropertyChanged("SelectedKey");
            RaisePropertyChanged("SelectedValue");
        }
    }

    public TValue SelectedValue
    {
        get
        {
            return _data[SelectedKey.Value.Key];
        }
        set
        {
            _data[SelectedKey.Value.Key] = value;
            RaisePropertyChanged("SelectedValue");
        }
    }
}

}

Unfortunately I still don't know how to pass "general" Dictionary Objects.. any ideas?

Thank you!

Cheers


Solution 1:

If you really want to make an ObservableDictionary, I'd suggest creating a class that implements both IDictionary and INotifyCollectionChanged. You can always use a Dictionary internally to implement the methods of IDictionary so that you won't have to reimplement that yourself.

Since you have full knowledge of when the internal Dictionary changes, you can use that knowledge to implement INotifyCollectionChanged.

Solution 2:

  • ObservableDictionary(Of TKey, TValue) - VB.NET
  • ObservableDictionary<TKey, TValue> - C#

Solution 3:

For historical purposes and to put people on the "current" path... It's important to know that Microsoft now solve this requirement in their Windows Store "Basic Page" template in Visual Studio 2012. In order to support the LayoutAwarePage they generate a private ObservableDictionary class.

However they implement a new IObservableMap interface rather than IDictionary directly. This interface adds a MapChanged event and MapChangedEventHandler, defined in the Windows.Foundation.Collections namespace.

The snippet below is just the ObservableDictionary class from the LayoutAwarePage.cs generated in the "Common" folder of your project:

    /// <summary>
    /// Implementation of IObservableMap that supports reentrancy for use as a default view
    /// model.
    /// </summary>
    private class ObservableDictionary<K, V> : IObservableMap<K, V>
    {
        private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<K>
        {
            public ObservableDictionaryChangedEventArgs(CollectionChange change, K key)
            {
                CollectionChange = change;
                Key = key;
            }

            public CollectionChange CollectionChange { get; private set; }
            public K Key { get; private set; }
        }

        private Dictionary<K, V> _dictionary = new Dictionary<K, V>();
        public event MapChangedEventHandler<K, V> MapChanged;

        private void InvokeMapChanged(CollectionChange change, K key)
        {
            var eventHandler = MapChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
            }
        }

        public void Add(K key, V value)
        {
            _dictionary.Add(key, value);
            InvokeMapChanged(CollectionChange.ItemInserted, key);
        }

        public void Add(KeyValuePair<K, V> item)
        {
            Add(item.Key, item.Value);
        }

        public bool Remove(K key)
        {
            if (_dictionary.Remove(key))
            {
                InvokeMapChanged(CollectionChange.ItemRemoved, key);
                return true;
            }
            return false;
        }

        public bool Remove(KeyValuePair<K, V> item)
        {
            V currentValue;
            if (_dictionary.TryGetValue(item.Key, out currentValue) &&
                Object.Equals(item.Value, currentValue) && _dictionary.Remove(item.Key))
            {
                InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
                return true;
            }
            return false;
        }

        public V this[K key]
        {
            get
            {
                return _dictionary[key];
            }
            set
            {
                _dictionary[key] = value;
                InvokeMapChanged(CollectionChange.ItemChanged, key);
            }
        }

        public void Clear()
        {
            var priorKeys = _dictionary.Keys.ToArray();
            _dictionary.Clear();
            foreach (var key in priorKeys)
            {
                InvokeMapChanged(CollectionChange.ItemRemoved, key);
            }
        }

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

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

        public bool TryGetValue(K key, out V value)
        {
            return _dictionary.TryGetValue(key, out value);
        }

        public ICollection<V> Values
        {
            get { return _dictionary.Values; }
        }

        public bool Contains(KeyValuePair<K, V> item)
        {
            return _dictionary.Contains(item);
        }

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

        public bool IsReadOnly
        {
            get { return false; }
        }

        public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
        {
            return _dictionary.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _dictionary.GetEnumerator();
        }

        public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
        {
            if (array == null) throw new ArgumentNullException("array");
            int arraySize = array.Length;
            foreach (var pair in _dictionary)
            {
                if (arrayIndex >= arraySize) break;
                array[arrayIndex++] = pair;
            }
        }
    }

Further examination of the new Windows.Foundation.Collections namespace shows a load of new interfaces defined, but only one PropertySet class implemented. Actually this seems like a pretty good ObservableDictionary itself. But there must be a reason why MS still generate a private ObservableDictionary. So further examination is required here to identify the pros and cons.

In short, either the PropertySet or your own IObservableMap based ObservableDictionary should solve immediate requirements for "current" Windows 8 and Phone 8 projects. However for older frameworks (WPF 4 and Phone 7.5) there is still more work to do.

Solution 4:

Microsoft has an implementation of an observable dictionary in the MSFT.ParallelExtensionsExtras package available via Nuget: https://www.nuget.org/packages/ParallelExtensionsExtras/

ObservableConcurrentDictionary<TKey, TValue>

Solution 5:

I suggest the following article, where is explanied how to implement an observable dictionary and the source code is available with a sample:

http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/