Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Binding to a Dictionary in WPF and Silverlight

Pete Brown - 08 March 2010

I saw this tweet in my Windows Client tweetstream today:

image

I thought, "what a good idea for a sample." I know that binding in WPF and Silverlight can be a challenge, especially if you're new to the technology. It can be even more difficult when you're working with an edge case like binding using indexers.

So, here's a quickie on binding to a Dictionary<TKey, TValue> in WPF (note that the concept applies to Silverlight as well).

Binding to Items by Key

To bind to an element by a string key, you need to include the key inside brackets, just as you would in C# code. You omit any quotes, though. For example, if you want to bind a textbox to the dictionary item with a key of "field1", and support both display and editing, you'd write this:

<TextBox Text="{Binding [field1], Mode=TwoWay}" />

That assumes your dictionary is in scope as the DataContext. Let's say you have a view model with a dictionary called "Fields" and you want to bind to field1 in that dictionary, but your view model, not the dictionary, is the DataContext. Simple:

<TextBox Text="{Binding Fields[field1], Mode=TwoWay}" />

You can take a similar approach to use numeric indexers in collections: just use a number instead of the string.

Example

Here's a quick example that shows binding to the dictionary, using a simple code-behind approach.

Code-Behind

The code-behind is shown below. In a real application, I recommend this (or similar) code be placed in a view model.

public partial class MainWindow : Window
{
    Dictionary<string, object> _data = new Dictionary<string, object>();

    public MainWindow()
    {
        InitializeComponent();

        Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {

        _data["field1"] = 100;
        _data["field2"] = "Pete Brown";
        _data["field3"] = new DateTime(2010, 03, 08);

        this.DataContext = _data;
        
    }
}

Note that the fields are all declared as object. If you have a homogeneous set, you can more strongly type. One limitation of a dictionary approach is that all the field values must be the same type.

Xaml UI

The binding is as I described above. For grins, I threw in a string formatter to show it's possible.

<Window x:Class="WpfDictionaryBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="HorizontalAlignment"
                    Value="Right" />
            <Setter Property="VerticalAlignment"
                    Value="Center" />
            <Setter Property="Margin"
                    Value="0 0 10 0" />
        </Style>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="HorizontalAlignment"
                    Value="Stretch" />
            <Setter Property="VerticalAlignment"
                    Value="Stretch" />
            <Setter Property="Margin"
                    Value="4" />
        </Style>
    </Window.Resources>
    
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBlock Text="Field 1"
                   Grid.Row="0"
                   Grid.Column="0" />
        
        <TextBlock Text="Field 2"
                   Grid.Row="1"
                   Grid.Column="0" />

        <TextBlock Text="Field 3"
                   Grid.Row="2"
                   Grid.Column="0" />


        <TextBox Text="{Binding [field1], Mode=TwoWay}"
                 Grid.Row="0"
                 Grid.Column="1" />
        <TextBox Text="{Binding [field2], Mode=TwoWay}"
                 Grid.Row="1"
                 Grid.Column="1" />
        <TextBox Text="{Binding [field3], Mode=TwoWay, StringFormat='D'}"
                 Grid.Row="2"
                 Grid.Column="1" />


        <StackPanel Grid.Row="3"
                    Grid.Column="1">
            <TextBlock Text="Raw Values" />
            <TextBlock Text="{Binding [field1]}" />
            <TextBlock Text="{Binding [field2]}" />
            <TextBlock Text="{Binding [field3]}" />
        </StackPanel>
        
