WPF DataGrid 验证错误未清除

Posted

技术标签:

【中文标题】WPF DataGrid 验证错误未清除【英文标题】:WPF DataGrid validation errors not clearing 【发布时间】:2011-07-03 05:05:07 【问题描述】:

所以我有一个 WPF DataGrid,它绑定到 ObservableCollection。该集合通过IDataErrorInfo 对其成员进行了验证。如果我以一种无效的方式编辑一个单元格,然后在按 Enter 之前将其移开,然后返回并使其有效,则该单元格将停止显示无效,但是,“!”行首仍然存在,ToolTip 将引用之前的无效值。

【问题讨论】:

【参考方案1】:

不使用Mode=TwoWay 代替DataGridTextColumns 解决了一个版本的问题,但似乎这个问题也可能由于其他原因突然出现。

(任何对为什么不使用 Mode=TwoWay 有很好的解释的人都可能首先解决这个问题)

DataGridComboBoxColumn 也发生了同样的事情,所以我试图深入挖掘。

问题不在于Control 中的BindingDataGridHeaderBorder 中显示ErrorTemplate。它正在将其Visibility 绑定到Validation.HasError 的祖先DataGridRow(正如它应该做的那样)并且该部分正在工作。

Visibility="Binding (Validation.HasError),
                     Converter=StaticResource bool2VisibilityConverter,
                     RelativeSource=RelativeSource AncestorType=x:Type DataGridRow"/>

问题是验证错误在解决后不会从DataGridRow 中清除。在我的问题版本中,DataGridRow 开始时出现 0 个错误。当我输入一个无效值时,它得到了 1 个错误,到目前为止一切都很好。但是当我解决这个错误时,它跳到了 3 个错误,所有这些都是一样的。

在这里我尝试使用 DataTrigger 解决它它回来了。它不再有 3 个错误,而是有 7 个!经过几次迭代后,它超过了 10。

我还尝试通过在 BindingExpressions 上执行 UpdateSourceUpdateTarget 手动清除错误,但没有骰子。 Validation.ClearInvalid 也没有任何效果。并且查看工具包中的源代码并没有让我有任何收获:)

所以我对此没有任何好的解决方案,但我想我还是应该发布我的发现..

到目前为止,我唯一的“解决方法”是将ErrorTemplate 隐藏在DataGridRowHeader

<DataGrid ...>
    <DataGrid.RowStyle>
        <Style TargetType="DataGridRow">
            <Setter Property="ValidationErrorTemplate" Value="x:Null"/>
        </Style>
    </DataGrid.RowStyle>
    <!-- ... -->
</DataGrid>

【讨论】:

