I'd like to bind a Dictionary<string, int> to a ListView in WPF. I'd like to do this in such a way that the Values in the Dictionary get updated via the data binding mechanism. I don't want to change the Keys just the Values. I also don't care about adding new mappings to the Dictionary. I just want to update existing ones.
Setting the Dictionary as the ItemsSource of the ListView doesn't accomplish this. It doesn't work because the ListView uses the Enumerator to access the contents of the Dictionary and the elements of that enumeration are immutable KeyValuePair objects.
My current line of investigation attempts to use the Keys property. I assign this to the ItemsSource property of my ListView. This does allow me to display the Keys but I don't know enough about WPF's databinding mechanism to access the Values in the Dictionary.
I found this question: Access codebehind variable in XAML but still can't seem to bridge the gap.
Do any of you know how to make this approach work? Does anyone have a better approach?
It seems like, as a last resort, I could build a custom object and stick it in a List from which I recreate/update my Dictionary but this seems like a way to circumvent the built-in data binding functionality rather than effectively utilize it.
-
I don't think you're going to be able to do what you'd like with a dictionary.
- Because the Dictionary doesn't implement INotifyPropertyChanged or INotifyCollectionChanged
- Because it's not going to allow two way binding like you want.
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>Waylon Flinn : I'm actually less worried about propagating changes from the Dictionary into the ListView than I am about propagating changes from the ListView into the Dictionary. The INotify interfaces seem like they're concerned mostly with the former.Ray : Yes, that's correct. However I tried to a Dictionary binding correctly and couldn't get it to work, as you couldn't. Will see if anyone else has any suggestions. -
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
Dataproperty is bound to theItemsSourceof theListBox. As you state in the question, this results in using instances ofKeyValuePair<int, string>as the data behind theListBox. I set theDisplayMemberPathtoKeyso that the value of the key will be used as the displayed value for each item in theListBox.As you found, you can't just use the
Valueof theKeyValuePairas the data for theTextBox, since that is read only. Instead, theTextBoxis 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 theSelectedItemproperty of theListBoxto another property on the view model). I had to make this property nullable (sinceKeyValuePairis 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
TextBoxpropagating to theDictionaryin 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.
-
Dr WPF has a great article that details how to build an ObservableDictionary. (full source download too).
0 comments:
Post a Comment