    </Grid>
</Window>

When run, it looks like this:

image

That's it. However, you may have noticed that the "raw values" section never updates when you change values. Read on.

Change Notification

Of course, the problem comes about when you change the values in the dictionary: the UI doesn't change. That's because the built-in Dictionary class is not observable; it does't raise any change events to notify the binding system that the values have changed.

The community has come up with a few implementations of an ObservableDictionary. This link is to one of the good ones, by Dr. WPF. Here is another ObservableDictionary on codeplex. Unfortunately, the first works only for ItemsControl type scenarios (not field changing) and the latter is not really geared towards WPF.

However, with one simple change, the one from Dr. WPF will work in our scenario (WPF only, you'd need to spin off a version for Silverlight). Open up the source code for the ObservableCollection, and find public TValue this[TKey key] and change it like this:

// old version
public TValue this[TKey key]
{
    get { return (TValue)_keyedEntryCollection[key].Value; }
    set { DoSetEntry(key, value);}
}

// new version
public TValue this[TKey key]
{
    get { return (TValue)_keyedEntryCollection[key].Value; }
    set 
    { 
        DoSetEntry(key, value); 
        OnPropertyChanged(Binding.IndexerName); 
    }
}

The change is simply to fire the PropertyChanged event for the item. Note that I haven't tested the dictionary class to find out if there are ways around this or edge cases, so there certainly are likely to be some. That said, this will solve the main issue.

By simply using the ObservableDictionary with this modification, rather than the regular Dictionary, your notification problems will be solved. Please note, however, that there's a slight performance penalty to pay for ObservableDictionary, so you'll want to be smart about how you use them in your own applications.

I hope that helps you out in your apps.

Updated 2010-03-10: Replaced "Item[]" with the Binding.IndexerName constant.

       
posted by Pete Brown on Monday, March 8, 2010
filed under:        

23 comments for “Binding to a Dictionary in WPF and Silverlight”

  1. Tor Langlosays:
    Hi Pete, where do I find the ultimate documentation all these binding syntaxes?
    It would be nice to have it all documented once and for all.
    Or alternatively which .net source files, class/method names, etc should we look at (I've tried to find it before, but without luck).
  2. Dr. WPFsays:
    Thanks for the heads up on this, Pete!

    You uncovered a regression that was caused by a recent attempt to optimize change notifications. The logic in FirePropertyChangedNotifications() was only raising the change for "Item[]" when the count of dictionary entries changed. Clearly, this won't work for setting a dictionary value if the key already exists because there is no change in Count. :S

    To fix this, I added a line to DoSetEntry() that will decrement the _countCache member for this particular case, which will, in turn, make sure that the "Item[]" change is raised.

    Note that I intentionally do not use the Binding.IndexerName property in my class, as this adds a framework dependency. I prefer that VM classes do not have dependencies on any of the System.Windows.* namespaces.

    Thanks again for catching this! The updated class should work much better. :D

    Cheers,
    -dw
  3. Israel Pereirasays:
    Hi Pete, Thanks for this post!

    I'm trying to implement something that I don't know if is possible.
    Let's assume I have a combobox with all the keys in my Dictionary with its SelectedValue property binded to property in my VM. In addition to this I have a label, in which I want to show the VALUE of the selected key in the combobox. How should I approach this through binding in XAML? What im trying to do is explained by the next code line (but it obviously doesn't work):

    <Label Content="{Binding [{Binding SelectedKey}], UpdateSourceTrigger=PropertyChanged}"/>

    If I change this: [{Binding SelectedKey}] for something like [1], it will display the Value with the Key = 1. But I don't know how to do it for a Dynamic string I guess.

    Thanks a lot for your help!
  4. Petesays:
    @Israel

    I would actually expose that info as properties off the viewmodel. When SelectedKey changes, change a SelectedValue (or more meaninfully named) property in the VM. Then bind to that from the UI.

    Pete
  5. Israel Pereirasays:
    @Pete

    Thanks a lot, that is exactly the way I managed to do it just after I posted my question. I wonder if there is a way to do it the way I was trying to before though.

    Israel
  6. Arnoud van Berssays:
    I think binding to indexers is broken in SL 4 RC

    See my post..
    http://forums.silverlight.net/forums/t/169250.aspx

    Can somebody confirm this behavior?

  7. Shanesays:
    How would you go about binding a Dictionary to a combobox in a scenario where you don't know how many fields you have. I want to see only the value and not the key from the Dictionary. The below binding works but you see the key value pair how do I make it show only the value?

    <ComboBox ItemsSource="{Binding ValueCollection}" SelectedIndex="{Binding Value, Mode=TwoWay}" >

    </ComboBox>

    Thanks,

    Shane
  8. Karamvirsays:
    Hi Pete,

    I'm kinda newbie to wPF. Trying to implement "Observable Dictiobary"

    As suggested by you i've made the changes in Dr WPF version of "Observable Dictionary" for getting the change notifications.

    changes made are:

    // old version

    public TValue this[TKey key]
    {

    get { return (TValue)_keyedEntryCollection[key].Value; }
    set { DoSetEntry(key, value);}

    }



    // new version

    public TValue this[TKey key]
    {

    get { return (TValue)_keyedEntryCollection[key].Value; }

    set

    {
    DoSetEntry(key, value);

    OnPropertyChanged(Binding.IndexerName);

    }

    }

    But i'm getting the following error -

    Error:The name 'Binding' does not exist in the current context

    Would appreciate if you could suggest something.

    Regards
    Karamvir
  9. Karamvirsays:
    Pete,

    Thanks alot for the quick reply.

    Maybe i'm doing something wrong elsewhere in the code.

    Could you please check out the following thread.
    I've posted detailed description on the microsoft forum.

    http://social.msdn.microsoft.com/Forums/en/wpf/thread/9f562a00-5d11-4802-b869-27c143195e25

    Would appreciate your effort.

    Regards
    Karmvir

  10. Bigsbysays:
    Hi Peter & the gang,

    I was playing around with Expando's in Silverlight and bindings and, eventually got here. It looks like the Silverlight forgot something about the binding. The thing is that Item[] is no longer needed for the Binding when setting Path but it is needed for PropertyChanged notifications. This is where I got:

    public class Expando : INotifyPropertyChanged
    {
    private Dictionary<string, object> properties = new Dictionary<string, object>();

    public object this[string property]
    {
    get
    {
    if (!properties.ContainsKey(property)) return null;
    return properties[property];
    }
    set
    {
    if (!properties.ContainsKey(property))
    properties.Add(property, value);
    else
    properties[property] = value;
    RaiseExpandoPropertyChanged(property);
    }
    }

    private void RaiseExpandoPropertyChanged(string propertyName)
    {
    if (null != PropertyChanged)
    PropertyChanged(this, new PropertyChangedEventArgs(string.Format("Item[{0}]", propertyName)));
    }
    public event PropertyChangedEventHandler PropertyChanged;
    }

    This makes these bindings work and update as expected, i.e., when TextBoxes loose focus, TextBlocks.Text are updated:

    var expando = new Expando();
    expando["Name"] = "Some name";
    expando["Age"] = 23;
    DataContext = expando;

    <TextBox Text="{Binding [Name], Mode=TwoWay}"/>
    <TextBox Text="{Binding [Age], Mode=TwoWay}"/>
    <TextBlock Text="{Binding [Name]}"/>
    <TextBlock Text="{Binding [Age]}"/>
  11. mawryasays:
    Is it possible to bind the key to something, e.g.

    <TextBox Text="{Binding Fields[field1], Mode=TwoWay}" />

    might become something like

    <TextBox Text="{Binding Fields[{Binding Element=myElement, Path=myPath], Mode=TwoWay}" />

    Is that sort of thing even possible?
  12. Dharmasays:
    Check out latest build that is aliavable on Customer Only web site, it should get your issue resolved Make sure you always send the bug reports to support since blog comments are really not reliable way to get that tracked.
  13. Davesays:
    What are your thoughts on an ObservableDictionary to do what the following simple class would do (although not generic):

    public class DictionaryEntry : INotifyPropertyChanged
    {
    string key;
    object value;

    public string Key { get { this.key; } set { this.key = value; } }
    public object Value
    {
    get { return this.value; }
    set
    {
    this.value = value;
    OnPropertyChanged("Value");
    }
    }

    // Implement IPropertyChanged
    }

    You can then have an ObservableCollection<DictionaryEntry>.

Comment on this Post

Remember me