I'm trying to create a UserControl, that will let me edit a Dictionary of type Dictionary<string,string>
in a grid (just editing entries so far, not adding or deleting).
Whenever I bind the DataGrid to a Dictionary it shows the grid as read only, so I decieded to create a value converter, that would convert it to an ObservableCollection<DictionaryEntry>
where DictionaryEntry
is just a class with two properties Key
, and Value
.
This works for display the dictionary in the grid, but now when I make changes to the grid, my dictionary is not being updated. I'm unsure why.
I think it's either a problem with the way I have my bindings set up, or my value converter. If anyone could shed some light, that would be fantastic.
Below is the smallest demo I could make that shows what I'm doing. Again the problem is when I change values in the grid, the MyDictionary
on my MainViewModel
is not updated..ever. Why?
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
_myDictionary = new Dictionary<string, string>()
{
{"Key1", "Value1"},
{"Key2", "Value2"},
{"Key3", "Value3"}
};
}
private Dictionary<string, string> _myDictionary;
public Dictionary<string, string> MyDictionary
{
get
{
return _myDictionary;
}
set
{
if (_myDictionary == value)
return;
_myDictionary = value;
OnPropertyChanged("MyDictionary");
}
}
...
}
MainWindow.xaml
<Window ...>
<Window.Resources>
<local:MainViewModel x:Key="MainViewModel"></local:MainViewModel>
</Window.Resources>
<StackPanel Name="MainStackPanel" DataContext="{Binding Source={StaticResource MainViewModel}}">
<local:DictionaryGrid />
<Button Content="Print Dictionary" Click="PrintDictionary"></Button>
</StackPanel>
</Window>
DictionaryGrid.xaml
<UserControl ...>
<UserControl.Resources>
<testingGrid:DictionaryToOcConverter x:Key="Converter" />
</UserControl.Resources>
<Grid>
<DataGrid ItemsSource="{Binding MyDictionary,
Converter={StaticResource Converter}}"
/>
</Grid>
</UserControl>
DictionaryToOcConverter.cs
public class DictionaryToOcConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = new ObservableCollection<DictionaryEntry>();
var dictionary = value as Dictionary<string, string>;
if (dictionary != null)
{
foreach (var kvp in dictionary)
collection.Add(new DictionaryEntry { Key = kvp.Key, Value = kvp.Value });
}
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var dictionary = new Dictionary<string, string>();
var entries = value as ObservableCollection<DictionaryEntry>;
if (entries != null)
{
foreach (var entry in entries)
dictionary.Add(entry.Key, entry.Value);
}
return dictionary;
}
public class DictionaryEntry
{
public string Key { get; set; }
public string Value { get; set; }
}
}
There's actually two issues here: your DictionaryEntry
class should implement INotifyPropertyChanged to work correctly with the binding engine, and secondarily it should implement IEditableObject because you want to edit items in a data grid and avoid 'random results'. So your class should look something like this...
public class DictionaryEntry : INotifyPropertyChanged, IEditableObject
{
private string _k;
[Description("The key")]
public string K
{
[DebuggerStepThrough]
get { return _k; }
[DebuggerStepThrough]
set
{
if (value != _k)
{
_k = value;
OnPropertyChanged("K");
}
}
}
private string _v;
[Description("The value")]
public string V
{
[DebuggerStepThrough]
get { return _v; }
[DebuggerStepThrough]
set
{
if (value != _v)
{
_v = value;
OnPropertyChanged("V");
}
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
#region IEditableObject
public void BeginEdit()
{
// implementation goes here
}
public void CancelEdit()
{
// implementation goes here
}
public void EndEdit()
{
// implementation goes here
}
#endregion
}
In your ViewModel (or code behind) you would instantiate it like this...
public ObservableCollection<DictionaryEntry> MyItems { get; set; }
public ViewModel()
{
MyItems = new ObservableCollection<DictionaryEntry>();
MyItems.Add(new DictionaryEntry{K="string1", V="value1"});
MyItems.Add(new DictionaryEntry { K = "color", V = "red" });
}
...which is pretty close to what you have. And the Xaml would look like this...
<DataGrid ItemsSource="{Binding MyItems}" AutoGenerateColumns="True">
</DataGrid>
Those things will bring about the behaviour you are after. I.e., edits will be sticky.
On the IEditableObject
interface vis-à-vis DataGrids, it's a known 'gotcha' and there's a description of it here... http://blogs.msdn.com/b/vinsibal/archive/2009/04/07/5-random-gotchas-with-the-wpf-datagrid.aspx
which says...
If you are not familiar with IEditableObject, see this MSDN article which has a good explanation and code sample. The DataGrid has baked in functionality for transactional editing via the IEditableObject interface. When you begin editing a cell, the DataGrid gets into cell editing mode as well as row editing mode. What this means is that you can cancel/commit cells as well as cancel/commit rows. For example, I edit cell 0 and press tab to the next cell. Cell 0 is committed when pressing tab. I start typing in cell 1 and realize I want to cancel the operation. I press ‘Esc’ which reverts cell 1. I now realize I want to cancel the whole operation so I press ‘Esc’ again and now cell 0 is reverted back to its original value.
来源:https://stackoverflow.com/questions/17908663/why-isnt-a-property-in-my-viewmodel-updated-when-datagrid-changes