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 1: Observable Dictionary, problem with Remove
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.
Cheers,
Dr. WPF
Hot damn! I rarely use 3rd party code because I always seem to run into issues. I just removed some of the serializable stuff (I’m using Silverlight 3) and it worked perfectly. Big thanks for takin’ the time to do this!
Excellent, Rocky! Glad to hear it works in SL… I’ve been meaning to port it and test it, but you saved me the time. 😀
add this if you want your solution to serialize to xml
http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx
I started out looking for an observable dictionary, and now i wonder if its even appropriate. What is a dictionary really? It’s an index to a collection of items. There are many ways to find an item in a collection. Before using an observable dictionary, consider if a hash table index is even really needed. It may be over kill to index a screen of items, and we are just talking about WPF here. More than likely a linear iteration will be all the speed you need for finding a visible item.
At the very least, first use a linear search. If ya really need to search faster, then go for a dictionary or some other indexing system.
Another approach is to really just keep things in a dictonary, and only fill your UX with “pages” of items form the dictionary. That’s the whole point of some of the virtualized controls.
My two cents, I hope it helps someone think about the problem differently.
Hi Dr WPF/All,
Does anybody know or can tell me a way of binding to a dictionary using a dynamic key.
e.g. Path = “.[]”
I’ve used your (Dr WPF) ObjectReference class in combination with the controls tag property to hold the key info, I’ve also tried MultiBinding.
The problem i have is converting back, unless the path can be set dynamically (which is difficult not being a dp) I have to update the data source (indexer) from within the converter which is obviously wrong and bypasses validation.
Has anyone got any ideas?
TIA
Sam
apologies, submission removed content of ‘[]’, should read:
e.g. Path=’.[{Binding indexerKey}]’
Thanks,
Sam
Ok, well I’ve programatically set the Binding on the control’s Load event by building the path string based on other bound properties.
Something like:
private void Control_Loaded(object sender, RoutedEventArgs e)
{
// MyDataItem would contain a reference to a data source and key
// the tag is assigned to be MyDataItem created using a IMultiConverter defined in the xaml
MyDataItem dataItem = ((sender as FrameworkElement).Tag as MyDataItem);
Binding b = new Binding(“[“+dataItem.Key+”]”);
b.Source = dataItem.DataSourceWithIndexer;
if (sender is TextBox)
{
(sender as TextBox).SetBinding(TextBox.TextProperty, b);
}
}
This works, but was hoping to do this within the XAML.
Also, is it correct to set Binding in the Load event?
Thanks again
Sam
I think the _keyedEntryCollection assignment is missing for the Deserialization constructor that causes the deserializating failure.
another line should be added to Deserialization constructor or the TrueDictionary will not be updated when deserializing:
_dictionaryCacheVersion = -1;
This solved exactly the problem I was having! Thanks for writing it.
I did have a bit of trouble for bit there as I had bound my source to Dictionary.Values, but after reading through the comments I was able to bind the source directly to the Dictionary as suggested and then change my data context to ‘Value’ in the XAML and it worked like a charm.
One stumbling block I ran in to was that the ObservableSortedDictionary constructor requires an explicit IComparer object whereas the base SortedDictionary does not. Your observable version would have been a lot easier to drop in if it also used a default comparer when none is supplied.
Cheers,
Brian