I have created a DataGrid
with two columns that both use TextBox
s for editing the properties of a ViewModel. When both columns have validation errors, and the property values are changed from the ViewModel, entering edit mode in one of the cells retains the previously edited value.
Here is a short example:
<Window ...>
<Window.DataContext>
<ViewModels:MainPresenter />
</Window.DataContext>
<DockPanel>
<Button Command="{Binding ResetValuesCommand}"
Margin="5" DockPanel.Dock="Top">Reset Values</Button>
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTextColumn Header="Value 1"
Binding="{Binding Value1, ValidatesOnDataErrors=True}" />
<DataGridTextColumn Header="Value 2"
Binding="{Binding Value2, ValidatesOnDataErrors=True}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
public class MainPresenter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<ItemPresenter> Items { get; }
= new ObservableCollection<ItemPresenter> {new ItemPresenter()};
public ICommand ResetValuesCommand => new ResetCommand(Items);
private class ResetCommand : ICommand
{
private readonly IEnumerable<ItemPresenter> _items;
public ResetCommand(IEnumerable<ItemPresenter> items) { _items = items; }
public void Execute(object parameter) => _items.ToList().ForEach(i => i.Reset());
public bool CanExecute(object parameter) => true;
public event EventHandler CanExecuteChanged { add { } remove { } }
}
}
public class ItemPresenter : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string Value1 { get; set; } = "A";
public string Value2 { get; set; } = "B";
public string this[string columnName] => "ERROR";
public string Error => "ERROR";
public void Reset()
{
Value1 = "A";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
Value2 = "B";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
}
}
When running the app, both columns are highlighted as being invalid.
The cell in 'Value 1' column changes to edit mode and the value is shown as "Z" again.
One thing to note: this only occurs when other columns have validation errors. If this is the only column with a validation error, then the TextBox in edit mode will correctly show "A" when entering edit mode.
Oddly, explicitly setting the Mode in the bindings to TwoWay
(presumably the default, as that's the apparent behaviour) fixes the problem.
However, if I want some custom cell templates (and replace the DataGridTextColumn
s with DataGridTemplateColumns
s) but still use a TextBox
for editing:
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Value 1">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value1, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value1, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value 2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value2, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value2, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I encounter the same problem but explicitly setting the binding modes to TwoWay
does not fix it.
Am I doing something wrong somewhere, that I've overlooked? Alternatively, does anyone have a workaround for this?
I've found a workaround.
If I change to using INotifyDataErrorInfo
(only available in .NET 4.5 and above), then it works as expected.
public class ItemPresenter : INotifyPropertyChanged, INotifyDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public string Value1 { get; set; } = "A";
public string Value2 { get; set; } = "B";
public IEnumerable GetErrors(string propertyName) => new[] { "ERROR" };
public bool HasErrors => true;
public void Reset()
{
Value1 = "A";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value1)));
Value2 = "B";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value2)));
}
}
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Value 1">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value1}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value1}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value 2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value2}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value2}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>