所以我有一个 WPF DataGrid
,它绑定到一个ObservableCollection
. 该集合通过 对其成员进行验证IDataErrorInfo
。如果我以一种无效的方式编辑一个单元格,然后在按 Enter 之前将其移开,然后返回并使其有效,则该单元格将停止显示无效,但是,“!” 行首仍将存在,并且ToolTip
将引用先前的无效值。
15 回答
不使用Mode=TwoWay
forDataGridTextColumns
解决了问题的一个版本,但似乎这个问题也可能由于其他原因突然出现。
(任何对为什么不使用有很好的解释Mode=TwoWay
的人首先解决了这个问题可能接近这个问题的解决方案)
同样的事情也发生在我身上,DataGridComboBoxColumn
所以我试图更深入地挖掘。
问题不在于显示inside的Binding
in 。它正在将其绑定到祖先(正如它应该做的那样)并且该部分正在工作。Control
ErrorTemplate
DataGridHeaderBorder
Visibility
Validation.HasError
DataGridRow
Visibility="{Binding (Validation.HasError),
Converter={StaticResource bool2VisibilityConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"/>
问题是验证错误DataGridRow
一旦解决就没有被清除。在我的问题版本中,DataGridRow
开始时出现 0 个错误。当我输入一个无效值时,它得到了 1 个错误,到目前为止一切都很好。但是当我解决了这个错误时,它跳到了 3 个错误,所有这些都是一样的。
在这里,我尝试使用将 ifDataTrigger
设置ValidationErrorTemplate
为{x:Null}
ifValidation.Errors.Count
不是 1 来解决它。它在第一次迭代时效果很好,但是一旦我第二次清除错误,它又回来了。它不再有 3 个错误,而是有 7 个!经过几次迭代后,它超过了 10。
我还尝试通过在但没有骰子UpdateSource
上手动清除错误。也没有任何效果。并且查看工具包中的源代码并没有让我到任何地方:)UpdateTarget
BindingExpressions
Validation.ClearInvalid
所以我对此没有任何好的解决方案,但我认为无论如何我应该发布我的发现..
到目前为止,我唯一的“解决方法”就是ErrorTemplate
将DataGridRowHeader
<DataGrid ...>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
</Style>
</DataGrid.RowStyle>
<!-- ... -->
</DataGrid>
我找到了这个问题的根本原因。它与如何BindingExpressionBase
s 失去对 the 的引用有关BindingGroup
,因为只有 theBindingExpression
负责删除它的ValidationErrors
.
在这种 DataGrid 验证的情况下,它有多个可能会丢失引用的来源:
- 明确地,当为 a 重建可视化树时,在其属性更改为新值之前,属于该单元格的
DataGridCell
所有DataGridCell.BuildVisualTree()
旧BindingExpressions
的都将被删除BindingGroup
Content
- 明确地,当 的
Content
属性DataGridCell
被更改(通过DataGridCell.BuildVisualTree()
或其他方式)时,BindingExpressionBase.Detach()
会为旧属性值上的所有绑定调用该方法,这也会BindingGroup
在任何ValidationError
有机会被删除之前删除对 的引用 - 隐含地,因为大多数对和 from
BindingExpressionBase
的引用实际上都是WeakReference
s,即使上述所有情况都不会导致引用被删除,但是当某些东西查找TargetElement
of 时BindingExpressionBase
,有可能底层WeakReference
返回null
并且属性访问器再次调用破碎的Detach()
方法
有了上述发现,现在也很清楚为什么不使用Mode=TwoWay
forDataGridTextColumn
有时可以解决问题。将DataGridTextColumn
变为只读的,因此永远不会改变的Content
属性。DataGridCell
我已经为此使用附件编写了一个解决方法DependencyProperty
。
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
namespace Utilities
{
public static class DataGridExtension
{
/// <summary>
/// Identifies the FixBindingGroupValidationErrorsFor attached property.
/// </summary>
public static readonly DependencyProperty FixBindingGroupValidationErrorsForProperty =
DependencyProperty.RegisterAttached("FixBindingGroupValidationErrorsFor", typeof(DependencyObject), typeof(DataGridExtension),
new PropertyMetadata(null, new PropertyChangedCallback(OnFixBindingGroupValidationErrorsForChanged)));
/// <summary>
/// Gets the value of the FixBindingGroupValidationErrorsFor property
/// </summary>
public static DependencyObject GetFixBindingGroupValidationErrorsFor(DependencyObject obj)
{
return (DependencyObject)obj.GetValue(FixBindingGroupValidationErrorsForProperty);
}
/// <summary>
/// Sets the value of the FixBindingGroupValidationErrorsFor property
/// </summary>
public static void SetFixBindingGroupValidationErrorsFor(DependencyObject obj, DependencyObject value)
{
obj.SetValue(FixBindingGroupValidationErrorsForProperty, value);
}
/// <summary>
/// Handles property changed event for the FixBindingGroupValidationErrorsFor property.
/// </summary>
private static void OnFixBindingGroupValidationErrorsForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DependencyObject oldobj = (DependencyObject)e.OldValue;
if (oldobj != null)
{
BindingGroup group = FindBindingGroup(d); //if d!=DataGridCell, use (DependencyObject)e.NewValue
var leftOverErrors = group.ValidationErrors != null ?
Validation.GetErrors(group.Owner).Except(group.ValidationErrors).ToArray() : Validation.GetErrors(group.Owner).ToArray();
foreach (var error in leftOverErrors)
{
//HINT: BindingExpressionBase.Detach() removes the reference to BindingGroup, before ValidationErrors are removed.
if (error.BindingInError is BindingExpressionBase binding && (binding.Target == null ||
TreeHelper.IsDescendantOf(binding.Target, oldobj)) && binding.BindingGroup == null &&
(binding.ValidationErrors == null || binding.ValidationErrors.Count == 0 || !binding.ValidationErrors.Contains(error)))
{
typeof(Validation).GetMethod("RemoveValidationError", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] {error, group.Owner, group.NotifyOnValidationError});
}
}
}
}
private static BindingGroup FindBindingGroup(DependencyObject obj)
{
do
{
if (obj is FrameworkElement fe)
{
return fe.BindingGroup;
}
if (obj is FrameworkContentElement fce)
{
return fce.BindingGroup;
}
obj = LogicalTreeHelper.GetParent(obj);
} while (obj != null);
return null;
}
private static class TreeHelper
{
private static DependencyObject GetParent(DependencyObject element, bool recurseIntoPopup)
{
if (recurseIntoPopup)
{
// Case 126732 : To correctly detect parent of a popup we must do that exception case
Popup popup = element as Popup;
if ((popup != null) && (popup.PlacementTarget != null))
return popup.PlacementTarget;
}
Visual visual = element as Visual;
DependencyObject parent = (visual == null) ? null : VisualTreeHelper.GetParent(visual);
if (parent == null)
{
// No Visual parent. Check in the logical tree.
parent = LogicalTreeHelper.GetParent(element);
if (parent == null)
{
FrameworkElement fe = element as FrameworkElement;
if (fe != null)
{
parent = fe.TemplatedParent;
}
else
{
FrameworkContentElement fce = element as FrameworkContentElement;
if (fce != null)
{
parent = fce.TemplatedParent;
}
}
}
}
return parent;
}
public static bool IsDescendantOf(DependencyObject element, DependencyObject parent)
{
return TreeHelper.IsDescendantOf(element, parent, true);
}
public static bool IsDescendantOf(DependencyObject element, DependencyObject parent, bool recurseIntoPopup)
{
while (element != null)
{
if (element == parent)
return true;
element = TreeHelper.GetParent(element, recurseIntoPopup);
}
return false;
}
}
}
}
然后将此属性通过绑定附加到 的Content
属性DataGridCell
。
<Window ...
xmlns:utils="clr-namespace:Utilities">
...
<DataGrid ...>
<DataGrid.CellStyle>
<Style BasedOn="{StaticResource {x:Type DataGridCell}}" TargetType="{x:Type DataGridCell}">
<Setter Property="utils:DataGridExtension.FixBindingGroupValidationErrorsFor" Value="{Binding Content, RelativeSource={RelativeSource Self}}" />
</Style>
</DataGrid.CellStyle>
</DataGrid>
...
</Window>
我找到了对我有用的最佳答案。只需清除您DataGrid
的RowValidationErrorTemplate
.
在代码中
YourGrid.RowValidationErrorTemplate = new ControlTemplate();
在 Xaml 中
<DataGrid.RowValidationErrorTemplate> <ControlTemplate> </ControlTemplate> </DataGrid.RowValidationErrorTemplate>`
然后制作自己的行验证错误模板。
如果您的数据项是INotifyPropertyChanged
((INotifyPropertyChanged)i).PropertyChanged += this.i_PropertyChanged;`
然后
private void i_PropertyChanged(object sender, PropertyChangedEventArgs e) { this.Dispatcher.BeginInvoke(new Action(() => { var row = this.ItemContainerGenerator.ContainerFromItem(sender) as DataGridRow; if (row == null) return; var Errs = IsValid(row); if (Errs.Count == 0) row.Header = null; else { // Creatr error template var gg = new Grid { ToolTip = "Error Tooltip" }; var els = new Ellipse { Fill = new SolidColorBrush(Colors.Red), Width = row.FontSize, Height = row.FontSize }; var tb = new TextBlock { Text = "!", Foreground = new SolidColorBrush(Colors.White), HorizontalAlignment = HorizontalAlignment.Center, FontWeight = FontWeights.Bold }; gg.Children.Add(els); gg.Children.Add(tb); row.Header = gg; } }), System.Windows.Threading.DispatcherPriority.ApplicationIdle); }
写你自己的 IsValid 方法,用你喜欢的方式
RowHeader 错误模板不会消失,我也有同样的问题。我正在使用 INotifyDataErrorInfo。跟进 Fredrik Hedblad 的研究,我做了一个解决方法;我已修改 DataGridRowHeader 模板以使用 MultiBinding 来实现 ValidationErrorTemplate 可见性:
<Style x:Key="DataGridRowHeaderStyle" TargetType="{x:Type DataGridRowHeader}">
<!--<Setter Property="Background" Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=Brushes:BrushesLibrary1,
ResourceId=HeaderBrush}}"/>-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRowHeader}">
<Grid>
<Microsoft_Windows_Themes:DataGridHeaderBorder BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"
IsPressed="{TemplateBinding IsPressed}" IsHovered="{TemplateBinding IsMouseOver}"
IsSelected="{TemplateBinding IsRowSelected}" Orientation="Horizontal"
Padding="{TemplateBinding Padding}" SeparatorBrush="{TemplateBinding SeparatorBrush}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}">
<StackPanel Orientation="Horizontal">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"
Width="15"/>
<Control SnapsToDevicePixels="false"
Template="{Binding ValidationErrorTemplate, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}">
<Control.Visibility>
<MultiBinding Converter="{StaticResource ValidationConverter}">
<Binding Path="(Validation.HasError)" RelativeSource="{RelativeSource AncestorType={x:Type DataGridRow}}"/>
<Binding Path="DataContext.HasErrors" RelativeSource="{RelativeSource AncestorType={x:Type DataGridRow}}"/>
</MultiBinding>
</Control.Visibility>
<!-- Original binding below -->
<!--Visibility="{Binding (Validation.HasError), Converter={StaticResource bool2VisibilityConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}">-->
</Control>
</StackPanel>
</Microsoft_Windows_Themes:DataGridHeaderBorder>
<Thumb x:Name="PART_TopHeaderGripper" Style="{StaticResource RowHeaderGripperStyle}" VerticalAlignment="Top"/>
<Thumb x:Name="PART_BottomHeaderGripper" Style="{StaticResource RowHeaderGripperStyle}" VerticalAlignment="Bottom"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
这依赖于具有“HasErrors”属性和更改通知的绑定对象。在我的项目中,我通过在 EndEdit 事件中为 HasErrors 提升 PropertyChanged 来确保更新 HasErrors 属性。
我的解决方案是实现自定义行验证反馈,类似于自定义行验证反馈部分下的此页面。然后行错误会适当地消失。
(我还添加了定义,以避免第一次出现感叹号时表格向右移动。)RowHeaderWidth="20"
DataGrid
尝试 从每个 Binding 元素中删除Mode=TwoWay
for each 。DataGridTextColumns
如果您看到越来越多的类似于 Meleak 的错误,我很想知道您的错误集合是如何填充的。在问题的 Meleaks 版本中,他在解决无效数据后看到了三个错误(以及更多错误)。
在我的数据验证代码中,我删除了特定错误的先前实例,然后在每次数据更改时重新添加。作为参考,这里是一个示例:
验证管道
#Region " Validation workers "
Private m_validationErrors As New Dictionary(Of String, String)
Private Sub AddError(ByVal ColName As String, ByVal Msg As String)
If Not m_validationErrors.ContainsKey(ColName) Then
m_validationErrors.Add(ColName, Msg)
End If
End Sub
Private Sub RemoveError(ByVal ColName As String)
If m_validationErrors.ContainsKey(ColName) Then
m_validationErrors.Remove(ColName)
End If
End Sub
Public ReadOnly Property [Error]() As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
If m_validationErrors.Count > 0 Then
Return "Shipment data is invalid"
Else
Return Nothing
End If
End Get
End Property
Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
If m_validationErrors.ContainsKey(columnName) Then
Return m_validationErrors(columnName).ToString
Else
Return Nothing
End If
End Get
End Property
#End Region
正在验证的属性
Private Sub OnZIPChanged()
Me.RemoveError("ZIP")
If _ZIP Is Nothing OrElse _ZIP.Trim = "" Then
Me.AddError("ZIP", "Please enter a ZIP Code")
Else
Select Case _ZIP.Length
Case 5
Case 10
Case Else
Me.AddError("ZIP", "Please enter a ZIP Code")
End Select
End If
OnPropertyChanged("CanShip")
End Sub
因此,当属性 Changed 处理程序运行时,如果 ValidationErrors 字典中存在错误,则将其删除,然后检查值,如果不符合要求,则将错误添加到字典中。这有助于确保该实体验证错误字典中仅存在任何错误的一个实例。
我的解决方法是不使用 Validation.Errors,而是使用 DataGridRow.Item 属性。如果您的 DataGrid 绑定到实现 IDataErrorInfo 接口的业务对象,那么您可以添加 IsNotValid 属性(或 IsValid),并确保 Error 属性返回与该对象关联的所有错误。然后自定义 DataGridRowHeader 的默认样式:
<Style x:Key="{x:Type DataGridRowHeader}" TargetType="{x:Type DataGridRowHeader}">
...
<Control SnapsToDevicePixels="false"
Visibility="{Binding RelativeSource={RelativeSource
AncestorType={x:Type DataGridRow}},
Path=Item.IsNotValid, Converter={StaticResource
Bool2VisibilityConverter}}"
Template="{Binding RelativeSource={RelativeSource
AncestorType={x:Type DataGridRow}},
Path=ValidationErrorTemplate}" />
...
</Style>
同样在 DataGridRow 样式中自定义 ValidationErrorTemplate,以便它显示来自 DataGridRow.Item.Error 属性的错误消息。
最初的问题来自 2011 年,Datagrids Validation-System 仍然存在问题,无法使用。我花了 2 天时间试图找到一个解决方案来完成以下工作:
- 我的模型项实现 INotifyDataErrorInfo 和 INotifyPropertyChanged
- 它们位于绑定到 DataGrid 的 BindingList 中
- DataGrid 应该显示来自用户输入的验证错误以及来自不同来源的模型更改
- RowValidation-Error-mark 应该显示任何单元格是否有验证错误,否则隐藏,无论用户当前是否正在编辑、提交或对该行不做任何事情
- 无效单元格应显示带有错误文本的工具提示
- 没有错误或故障
实现此行为的唯一方法是放弃 RowValidation 和 CellValidation,而使用 RowHeader 和 Styles。我从网络上的各种来源复制了以下代码。我还不能对此进行广泛的测试,但乍一看它看起来很有希望。
在 DataGrids XAML 中:
<DataGrid ... local:DataGridProps.ShowCellErrorBorder="False">
<DataGrid.Resources>
<local:DataGridValidationConverter x:Key="DataGridValidationConverter" />
<Style TargetType="TextBlock" x:Key="errTemplate">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding Path=(Validation.Errors)[0].ErrorContent}
RelativeSource={x:Static RelativeSource.Self}, "/>
<Setter Property="Background" Value="LightSalmon"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<Grid Margin="0,-2,0,-2"
Visibility="{Binding Path=DataContext.HasErrors,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}},
Converter={StaticResource DataGridValidationConverter},
FallbackValue=Hidden}">
<Ellipse StrokeThickness="0" Fill="Red"
Width="{Binding Path=FontSize,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}}"
Height="{Binding Path=FontSize,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}}" />
<TextBlock Text="!" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"
FontSize="{Binding Path=FontSize,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}}}" />
</Grid>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="Vorname" ElementStyle="{StaticResource errTemplate}"
Binding="{Binding Path=Vorname,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}" />
...
</DataGrid.Columns>
</DataGrid>
数据网格验证转换器:
public class DataGridValidationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
数据网格道具:
public class DataGridProps
{
public static readonly DependencyProperty ShowCellErrorBorderProperty = DependencyProperty.RegisterAttached(
"ShowCellErrorBorder", typeof(bool), typeof(DataGridProps), new PropertyMetadata(true, ShowCellErrorBorderPropertyChangedCallback));
public static bool GetShowCellErrorBorder(DependencyObject element)
{
return (bool)element.GetValue(ShowCellErrorBorderProperty);
}
public static void SetShowCellErrorBorder(DependencyObject element, bool value)
{
element.SetValue(ShowCellErrorBorderProperty, value);
}
private static void ShowCellErrorBorderPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (GetShowCellErrorBorder(dependencyObject)) return;
var dg = dependencyObject as DataGrid;
if (null != dg)
{
dg.Loaded += (sender, args) =>
{
var scrollView = dg.Template.FindName("DG_ScrollViewer", dg) as ScrollViewer;
if (null == scrollView) return;
var scrollContent = scrollView.Template.FindName("PART_ScrollContentPresenter", scrollView) as ScrollContentPresenter;
if (null == scrollContent) return;
scrollContent.AdornerLayer.Visibility = Visibility.Hidden;
};
}
}
}
模型的实现:
public Model()
{
if (Vorname == null)
Vorname = "";
...
errorsByPropertyName = new Dictionary<string, List<string>>();
this.PropertyChanged += Model_PropertyChanged;
ForceRevalidation(null);
}
private string _vorname;
public string Vorname { get => _vorname; set => SetField(ref _vorname, value); }
...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private Dictionary<string, List<string>> errorsByPropertyName;
public void ForceRevalidation(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
foreach (PropertyInfo property in GetType().GetProperties())
{
ValidateProperty(property.Name);
}
}
else
{
ValidateProperty(propertyName);
}
}
protected virtual void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
OnPropertyChanged(nameof(HasErrors));
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => errorsByPropertyName.Any();
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (propertyName == null)
propertyName = "";
return errorsByPropertyName.ContainsKey(propertyName) ? errorsByPropertyName[propertyName] : null;
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
ValidateProperty(e.PropertyName);
}
protected virtual void ValidateProperty(string propertyName)
{
if (propertyName == null)
propertyName = "";
ClearErrors(propertyName);
switch (propertyName)
{
case nameof(Vorname):
if (string.IsNullOrWhiteSpace(Vorname))
AddError(propertyName, propertyName + " is empty");
break;
...
default:
break;
}
}
protected void AddError(string propertyName, string error)
{
if (!errorsByPropertyName.ContainsKey(propertyName))
errorsByPropertyName[propertyName] = new List<string>();
if (!errorsByPropertyName[propertyName].Contains(error))
{
errorsByPropertyName[propertyName].Add(error);
OnErrorsChanged(propertyName);
}
}
protected void ClearErrors(string propertyName)
{
if (errorsByPropertyName.ContainsKey(propertyName))
{
errorsByPropertyName.Remove(propertyName);
OnErrorsChanged(propertyName);
}
}
我从我更大的 Model-Base-Class 中创建了这个最小的例子,希望我在这里得到了这方面的所有重要信息。
我的场景是这样的:
- 模型实现
IDataErrorInfo
基于WPF DataGrid Practical Examples -Validation with IDataErrorInfo的自定义行验证规则,它使用 IDataErrorInfo 结合了模型中的所有错误。
<DataGrid.RowValidationRules> <local:RowDataInfoValidationRule ValidationStep="UpdatedValue" /> </DataGrid.RowValidationRules>
ValidatesOnDataErrors=True
,ValidatesOnExceptions=True
,NotifyOnValidationError=True
在绑定中(我开始使用)
这导致对我的验证引擎的多次访问,并最终使我DataGrid
处于不一致的状态(即使行有效,行标题上的错误通知)。
解决方案是从绑定中删除开关(第 3 点。)
我也建议通读Clearing a DataGrid 行验证错误。
我的解决方法是从每个数据网格列中的绑定声明中简单地删除属性UpdateSourceTrigger="LostFocus"。
就我而言,当我们使用 DataGrid WPF3.5 版本时,它运行良好。我们升级到 4.0,然后它停止重置。在搜索 SO、google 等之后,我偶然发现了我的解决方案。在 DataGridTextColumn 中的绑定上设置UpdateSourceTrigger=PropertyChanged为我修复了它。
我刚刚意识到红色感叹号在将其设置为正确值时并不清楚。
就我而言,我必须从绑定定义中删除
UpdateSourceTrigger=PropertyChanged
对我来说,它适用于这两个定义:
<DataGridTextColumn
Header="Time, min"
x:Name="uiDataGridTextColumnTime"
Width="Auto"
CellStyle="{StaticResource ResourceKey=DataGridCellText}"
IsReadOnly="False">
<DataGridTextColumn.Binding>
<Binding Path="fTime" StringFormat="{}{0:0.00}">
<Binding.ValidationRules>
<Validation:CellDataInfoValidationRule ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
和
<DataGridTextColumn
Header="Time, min"
x:Name="uiDataGridTextColumnTime"
Width="Auto"
CellStyle="{StaticResource ResourceKey=DataGridCellText}"
Binding="{Binding fTime, StringFormat={}\{0:0.00\}, ValidatesOnDataErrors=True}"
IsReadOnly="False">
验证:CellDataInfoValidationRule 是自定义类并在此处获取
public class CellDataInfoValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// obtain the bound business object
BindingExpression expression = value as BindingExpression;
IDataErrorInfo info = expression.DataItem as IDataErrorInfo;
// determine the binding path
string boundProperty = expression.ParentBinding.Path.Path;
// obtain any errors relating to this bound property
string error = info[boundProperty];
if (!string.IsNullOrEmpty(error))
{
return new ValidationResult(false, error);
}
return ValidationResult.ValidResult;
}
}
并且您的数据对象必须实现 IDataErrorInfo
我没有使用IDataErrorInfo
orINotifyDataErrorInfo
我的解决方案是将绑定从UpdateSourceTrigger="PropertyChanged"
更改UpdateSourceTrigger="LostFocus"
为
如果您在 DataGrid 列定义中使用 ValidationRules,并且您需要在属性更改(在 UI 或属性中)时运行验证规则,请查看ValidatesOnTargetUpdated="True"
您的设置ValidationRule
XAML 示例:
<DataGridTextColumn Header="Name"
CellStyle="{StaticResource DGCellStyle}"
ElementStyle="{StaticResource DGTextColValidationStyle}"
EditingElementStyle="{StaticResource DGTextColEditValidationStyle}">
<DataGridTextColumn.Binding>
<Binding Path="Name" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<ValidationResource:YourValidationRule ValidationStep="UpdatedValue" ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
我使用了这种技术,它取消了 RowValidationRules,而是在视图模型中使用属性验证。这需要静态变量和数据注释:
//uses Prism.MVVM for BindableBase and INotifyDataErrorInfo
private static int _xxStartNo;
private static int _xxEndNo;
// in property getter/setter
private int _startNo;
[CustomValidation(typeof(YourModel), "ValidateStartNoRange")]
public int StartNo
{
get
{
_xxStartNo=_startNo;
return _startNo;
}
set
{
..........
ValidateProperty("StartNo")
}
}
.......
public static ValidationResult ValidateStartNoRange(int number)
{
if(number > _xxEndNo)
{
return ValidationResult("Start No must be less than End No.";
}
return ValidationResult.Success;
}