I\'d like to bind a Dictionary
to a ListView
in WPF. I\'d like to do this in such a way that the Values
in the
I managed to do that in my project using an observable dictionary implemented by dr.wpf here and using as Value not a String but an object[]. In xaml i got something 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>
This is a little hacky, but I got it to work (assuming that I understand what you 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 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 is bound to the ItemsSource
of the ListBox
. As you state in the question, this results in using instances of KeyValuePair<int, string>
as the data behind the ListBox
. I set the DisplayMemberPath
to Key
so that the value of the key will be used as the displayed value for each item in the ListBox
.
As you found, you can't just use the Value
of the KeyValuePair
as the data for the TextBox
, since that is read only. Instead, the TextBox
is bound to a property on the view model which can get and set the value for the currently selected key (which is updated by binding the SelectedItem
property of the ListBox
to another property on the view model). I had to make this property nullable (since KeyValuePair
is a struct), so that the code could detect when there is no selection.
In my test app, this seems to result in edits to the TextBox
propagating to the Dictionary
in the view model.
Is that more or less what you are going for? It seems like it should be a bit cleaner, but I'm not sure if there is any way to do so.
I don't think you're going to be able to do what you'd like with a dictionary.
I'm not sure if it fits your requirements exactly, but I would use an ObservableCollection
, and a custom class.
I'm using a DataTemplate, to show how to make the binding on the values two way.
Of course in this implementation Key isn't used, so 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>
Instead of
Dictionary<string, int>
, can you have a
Dictionary<string, IntWrapperClass>?
Have IntWrapperClass implement INotifyPropertyCHanged. Then you can have a Listview/Listbox two-way bind to items in the dictionary.
Just posting what I derived from the above. You can place the below in a ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>>
Made the Key read-only as they key probably shouldn't be changed. This needs Prism to work - or you can implement 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;
}
}