Sunday, October 12, 2008   Search 
Links

 

Disciple
 Can I bind my ItemsControl to a dictionary? Minimize
Location: BlogsAsk Dr. WPF    
Posted by: Dr. WPF 9/16/2007 10:47 PM

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. J)

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

Permalink |  Trackback

Comments (27)   Add Comment
bug fix    By Nathan on 11/23/2007 9:35 PM
bug in the IDictionary index property... recursive calls == stack overflow

this was my simple fix:

object IDictionary.this[object key]
{
get { return ((IDictionary)this)[(TKey)key]; }
set { DoSetEntry((TKey)key, (TValue)value); }
}

Re: bug fix    By Dr. WPF on 12/17/2007 11:00 AM
Great catch, Nathan! I have added the fix to the sample. Much thanks! -dw

Re: Can I bind my ItemsControl to a dictionary?    By marco.ragogna on 1/21/2008 3:18 AM
I was planning to create by myself but a brief search in google take me here. Thanks for the code I will try it and come back if I find some suggestion or problem

thank you for sharing

Problem sorting ObservableSortedDictionary    By Rodrigo on 2/11/2008 12:08 AM
Hi,
I'm having trouble sorting the ObservableSortedDictionary. I create a CollectionViewSource bound to the ObservableSortedDictionary, and create a ListBox which binds to the CollectionViewSource. This works fine, and all the items in the collection are displayed as expected. However when I add some sort descriptions as follows:
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Key" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
I get an InvalidOperationException ('System.Windows.Data.CollectionView' view does not support sorting.) What am I doing wrong?

Any help would be much appreciated! Thanks,

Rod

Problem sorting ObservableSortedDictionary    By Dr. WPF on 2/11/2008 12:17 AM
Hi Rod,

Sorry. I really should have been more clear in my description of the sorted dictionary class.

Just like the standard dictionary, this class does not support sorting via a CollectionView. Rather, it provides a dictionary that stores its items already sorted. You must supply an IComparer instance when constructing the class. Clearly, this adds some overhead to the dictionary.

You would only use a sorted dictionary if you knew that you would be binding to it and you needed the bound items to appear in a certain order. This is no where near as convenient as the CollectionView approach where you are really only sorting a view of the items. Again, a dictionary does not lend itself nicely to such an approach, since it is really built for speed.

Cheers,
-dw

Re: Can I bind my ItemsControl to a dictionary?    By marco.ragogna on 2/21/2008 6:33 AM
Hi Dr.Wpf,
you're class is really interesting and helpful.
I found only a small bug in it but I am not really sure how to solve it.

Maybe you can help me...

The problem happens when I try to iterate in the ObservableDictionary collection.

For example the following code:

obsDictionary.Last().Value

return me the following expcetion:

Unable to cast object of type 'Enumerator[System.Collections.DictionaryEntry]' to type 'System.Collections.Generic.IEnumerator`1[System.Collections.Generic.KeyValuePair`2[System.String,System.Int32]]'.

locate in

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

do you have any idea. If you prefer to not use the blog as support I can make the question in the MSDN forum ;)

Thank you

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 2/21/2008 11:09 AM
Thx Marco. Yes, that's a bug... I was a bit too hasty in completing some of the interfaces. That line should read as follows:

return (IEnumerator<KeyValuePair<TKey, TValue>>)TrueDictionary.GetEnumerator();

I've updated the code in the sample. Keep me posted if you encounter any more such issues. :)

Thanks again!
-dw

Re: Can I bind my ItemsControl to a dictionary?    By marco.ragogna on 2/22/2008 12:34 AM
Thanks a lot for your help, this class if very useful. Unfortunately for me I discover yesterday that I cannot use it in my current project.