如果我不使用Mode=TwoWay,则文本单元格不再可编辑,DataGridTextColumn.EditingElementStyle 将永远不会应用。 根本没有帮助。 (我正在尝试将 ValidationErrorTemplate 隐藏在外部文件(Style.xaml)中 我只在我的 DataGrid 中使用DataGridTextColumns,您的第一个解决方案非常适合我!我也没有像@bitbonk 提到的任何编辑问题。所以谢谢你,即使我仍然不明白为什么会这样:D【参考方案2】:

我找到了这个问题的根本原因。这与BindingExpressionBases 如何失去对BindingGroup 的引用有关,因为只有BindingExpression 负责删除其ValidationErrors

在这种 DataGrid 验证的情况下,它有多个可能会丢失引用的来源:

明确地,当DataGridCell.BuildVisualTree()DataGridCell 重建可视化树时,属于此单元格的BindingGroup 的所有旧BindingExpressions 都将被删除,然后其Content 属性更改为新价值 明确地,当DataGridCellContent 属性发生更改(通过DataGridCell.BuildVisualTree() 或其他方式)时,将为旧属性值上的所有绑定调用BindingExpressionBase.Detach() 方法,这也删除了引用在任何ValidationError 有机会被删除之前,转到BindingGroup 隐含地,因为大多数对BindingExpressionBase 的引用实际上都是WeakReferences,即使上述所有情况都不会导致引用被删除,但是当查找TargetElementBindingExpressionBase 时,有可能底层的WeakReference 返回null 并且属性访问器再次调用损坏的Detach() 方法

根据上述发现,现在也很清楚为什么使用Mode=TwoWay 代替DataGridTextColumn 有时可以解决问题。 DataGridTextColumn 将变为只读,因此 DataGridCellContent 属性永远不会更改。

为此,我使用附加的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;
            
        
    

然后将此属性通过绑定附加到DataGridCellContent 属性。

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

【讨论】:

不知道如何将它集成到我的项目中也许有人可以解释一下吗? 必须使用什么“使用”?使用 System.Reflection;?林克? 这似乎对我有用。我有一个具有验证的DataGridComboboxColumn,它未在选择有效值时清除错误。添加此代码已解决此问题。 更正 - 这仅适用于 DataGridComboBoxColumn 当其属性绑定是唯一具有验证的属性时。如果我包含一个属性绑定到同一对象上另一个属性的属性绑定的 DataGridTextColumn,则会出现相同错误消息的多个实例。 我创建了这个解决方法来解决另一个answer 中描述的问题的根本原因,它实际上适用于每个DataGridColumn 类型和每个绑定场景。如果它对您不起作用,那么它可能是由其他原因引起的。如果它适用于绑定对象的一个​​属性而不适用于另一个属性,您应该检查您的验证逻辑,它实际上会创建重复的ValidationErrors。我看过一些IDataErrorInfo 的在线示例,它们使用缓存来处理错误,然后忘记重置缓存。【参考方案3】:

我找到了最适合我的答案。只需清除您的DataGridRowValidationErrorTemplate

    在代码中

    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 方法

【讨论】:

【参考方案4】:

我有同样的问题,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 属性。

【讨论】:

【参考方案5】:

我的解决方案是实现自定义行验证反馈,类似于 自定义行验证反馈部分下的 this page。然后行错误会适当地消失。 (我还在DataGrid定义中添加了RowHeaderWidth="20",以避免第一次出现感叹号时表格向右移动。)

【讨论】:

【参考方案6】:

尝试从每个 Binding 元素中删除每个 DataGridTextColumnsMode=TwoWay

【讨论】:

那么我应该如何通过 DataGrid 行和单元格进行编辑?【参考方案7】:

如果您看到越来越多的类似于 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 字典中存在错误,则将其删除,然后检查值,如果不符合要求,则将错误添加到字典中。这有助于确保该实体验证错误字典中仅存在任何错误的一个实例。

【讨论】:

集合中的所有错误完全相同,一旦DataGridCell 的内容出现验证错误,它们就会在内部由DataGrid 填充到DataGridRow。不幸的是,你不能对这个集合做任何事情,只能看看它。我的版本中的错误来自 viewmodels IDataErrorInfo 实现。要深入了解问题所在,您可能需要访问源代码(而不是工具包中的源代码,因为它们在这方面的工作方式不同)。反射器可能会起作用.. 嗯,是的,我之前没有在 ViewModel 上实现 IDataErrorInfo。我对我的数据对象执行此操作,以便它们可以报告自己的验证状态(从长远来看,这并不重要)。那么 DataGrid 填充了这些错误,它们是从 DataGridCell 传递给它的吗?我没有听说过这个(我绝不是专家)。这是普通的 .NET DataGrid 吗?【参考方案8】:

我的解决方法是不使用 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 proeprty 的错误消息。

【讨论】:

【参考方案9】:

最初的问题是 2011 年的,Datagrids 验证系统仍然存在问题,无法使用。我花了 2 天时间试图找到一个解决方案来完成以下工作:

我的模型项实现了 INotifyDataErrorInfo 和 INotifyPropertyChanged 它们位于绑定到 DataGrid 的 BindingList 中 DataGrid 应该显示来自用户输入的验证错误以及来自不同来源的模型更改 RowValidation-Error-mark 应该显示任何单元格是否存在验证错误,否则隐藏,无论用户当前是在编辑、提交还是对行不执行任何操作 无效单元格应显示带有错误文本的工具提示 没有错误或故障

实现此行为的唯一方法是放弃 RowValidation 和 CellValidation,而使用 RowHeader 和样式。我从网络上的各种来源复制了以下代码。我还不能对此进行广泛的测试,但乍一看它看起来很有希望。

在 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>

DataGridValidationConverter:

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();
    

DataGridProps:

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 中创建了这个最小的例子,希望我在这里得到了这方面的所有重要信息。

【讨论】:

【参考方案10】:

我的解决方法是从每个 datagridcolumn 的绑定声明中简单地删除属性 UpdateSourceTrigger="LostFocus"

【讨论】:

【参考方案11】:

就我而言,当我们使用 DataGrid WPF3.5 版本时,它运行良好。我们升级到 4.0,然后它停止重置。在搜索 SO、google 等之后,我偶然发现了我的解决方案。 在 DataGridTextColumn 中的 Binding 上设置 UpdateSourceTrigger=PropertyChanged 为我修复了它。

我刚刚意识到红色感叹号在设置为正确值时并不清楚。

【讨论】:

【参考方案12】:

在我的情况下,我必须从绑定定义中删除

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">

Validation: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

【讨论】:

【参考方案13】:

我没有使用IDataErrorInfoINotifyDataErrorInfo,我的解决方案是将绑定从UpdateSourceTrigger="PropertyChanged" 更改为UpdateSourceTrigger="LostFocus" 这是唯一的

如果您在 DataGrid 列定义中使用 ValidationRules,并且需要在属性更改(在 UI 或属性中)时运行验证规则,请查看在您的 ValidationRule 上设置 ValidatesOnTargetUpdated="True"

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>

【讨论】:

【参考方案14】:

我的场景是这样的:

    模型实现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 row validation error。

【讨论】:

【参考方案15】:

我使用了这种技术,它取消了 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;
       

【讨论】:

以上是关于WPF DataGrid 验证错误未清除的主要内容,如果未能解决你的问题,请参考以下文章

wpf datagrid怎么得到第一项焦点?然后在方向键上下移动

WPF Datagrid行上下文菜单-禁用菜单项[关闭]

wpf datagrid 文本验证工具提示

wpf中DataGrid如何清空缓存

在WPF DataGrid中未检测到CTRL + C.

WPF DataGrid 绑定到 XML 未更新