[ACCEPTED]-Two Way Data Binding With a Dictionary in WPF-dictionary

Accepted answer
Score: 20

I don't think you're going to be able to 7 do what you'd like with a dictionary.

  1. Because the Dictionary doesn't implement INotifyPropertyChanged or INotifyCollectionChanged
  2. Because it's not going to allow two way binding like you want.

I'm 6 not sure if it fits your requirements exactly, but 5 I would use an ObservableCollection, and a custom class.

I'm 4 using a DataTemplate, to show how to make 3 the binding on the values two way.

Of course 2 in this implementation Key isn't used, so 1 you could just use an ObservableCollection<int>

Custom Class

public class MyCustomClass
{
    public string Key { get; set; }
    public int Value { get; set; }
}

Set ItemsSource

ObservableCollection<MyCustomClass> dict = new ObservableCollection<MyCustomClass>();
dict.Add(new MyCustomClass{Key = "test", Value = 1});
dict.Add(new MyCustomClass{ Key = "test2", Value = 2 });
listView.ItemsSource = dict;

XAML

<Window.Resources>
    <DataTemplate x:Key="ValueTemplate">
        <TextBox Text="{Binding Value}" />
    </DataTemplate>
</Window.Resources>
<ListView Name="listView">
    <ListView.View>
        <GridView>
            <GridViewColumn CellTemplate="{StaticResource ValueTemplate}"/>
        </GridView>
    </ListView.View>
</ListView>
Score: 6

This is a little hacky, but I got it to 24 work (assuming that I understand what you 23 want).

I first created a view model class:

class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        this.data.Add(1, "One");
        this.data.Add(2, "Two");
        this.data.Add(3, "Three");
    }

    Dictionary<int, string> data = new Dictionary<int, string>();
    public IDictionary<int, string> Data
    {
        get { return this.data; }
    }

    private KeyValuePair<int, string>? selectedKey = null;
    public KeyValuePair<int, string>? SelectedKey
    {
        get { return this.selectedKey; }
        set
        {
            this.selectedKey = value;
            this.OnPropertyChanged("SelectedKey");
            this.OnPropertyChanged("SelectedValue");
        }
    }

    public string SelectedValue
    {
        get
        {
            if(null == this.SelectedKey)
            {
                return string.Empty;
            }

            return this.data[this.SelectedKey.Value.Key];
        }
        set
        {
            this.data[this.SelectedKey.Value.Key] = value;
            this.OnPropertyChanged("SelectedValue");
        }
    }

    public event PropertyChangedEventHandler  PropertyChanged;
    private void OnPropertyChanged(string propName)
    {
        var eh = this.PropertyChanged;
        if(null != eh)
        {
            eh(this, new PropertyChangedEventArgs(propName));
        }
    }
}

And 22 then in the XAML:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox x:Name="ItemsListBox" Grid.Row="0"
            ItemsSource="{Binding Path=Data}"
            DisplayMemberPath="Key"
            SelectedItem="{Binding Path=SelectedKey}">
        </ListBox>
        <TextBox Grid.Row="1"
            Text="{Binding Path=SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</Window>

The value of the Data property 21 is bound to the ItemsSource of the ListBox. As you state in 20 the question, this results in using instances 19 of KeyValuePair<int, string> as the data behind the ListBox. I set the DisplayMemberPath to 18 Key so that the value of the key will be used 17 as the displayed value for each item in 16 the ListBox.

As you found, you can't just use the 15 Value of the KeyValuePair as the data for the TextBox, since that 14 is read only. Instead, the TextBox is bound to 13 a property on the view model which can get 12 and set the value for the currently selected 11 key (which is updated by binding the SelectedItem property 10 of the ListBox to another property on the view 9 model). I had to make this property nullable 8 (since KeyValuePair is a struct), so that the code could 7 detect when there is no selection.

In my 6 test app, this seems to result in edits 5 to the TextBox propagating to the Dictionary in the view 4 model.

Is that more or less what you are 3 going for? It seems like it should be a 2 bit cleaner, but I'm not sure if there is 1 any way to do so.

Score: 1

Just posting what I derived from the above. You 4 can place the below in a ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>>

Made the Key read-only 3 as they key probably shouldn't be changed. This 2 needs Prism to work - or you can implement 1 your own version of INotifyPropertyChanged instead

public class ObservableKvp<K, V> : BindableBase
{
    public K Key { get; }

    private V _value;
    public V Value
    {
        get { return _value; }
        set { SetProperty(ref _value, value); }
    }

    public ObservableKvp(K key, V value)
    {
        Key = key;
        Value = value;
    }
}
Score: 0

Instead of

Dictionary<string, int>

, can you have a

Dictionary<string, IntWrapperClass>?

Have IntWrapperClass 3 implement INotifyPropertyCHanged. Then you 2 can have a Listview/Listbox two-way bind 1 to items in the dictionary.

Score: 0

I managed to do that in my project using 4 an observable dictionary implemented by 3 dr.wpf here and using as Value not a String 2 but an object[]. In xaml i got something 1 like:

<ListView ItemsSource="{Binding myObservableDictionary}" >
                            <ListView.View>
                                <GridView>
                                    <GridViewColumn DisplayMemberBinding="{Binding Key}"/>
                                    <GridViewColumn>
                                        <GridViewColumn.CellTemplate>
                                            <DataTemplate>
                                                <TextBox Text="{Binding Value[0], Mode=TwoWay}" AllowDrop="True" Drop="TextBox_Drop"></TextBox>
                                            </DataTemplate>
                                        </GridViewColumn.CellTemplate>
                                    </GridViewColumn>
                                </GridView>
                            </ListView.View>
                        </ListView> 

More Related questions