I’ve seen a number of forum posts lately expressing a desire for an observable dictionary. Here are links to a couple of them:
Thread 2: Bind observable dictionary
As promised in my response to the latter thread, I am now providing a sample demonstrating how one might implement an observable dictionary. You can download the sample here.
The Observable Dictionary Sample
In this sample, I demonstrate how to bind to a dictionary of button styles. The styles are sorted based on their Key values in the observable dictionary. (Clearly, this should be treated as a nonsensical illustration for demonstration purposes only.)
On the left side of the window is a ListView that is bound to the observable dictionary. If you click an odd-indexed button in the ListView, its corresponding entry is removed from the dictionary. If you click an even-indexed button, its entry is duplicated in the dictionary.
On the right side of the window is a ComboBox whose items are bound to the same dictionary. Beneath the ComboBox is a Button whose style is bound to the selected value of the ComboBox. If you click this button, the dictionary will be cleared and reloaded.
As you click around, you will notice that the bound controls respond appropriately to any changes in the observable dictionary.
So why would someone want to do this?
Dictionaries are easy to work with in code. Perhaps you’ve already written an application that leverages a dictionary to store and retrieve in-memory data. You may now want to provide a view of the dictionary entries in your user interface.
Unfortunately, to provide a dynamic view of your dictionary data, you would need to do a lot of extra work. Namely, you would need to write code to synchronize some observable collection with your dictionary whenever you modify it.
Wouldn’t it be nice if the dictionary, itself, were observable?
The Observable Dictionary Class
What we clearly need is an “observable” dictionary class. You will find an example of such a class in the ObservableDictionary.cs file in the provided sample. What does it mean for a dictionary to be observable? In WPF, an observable class is simply a collection class that provides change notifications whenever its collection changes. It does this by implementing the INotifyPropertyChanged and INotifyCollectionChanged interfaces. This allows it to serve as the “ItemsSource” of a collection control (like a ListBox, ComboBox, or any other ItemsControl). These controls know how to monitor the collection for changes.
For a detailed explanation of how binding to a collection works, see the MSDN documentation. Here is a good starting point.
So how do you implement an observable dictionary?
Dictionaries basically work by aggregating an internal collection of key-value pairs. People often start by trying to derive a class from Dictionary<TKey, TValue> or by trying to implement IDictionary<TKey, TValue> in a class that aggregates a dictionary. The idea is that they could then add the requisite INotifyPropertyChanged and INotifyCollectionChanged interfaces and have an observable dictionary.
There’s a major problem with this approach. Observable collections are indexed by integer. In order to fire the necessary collection change notifications, you must know the index of an entry when it changes. Unfortunately, none of the interfaces exposed by the Dictionary<TKey, TValue> class are indexed by integer. And if you derive directly from Dictionary<TKey, TValue>, you do not have access to the underlying collection. As a result, an attempt to create an observable dictionary by deriving from or aggregating a dictionary won’t work.
To truly create an observable dictionary, you need to aggregate your own collection (usually of type KeyedCollection<TKey, TValue>). Then you must implement a whole slew of interfaces on top of the collection, as shown in the following class declaration:
public class ObservableDictionary <TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback, INotifyCollectionChanged, INotifyPropertyChanged
By implementing all of the above interfaces, you ensure that your dictionary class can be used interchangeably with the CLR’s Dictionary class. You should only need to change the declaration from Dictionary<TKey, TValue> to ObservableDictionary<TKey, TValue> in your code.
Pros and Cons
The benefit to using an observable dictionary, of course, is that the dictionary can serve as the ItemsSource for a databound control and you can still access the dictionary in code the same way you access any other dictionary. It is truly an indexed dictionary of objects.
There are certainly some limitations inherent in the very idea of making a dictionary observable. Dictionaries are built for speed. When you impose the behaviors of an observable collection on a dictionary so that the framework can bind to it, you add overhead.
Also, a dictionary exposes its Values and Keys collections through separate properties of the same name. These collections are of types Dictionary<TKey, TValue>.ValueCollection and Dictionary<TKey, TValue>.KeyCollection, respectively. These CLR-defined collections are not observable. As such, you cannot bind to the Values collection or to the Keys collection directly and expect to receive dynamic collection change notifications. You must instead bind directly to the observable dictionary.
The Observable Sorted Dictionary Class
Another thing to note is that a dictionary doesn’t typically store its data in a deterministic order. When binding to an observable dictionary, you cannot simply apply a sort order via a CollectionView because the exposed interfaces do not support sorting. For this reason, I include an ObservableSortedDictionary class in the attached sample.
Binding to an Observable Dictionary
When you bind to an observable dictionary, you are really binding to a collection of KeyValuePair objects. A KeyValuePair is an object which makes the entry’s key available via an appropriately named Key property and its value available via a Value property. Your item templates must take this into consideration and provide the appropriate binding paths. The provided sample illustrates how to do this.
All the Standard Caveats Apply
As always, this sample is provided “as is”. It has not been heavily tested and there are probably lots of improvements that could be made. Use it and modify it as you see fit. Let me know how it works for you, what improvements you make, what bugs you find, etc.