The problem I discovered is that I cannot bind in TwoWay mode the Key and Value properties of ObservableDictionary items because they are, of course (but I didn't know before yesterday :) ) ReadOnly because part of structure KeyValuePair.

In any case this is indeed a very nice class that I will use in other projects.

Keep up the good work you are doing in sharing WPF code and hints.

Re: Can I bind my ItemsControl to a dictionary?    By Paul on 4/11/2008 5:43 AM
This is probably highlighting my lack of low down knowlage on the subject, but, when binding an Infragistics xamDataGrid control to your ObservableDictionary only the type of the value is displayed where as using a standard Dictionary object the value (in this case a List (Of T) object) is fully enumerated. Is this one of the limitations of making the Dictionary observable?

Many thanks

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 4/11/2008 11:44 AM
Hi Paul,

This class should behave in the same manner as a native dictionary when used as the source for a collection binding (unless the control is designed to behave differently when bound to a dictionary).

If you can send me a simple repro that demonstrates the difference you're seeing, I'd be willing to take a look.

Cheers,
-dw

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 4/28/2008 3:48 PM
Hi Paul,

Thanks so much for sending the sample application! You were absolutely correct! The default enumerator for the native Dictionary class returns KeyValuePair objects, whereas my enumerator was returning DictionaryEntry objects.

I have updated the code to include a custom enumerator so that it behaves just like the native class. Please give this a try and let me know if it works better for you.

Thanks again for helping me make this code better!
-dw

Re: Can I bind my ItemsControl to a dictionary?    By Nicole Taylor on 5/29/2008 6:14 AM
I am also trying to bind a combo box control to an ObservableDictionary. Everything shows fine based on the code below. I am coding everything in the code behind because my XAML developer uses Expression Blend and we have trouble going back and forth between Visual Studio and Expression Blend. When I change the selection in the combo box, the OnPropertyChanged(string name) is called but the string "name" is always null. Also, when I add a new KeyValue to the collection, the combo box doesn't show the new entry even though it is successfully added to the dictionary. I am pretty sure I am not binding to the Dictionary properly (as you can see from the my commented code I am trying several different methods). I am wondering if you have any suggestions for doing all this binding in the code behind.Thanks,Nicole

Binding binding1 = new Binding("UserTypeName");
binding1.Source = UserTypes.Values;
binding1.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
binding1.Mode = BindingMode.TwoWay;
  //_cmbUserTypes.SetBinding(ItemsControl.ItemsSourceProperty, binding1);
//_cmbUserTypes.SetBinding(ItemsControl.DisplayMemberPathProperty, "UserTypeName");
//_cmbUserTypes.SetBinding(System.Windows.Controls.Primitives.Selector.SelectedValuePathProperty, "UserTypeID");
  _cmbUserTypes.ItemsSource = UserTypes.Values;
_cmbUserTypes.DisplayMemberPath = "UserTypeName";
_cmbUserTypes.SelectedValuePath = "UserTypeID";
  //_cmbUserTypes.SetBinding(System.Windows.Controls.Primitives.Selector.SelectedItemProperty, binding1);

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 5/30/2008 9:01 AM
Hi Nicole,

I'm not sure I fully understand what you're trying to do in the code snippet, but I do notice that you're setting the source of the binding to be the Values collection. As I mentioned in the article, the Values collection is not observable. The source must be the ObservableDictionary itself. binding1.Source = UserTypes;

I don't think you want a two-way binding. There's no way to add entries to the dictionary automatically if entries are added to the ComboBox.

You probably want to set the binding via your first example: _cmbUserTypes.SetBinding(ItemsControl.ItemsSourceProperty, binding1);

I would need to see a full repro to really understand your scenario.

Re: Can I bind my ItemsControl to a dictionary? - TreeView and Remove    By Martin on 6/30/2008 1:46 PM
Hello Dr. WPF !

First of all, thank you for your ObservableDictionary class. I really like it since it helps to keep my code clean! In my project, I bind a WPF TreeView control to the ObservableDictionary and it works perfectly, except for one operation : Remove.

It crashes on this line :
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, entry, index));

With this exception/stack trace :

System.InvalidOperationException was unhandled
  Message="Added item does not appear at given index '4'."
  Source="PresentationFramework"
  StackTrace:
     at MS.Internal.Data.EnumerableCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
     at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
     at Collections.ObservableDictionary`2.OnCollectionChanged(NotifyCollectionChangedEventArgs args) in D:\Projets\Collections\ObservableDictionary.cs:line 199
     at Collections.ObservableDictionary`2.FireEntryRemovedNotifications(DictionaryEntry entry, Int32 index) in D:\Projets\Collections\ObservableDictionary.cs:line 311
     at Collections.ObservableDictionary`2.DoRemoveEntry(TKey key) in D:\Projets\Collections\ObservableDictionary.cs:line 267
     at Collections.ObservableDictionary`2.Remove(TKey key) in D:\Projets\Collections\ObservableDictionary.cs:line 152
     at Configuration.Snippets.RemoveSnippet(Snippet s) in D:\Projets\Configuration\Snippets.cs:line 64
     at TextExpander.Window1.btnRemoveSnippet_Click(Object sender, RoutedEventArgs e) in D:\Projets\MXG.Window1.xaml.cs:line 126
     at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
     at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
     at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
     at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
     at System.Windows.Controls.Primitives.ButtonBase.OnClick()
     at System.Windows.Controls.Button.OnClick()
     [...]

Do you have an idea of what is going on?

Thank you,
Martin

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 6/30/2008 1:47 PM
Hey Martin. Sorry for the late reply. This sounds like a bug worth investigating. Can you send me a repro?

Re: Can I bind my ItemsControl to a dictionary?    By Andreas on 7/22/2008 4:26 AM
Hi Dr. WPF!

I am having exactly the same problem as Martin. When removing an entry from the dictionary, I get the InvalidOperationException.

