当存在多个验证错误时,WPF DataGrid TextBoxes 保留以前的值
WPF DataGrid TextBoxes retaining previous values when there are multiple validation errors
我创建了一个包含两列的 DataGrid
,它们都使用 TextBox
来编辑 ViewModel 的属性。当两列都有验证错误,并且 属性 值从 ViewModel 更改时,在其中一个单元格中进入编辑模式会保留之前编辑的值。
这是一个简短的例子:
查看
<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)));
}
}
重现步骤
当 运行 应用程序时,两列都突出显示为无效。
- 双击 'Value 1' 列中的单元格(当前值为 "A")并更改它,例如到 "Z";
- 按回车键(或等效键)提交更改;
- 按 'Reset Values' 按钮(使我们刚刚编辑的单元格变为 "A");
- 再次双击 'Value 1' 列中的单元格。
'Value 1' 列中的单元格更改为编辑模式,值再次显示为 "Z"。
需要注意一件事:只有当其他列有验证错误时才会发生这种情况。如果这是唯一存在验证错误的列,则编辑模式下的文本框将在进入编辑模式时正确显示 "A"。
部分修复
奇怪的是,将绑定中的模式显式设置为 TwoWay
(大概是默认值,因为这是明显的行为)可以解决问题。
但是,如果我想要一些自定义单元格模板(并将 DataGridTextColumn
s 替换为 DataGridTemplateColumns
s)但仍使用 TextBox
进行编辑:
<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>
我遇到了同样的问题,但将绑定模式明确设置为 TwoWay
并没有解决问题。
我是不是哪里做错了,被我忽略了?或者,有人对此有解决方法吗?
我找到了解决方法。
如果我改为使用 INotifyDataErrorInfo
(仅在 .NET 4.5 及更高版本中可用),那么它会按预期工作。
视图模型
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>
我创建了一个包含两列的 DataGrid
,它们都使用 TextBox
来编辑 ViewModel 的属性。当两列都有验证错误,并且 属性 值从 ViewModel 更改时,在其中一个单元格中进入编辑模式会保留之前编辑的值。
这是一个简短的例子:
查看
<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)));
}
}
重现步骤
当 运行 应用程序时,两列都突出显示为无效。
- 双击 'Value 1' 列中的单元格(当前值为 "A")并更改它,例如到 "Z";
- 按回车键(或等效键)提交更改;
- 按 'Reset Values' 按钮(使我们刚刚编辑的单元格变为 "A");
- 再次双击 'Value 1' 列中的单元格。
'Value 1' 列中的单元格更改为编辑模式,值再次显示为 "Z"。
需要注意一件事:只有当其他列有验证错误时才会发生这种情况。如果这是唯一存在验证错误的列,则编辑模式下的文本框将在进入编辑模式时正确显示 "A"。
部分修复
奇怪的是,将绑定中的模式显式设置为 TwoWay
(大概是默认值,因为这是明显的行为)可以解决问题。
但是,如果我想要一些自定义单元格模板(并将 DataGridTextColumn
s 替换为 DataGridTemplateColumns
s)但仍使用 TextBox
进行编辑:
<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>
我遇到了同样的问题,但将绑定模式明确设置为 TwoWay
并没有解决问题。
我是不是哪里做错了,被我忽略了?或者,有人对此有解决方法吗?
我找到了解决方法。
如果我改为使用 INotifyDataErrorInfo
(仅在 .NET 4.5 及更高版本中可用),那么它会按预期工作。
视图模型
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>