Why isn't a property in my ViewModel updated w

2020-05-08 01:44发布

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; }
    }
}

1条回答
Anthone
2楼-- · 2020-05-08 02:11

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.

查看更多
登录 后发表回答