Did you find a solution to this one already? Would be great!

Thanks a lot!
Andreas

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 7/22/2008 12:35 PM
Hi Andreas,

Martin was kind enough to send me a stack trace, but I would still love to get a repro that I could actively debug. If you have something handy (and not too complicated ;-)), I'd love to get it and track this issue down.

Thanks!
-dw

Re: Can I bind my ItemsControl to a dictionary?    By Andreas on 7/22/2008 11:35 PM
Hi again,

unfortunately I cannot send you the code, and actually it is quite complicated as well. :) But I just replaced the ObservableDictionary with the standard ObservableCollection from the .Net framework and it also throws an exception at exactly the same line of code. It seems like something is wrong in the binding because the entry is successfully removed from the collection, but it is not removed from the ListView. If I just continue the debugging after the exception, the entry is still in the ListView, and upon selection I get another exception because the entry is no longer available...

It looks a little weird to me, but I will try to find the problem and post it here. If you or anyone has an idea, do not hesitate to post it here! At least the problem does not seem to be related to your ObservableDictionary.

Andreas

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 7/23/2008 1:41 AM
Thanks for the follow up, Andreas. Sounds like a tough one, so best of luck. I would love to know the resolution when you figure it out. :)

Re: Can I bind my ItemsControl to a dictionary?    By Andreas on 7/23/2008 11:29 PM
Hey,

I think I figured it out... I had bound several CollectionViews to the same data source, and each view had another filter applied to it. Now, the exception was thrown when I removed an item from View1 which was not visible in View2 due to the filter. It seems like View2 tried to remove the item from the list although it was not even in there. Therefore the provided index was out of range because View2 displayed far less items than View1.

Now I'm struggling with the next exception, but that does not seem to be related to the previous problem. ;)

Andreas

Re: Can I bind my ItemsControl to a dictionary?    By Igor on 8/5/2008 11:37 AM
G'day

I am trying to bind an ObservableDictionary to a DataSource component (without any luck so far...)

- Plan A: to inherit ObservableDictionary from BindingList<KeyValuePair<TKey,TValue>>;
- Plan B: manually implement the following interffaces:
IBindingList
ICancelAddNew
IRaiseItemChangeevents

Any ideas?

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 8/5/2008 11:43 AM
Hi Igor,

The ObservableDictionary class is designed specifically for WPF (or other frameworks that listen to INotifyCollectionChanged and INotifyPropertyChanged). If you wish to use it with other platforms (like WinForms), then yes, you will need to add the additional required interfaces. I probably would not try to derive from BindingList<T>, since that will require a pretty large rearchitecture.

Cheers,
-dw

Added item does not appear at given index    By Nick on 8/13/2008 2:27 PM
Has anyone really solved this? I constantly am getting this if the dictionary is populated immediately on application start up. If it is populated later and modified nothing goes wrong and the UI and dictionary update as expected.

Otherwise you get the error and the ListView or ListBox never updates again and the ObservableDictionary throws exceptions constantly in the background when an item in the dictionary has changed. This seems to happen on SetEntry when you have a new update and you remove the old value and populate the new. The exception seems to happen immediately after the NotificationCollectionChange event is fired.

Any help is welcomed. :)

Re: Can I bind my ItemsControl to a dictionary?    By Dr. WPF on 8/13/2008 2:54 PM
If you can give me a simple repro of this, Nick, I'd be happy to take a look. :)

Added item does not appear at given index    By Nick on 8/14/2008 9:01 PM
Sent you a fully working example that breaks the dictionary.

Re: Added item does not appear at given index    By Nick on 8/14/2008 9:04 PM
BTW, if you are updating the Visual Element Via TCP WCF or some other transport.. then you will want to check the this.IsLoaded on your Window or Framework Element before you update the ObservableDictionary. I don't know if this is a bug or something you should do for all ObservableCollections.
Hope this helps.

FIXED! Added item does not appear at given index    By Dr. WPF on 8/14/2008 9:16 PM
Thanks so much for the repro, Nick! That was exactly what I needed.

It turns out that I missed a couple of things in the change that I made for Paul back in April. This fully explains the problem that Martin reported (and probably others too).

I have posted an updated sample that contains the simple 2-line fix. Please grab it and verify that it works for you.

Wrt asynchronously accessing the collection, there are definitely some issues that you must address when accessing any observable collection. As long as the work is done on the same dispatcher that was used to create the collection, everything should be copacetic. You certainly shouldn't see exceptions based on whether the elements have loaded. This would also be a good thing to test with the updated ObservableDictionary class.

Thanks again for the repro! Someday I hope to have extra time to really optimize a few things in the dictionary, but at least the current version should be more solid now. :-)

Cheers,
-dw


Your name:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment   Cancel 

  
Copyright 2007 by Dr. WPF   Terms Of Use  Privacy Statement