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

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

【问题讨论】:

    标签: wpf validation datagrid wpfdatagrid idataerrorinfo


    【解决方案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

    1. 在代码中

      YourGrid.RowValidationErrorTemplate = new ControlTemplate();
      
    2. 在 Xaml 中

      <DataGrid.RowValidationErrorTemplate>
          <ControlTemplate>
          </ControlTemplate>
      </DataGrid.RowValidationErrorTemplate>`
      
    3. 然后制作自己的行验证错误模板。

      如果您的数据项是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);
      }
      
    4. 按照自己喜欢的方式编写自己的 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】:

                      我的场景是这样的:

                      1. 模型实现IDataErrorInfo
                      2. 基于 WPF DataGrid Practical Examples -Validation with IDataErrorInfo 的自定义行验证规则,它使用 IDataErrorInfo 结合了模型中的所有错误。

                        <DataGrid.RowValidationRules>
                            <local:RowDataInfoValidationRule ValidationStep="UpdatedValue" />
                        </DataGrid.RowValidationRules>
                        
                      3. 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;
                        }       
                        

                        【讨论】:

                          猜你喜欢
                          • 2015-10-04
                          • 1970-01-01
                          • 2011-03-10
                          • 2011-06-29
                          • 1970-01-01
                          • 2014-08-08
                          • 2012-11-23
                          • 2019-03-01
                          • 2014-06-19
                          相关资源
                          最近更新 更多