为什么 DataGrid 在编辑时用旧的无效值替换绑定 属性?
Why is the DataGrid replacing a bound property with an old, invalid value when edited?
我有一个简单的 DataGrid 绑定到视图模型的 ObservableCollection。一列是 DataGridTemplateColumn,其 CellTemplate 是一个 TextBlock,而 CellEditingTemplate 是一个 TextBox(我知道我可以使用 DataGridTextColumn,但我想明确说明要使用哪些控件)。 TextBox 配置为验证错误。
<DataGrid
ItemsSource="{Binding People}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
每个项目的视图模型 (PersonViewModel) 只定义一个 FirstName 属性,并实现 IDataErrorInfo 以进行验证。我正在使用 MVVM Light Toolkit 进行 属性 通知。
public class PersonViewModel : ViewModelBase, IDataErrorInfo
{
private string firstName;
public string FirstName
{
get => firstName;
set => Set(nameof(FirstName), ref firstName, value);
}
public string this[string columnName]
{
get
{
if (columnName == nameof(FirstName))
{
if (string.IsNullOrEmpty(FirstName))
return "First name cannot be empty";
}
return null;
}
}
public string Error => null;
}
此外,我还有一个按钮,单击该按钮会将每个人的名字设置为“你好”。
public ICommand HelloCommand =>
new RelayCommand(() =>
{
foreach (var person in People)
person.FirstName = "Hello";
});
问题是:当我输入一个无效的 FirstName(即,我将其设置为空字符串),然后单击 Hello 按钮时,它正确地将 FirstName 替换为“Hello”。但是如果我再次尝试编辑它,FirstName 会立即被空字符串替换。
作为测试,我将字符串“a”设为错误。执行与上述相同的步骤,将 TextBox 中的“Hello”字符串替换为“a”。就好像 TextBox 不知道 FirstName 已更改为“Hello”,即使 TextBlock 正确显示它并且它们都绑定到相同的 属性.
有谁知道发生了什么或解决这个问题的方法吗?我预期的行为是 TextBox 包含绑定 属性 更改为的值(无论该 TextBox 是否存在验证错误)。
注意:我正在使用 .NET 4.0,因为我必须这样做。
显然,这种行为的原因是在 HelloCommand 执行时,发起验证错误的 TextBox
还不存在。因此,验证错误的清除无法按常规方式进行。
很难说为什么在新建TextBox
和恢复绑定的时候没有从ViewModel
中取新值。更有趣的是这个错误值的来源。有人可能会想,如果我们从 ViewModel
中获取 FirstName 属性 的新值,并在 DataContext_Changed 事件处理程序中将其设置为 TextBox.Text
属性 TextBox
,那么它应该解决问题,因为此时 TextBox
和 ViewModel
都有一个新的(有效的)值,而且没有空间可以取错一个从。但神奇的是它仍然来自某个地方:)
幸运的是,有一个技巧可以帮助解决这个问题。这个想法是在执行 HelloCommand 之前取消所有挂起的绑定操作。为什么它应该起作用并不是很明显。毕竟此时导致错误的 BindingExpression
不存在。尽管如此,它仍然有效。
给DataGrid
起名字:
<DataGrid x:Name="myGrid" ItemsSource="{Binding People}" AutoGenerateColumns="False">
向按钮添加点击处理程序:
<Button Content="Hello" Command="{Binding HelloCommand}" Click="Hello_Click"/>
使用该代码
private void Hello_Click(object sender, RoutedEventArgs e)
{
foreach (var bg in BindingOperations.GetSourceUpdatingBindingGroups(myGrid))
bg.CancelEdit();
}
UPD
由于GetSourceUpdatingBindingGroups
在.NET 4.0中不可用,您可以尝试这样:
private void Hello_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < myGrid.Items.Count; i++)
{
DataGridRow row = (DataGridRow)myGrid.ItemContainerGenerator.ContainerFromIndex(i);
if (row != null && Validation.GetHasError(row))
{
row.BindingGroup?.CancelEdit();
}
}
}
它不是那么优雅,但确实是一样的。
我有一个简单的 DataGrid 绑定到视图模型的 ObservableCollection。一列是 DataGridTemplateColumn,其 CellTemplate 是一个 TextBlock,而 CellEditingTemplate 是一个 TextBox(我知道我可以使用 DataGridTextColumn,但我想明确说明要使用哪些控件)。 TextBox 配置为验证错误。
<DataGrid
ItemsSource="{Binding People}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
每个项目的视图模型 (PersonViewModel) 只定义一个 FirstName 属性,并实现 IDataErrorInfo 以进行验证。我正在使用 MVVM Light Toolkit 进行 属性 通知。
public class PersonViewModel : ViewModelBase, IDataErrorInfo
{
private string firstName;
public string FirstName
{
get => firstName;
set => Set(nameof(FirstName), ref firstName, value);
}
public string this[string columnName]
{
get
{
if (columnName == nameof(FirstName))
{
if (string.IsNullOrEmpty(FirstName))
return "First name cannot be empty";
}
return null;
}
}
public string Error => null;
}
此外,我还有一个按钮,单击该按钮会将每个人的名字设置为“你好”。
public ICommand HelloCommand =>
new RelayCommand(() =>
{
foreach (var person in People)
person.FirstName = "Hello";
});
问题是:当我输入一个无效的 FirstName(即,我将其设置为空字符串),然后单击 Hello 按钮时,它正确地将 FirstName 替换为“Hello”。但是如果我再次尝试编辑它,FirstName 会立即被空字符串替换。
作为测试,我将字符串“a”设为错误。执行与上述相同的步骤,将 TextBox 中的“Hello”字符串替换为“a”。就好像 TextBox 不知道 FirstName 已更改为“Hello”,即使 TextBlock 正确显示它并且它们都绑定到相同的 属性.
有谁知道发生了什么或解决这个问题的方法吗?我预期的行为是 TextBox 包含绑定 属性 更改为的值(无论该 TextBox 是否存在验证错误)。
注意:我正在使用 .NET 4.0,因为我必须这样做。
显然,这种行为的原因是在 HelloCommand 执行时,发起验证错误的 TextBox
还不存在。因此,验证错误的清除无法按常规方式进行。
很难说为什么在新建TextBox
和恢复绑定的时候没有从ViewModel
中取新值。更有趣的是这个错误值的来源。有人可能会想,如果我们从 ViewModel
中获取 FirstName 属性 的新值,并在 DataContext_Changed 事件处理程序中将其设置为 TextBox.Text
属性 TextBox
,那么它应该解决问题,因为此时 TextBox
和 ViewModel
都有一个新的(有效的)值,而且没有空间可以取错一个从。但神奇的是它仍然来自某个地方:)
幸运的是,有一个技巧可以帮助解决这个问题。这个想法是在执行 HelloCommand 之前取消所有挂起的绑定操作。为什么它应该起作用并不是很明显。毕竟此时导致错误的 BindingExpression
不存在。尽管如此,它仍然有效。
给DataGrid
起名字:
<DataGrid x:Name="myGrid" ItemsSource="{Binding People}" AutoGenerateColumns="False">
向按钮添加点击处理程序:
<Button Content="Hello" Command="{Binding HelloCommand}" Click="Hello_Click"/>
使用该代码
private void Hello_Click(object sender, RoutedEventArgs e)
{
foreach (var bg in BindingOperations.GetSourceUpdatingBindingGroups(myGrid))
bg.CancelEdit();
}
UPD
由于GetSourceUpdatingBindingGroups
在.NET 4.0中不可用,您可以尝试这样:
private void Hello_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < myGrid.Items.Count; i++)
{
DataGridRow row = (DataGridRow)myGrid.ItemContainerGenerator.ContainerFromIndex(i);
if (row != null && Validation.GetHasError(row))
{
row.BindingGroup?.CancelEdit();
}
}
}
它不是那么优雅,但确实是一样的。