WPF DataGrid 验证错误未清除

发布于 2024-10-18 15:13:52 字数 243 浏览 7 评论 0原文

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

So I have a WPF DataGrid, which is bound to an ObservableCollection. The collection has validation on its members, through IDataErrorInfo. If I edit a cell in a way so as to be invalid, and then tab away from it before hitting enter, then come back and make it valid, the cell will stop showing invalid, however, the "!" at the head of the row will still be there, and the ToolTip will reference the previous, invalid value.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

帅气尐潴 2024-10-25 15:13:52

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

(任何能够很好地解释为什么不使用 Mode=TwoWay 解决这个问题的人都可能接近解决这个问题)

同样的事情刚刚发生给我一个 DataGridComboBoxColumn 所以我尝试更深入地挖掘。

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

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

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

在这里,我尝试使用 DataTrigger 来解决该问题,如果 Validation.Errors.Count<,则将 ValidationErrorTemplate 设置为 {x:Null} /code> 不是 1。第一次迭代时效果很好,但当我第二次清除错误后,它又回来了。错误不再是 3 个,而是 7 个!经过几次迭代后,它超过了 10。

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

所以我对此没有任何好的解决方案,但我认为无论如何我应该发布我的发现..

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

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

Not using Mode=TwoWay for DataGridTextColumns solves one version of the problem, however it seems that this problem can appear out of nowhere for other reasons as well.

(Anyone who has a good explanation as of why not using Mode=TwoWay solves this in the first place is probably close to a solution to this problem)

The same thing just happened to me with a DataGridComboBoxColumn so I tried to dig a little deeper.

The problem isn't the Binding in the Control that displays the ErrorTemplate inside DataGridHeaderBorder. It is binding its Visibility to Validation.HasError for the ancestor DataGridRow (exactly as it should be doing) and that part is working.

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

The problem is that the validation error isn't cleared from the DataGridRow once it is resolved. In my version of the problem, the DataGridRow started out with 0 errors. When I entered an invalid value it got 1 error so, so far so good. But when I resolved the error it jumped up to 3 errors, all of which were the same.

Here I tried to resolve it with a DataTrigger that set the ValidationErrorTemplate to {x:Null} if Validation.Errors.Count wasn't 1. It worked great for the first iteration but once I cleared the error for the second time it was back. It didn't have 3 errors anymore, it had 7! After a couple of more iterations it was above 10.

I also tried to clear the errors manually by doing UpdateSource and UpdateTarget on the BindingExpressions but no dice. Validation.ClearInvalid didn't have any effect either. And looking through the source code in the Toolkit didn't get me anywhere :)

So I don't have any good solutions to this but I thought I should post my findings anyway..

My only "workaround" so far is to just hide the ErrorTemplate in the DataGridRowHeader

<DataGrid ...>
    <DataGrid.RowStyle>
        <Style TargetType="DataGridRow">
            <Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
        </Style>
    </DataGrid.RowStyle>
    <!-- ... -->
</DataGrid>
我的鱼塘能养鲲 2024-10-25 15:13:52

我找到了这个问题的根本原因。它与 BindingExpressionBase 失去对 BindingGroup 的引用的方式有关,因为只有 BindingExpression 负责删除其 验证错误

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

  • 显式地,当通过 DataGridCell.BuildVisualTree()DataGridCell 重建可视化树时, 属于此单元格的 BindingGroup 的所有旧 BindingExpressions 都会被删除,
  • 在其 Content 属性显式更改为新值之前, 当DataGridCellContent 属性已更改(通过 DataGridCell.BuildVisualTree() 或其他方式),BindingExpressionBase.Detach()<为旧属性值上的所有绑定调用 /code> 方法,这还会在任何 ValidationError 有机会被
  • 隐式删除之前删除对 BindingGroup 的引用,因为大多数对 BindingExpressionBase 的引用实际上都是 WeakReference,即使上述所有情况都不会导致引用的删除,但当某些内容查找 BindingExpressionBase 的 TargetElement,底层 WeakReference 有可能返回 null 并且属性访问器再次调用损坏的 Detach() 方法

通过上述发现,现在也清楚了为什么DataGridTextColumn 使用 Mode=TwoWay 有时会成为一种错误。问题的解决方案。 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>

I found the root cause of this problem. It has to do with the way how BindingExpressionBases lose their reference to the BindingGroup, because only the BindingExpression is responsible to remove its ValidationErrors.

In this case of DataGrid validation, it has multiple sources where it can lose the reference:

  • explicitly, when the visual tree is rebuild for a DataGridCell by DataGridCell.BuildVisualTree(), all the old BindingExpressions of the BindingGroup that belongs to this cell are removed, before its Content property is changed to the new value
  • explicitly, when the Content property for the DataGridCell is changed (by DataGridCell.BuildVisualTree() or other way) , the BindingExpressionBase.Detach() method is called for all the bindings on the old property value, which also removes the reference to the BindingGroup before any ValidationError has a chance to be removed
  • implicitly, because mostly all references to and from BindingExpressionBase are actually WeakReferences, even when all the above scenarios would not cause the remove of the reference, but when something looks up the TargetElement of BindingExpressionBase, there is a chance that the underlying WeakReference returns null and the property accessor calls again the broken Detach() method

With the above findings it is now also clear why not using Mode=TwoWay for DataGridTextColumn can sometimes be a solution to the problem. The DataGridTextColumn would become read-only and the Content property of the DataGridCell is therefore never changed.

I've written a workaround by using an attached DependencyProperty for this.

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

Then attach this property with a binding to the Content property of 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>
¢蛋碎的人ぎ生 2024-10-25 15:13:52

我找到了对我有用的最佳答案。只需清除 DataGridRowValidationErrorTemplate 即可。

  1. 在代码中

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

    ;
        <控制模板>
        
    `
    
  3. 然后制作您自己的行验证错误模板。

    如果您的数据项是INotifyPropertyChanged

    ((INotifyPropertyChanged)i).PropertyChanged += this.i_PropertyChanged;`
    

    然后

    private void i_PropertyChanged(对象发送者,PropertyChangedEventArgs e)
    {
        this.Dispatcher.BeginInvoke(new Action(() =>;
        {
            var row = this.ItemContainerGenerator.ContainerFromItem(sender) as DataGridRow;
            如果(行==空)
                返回;
    
            var Errs = IsValid(行);
    
            if (Errs.Count == 0) row.Header = null;
            别的
            {
                // 创建错误模板
                var gg = new Grid { ToolTip = "错误工具提示" };
    
                var els = new Ellipse { Fill = new SolidColorBrush(Colors.Red), 宽度 = row.FontSize, 高度 = row.FontSize };
    
                var tb = 新的文本块
                {
                    文本=“!”,
                    前景 = new SolidColorBrush(Colors.White),
                    水平对齐 = 水平对齐.居中,
                    FontWeight = FontWeights.Bold
                };
    
                gg.Children.Add(els);
                gg.Children.Add(tb);
    
                行.标题= gg;
            }
        }),
         System.Windows.Threading.DispatcherPriority.ApplicationIdle);
    }
    
  4. 按照您喜欢的方式编写您自己的 IsValid 方法

I found best answer that worked for me. Just clear your DataGrid's RowValidationErrorTemplate.

  1. In Code

    YourGrid.RowValidationErrorTemplate = new ControlTemplate();
    
  2. In Xaml

    <DataGrid.RowValidationErrorTemplate>
        <ControlTemplate>
        </ControlTemplate>
    </DataGrid.RowValidationErrorTemplate>`
    
  3. Then make your own Row Validation Error Template.

    If your data item is INotifyPropertyChanged

    ((INotifyPropertyChanged)i).PropertyChanged += this.i_PropertyChanged;`
    

    then

    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. Write your own IsValid method, the way you like

甜宝宝 2024-10-25 15:13:52

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

I have the same problem with the RowHeader error template not going away. I am using INotifyDataErrorInfo. Following up on the research by Fredrik Hedblad I have made a workaround; I have modified the DataGridRowHeader template to use a MultiBinding for the ValidationErrorTemplate visibility:

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

This relies on the bound objects having a "HasErrors" property with change notification. In my project I have ensured that the HasErrors property is updated by raising the PropertyChanged for HasErrors in the item EndEdit event.

狼亦尘 2024-10-25 15:13:52

我的解决方案是实现自定义行验证反馈,类似于 此页面位于自定义行验证反馈部分。然后行错误就会相应消失。

(我还在 DataGrid 定义中添加了 RowHeaderWidth="20",以避免第一次出现感叹号时表格向右移动。)

My solution was to implement custom row validation feedback, similar to this page under the To customize row validation feedback section. The row error then disappears appropriately.

(I also added RowHeaderWidth="20" to the DataGrid definition, to avoid the table shift to the right the first time the exclamation point appears.)

生死何惧 2024-10-25 15:13:52

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

try removing the Mode=TwoWay for each of the DataGridTextColumns from each of the Binding elements.

笔芯 2024-10-25 15:13:52

如果您看到越来越多的类似于 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

因此,当运行属性更改处理程序时,如果 ValidationErrors 字典中存在错误,则将其删除,然后检查该值,如果不匹配,则将其删除要求,将错误添加到字典中。这有助于确保该实体验证错误字典中仅存在任何错误的一个实例。

If you are seeing an increasing number of errors similar to Meleak, I would be interested to know how your error collection gets populated. In Meleaks version of the problem, he sees three errors (and more) after resolving the invalid data.

In my Data Validation code, I remove the previous instance of a particular error then re-add every time the data changes. For reference, here is a sample:

The Validation Plumbing

#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

A Property Being Validated

    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

So, when the property Changed handler is run, if an error exists in the ValidationErrors dictionary, it is removed, then the value is checked, and it if does not match requirements, an error is added to the dictionary. This helps ensure that only one instance of any error is present in that entities validation error dictionary.

网名女生简单气质 2024-10-25 15:13:52

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

My workaround was not to use Validation.Errors, but use DataGridRow.Item property. If your DataGrid is bound to business objects which implement IDataErrorInfo interface, then you can add IsNotValid property (or IsValid), and make sure Error property returns all errors associated with the object. Then customize default style for 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>

Also in DataGridRow style customize ValidationErrorTemplate, so that it shows error message from DataGridRow.Item.Error proeprty.

与之呼应 2024-10-25 15:13:52

最初的问题是 2011 年的,Datagrids 验证系统仍然有很多错误,以至于无法使用。我花了 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>

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

我从更大的模型基类创建了这个最小的示例,并希望我在这里已经获得了这方面的所有重要内容。

The original Question is from 2011 and Datagrids Validation-System is still so buggy, that it is unusable. I spent 2 Days trying to find a solution to make the following work:

  • My Model-Items implement INotifyDataErrorInfo and INotifyPropertyChanged
  • They are in a BindingList bound to the DataGrid
  • The DataGrid should Display Validation-Errors from Userinputs as well as from changes to the Model from different sources
  • The RowValidation-Error-mark should show if any cell has validation errors and hide otherwise, no matter if the user is currently editing, commiting or doing nothing with the row
  • Invalid cells should display a tooltip with error text
  • No bugs or glitches

The only way to achieve this behaviour was to ditch RowValidation and CellValidation and instead use the RowHeader and Styles. I copied together the following code from various sources across the net. I haven't yet been able to do extensive testing of this, but it looks promising at first glances.

In 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;
        };
    }
}
}

Implementation of Model:

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

I created this minimal example from my bigger Model-Base-Class and hope I've got everything important for this aspect here.

浪荡不羁 2024-10-25 15:13:52

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

My workaround was to simply remove the property UpdateSourceTrigger="LostFocus" from the binding declaration in each datagridcolumn.

左耳近心 2024-10-25 15:13:52

就我而言,当我们使用 DataGrid WPF3.5 版本时,它运行得很好。我们升级到4.0,然后它就停止重置了。在搜索SO、谷歌等之后,我偶然发现了我的解决方案。
在 DataGridTextColumn 中的绑定上设置 UpdateSourceTrigger=PropertyChanged 为我解决了这个问题。

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

In my case, it worked all well and good when we were using the DataGrid WPF3.5 version. We upgraded to 4.0, then it stopped resetting. After searches on SO, google etc, I chanced upon my solution.
Setting UpdateSourceTrigger=PropertyChanged on the Binding in the DataGridTextColumn fixed it for me.

I just realised that the red exclamation mark does not clear on setting it to a correct value.

我做我的改变 2024-10-25 15:13:52

就我而言,我必须从绑定定义中删除

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>

Validation

<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

In my case I had to remove from binding definition

UpdateSourceTrigger=PropertyChanged

For me it works with both of these definitions:

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

And

<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 is custom class & get it here

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

And your data object must implement IDataErrorInfo

笔落惊风雨 2024-10-25 15:13:52

我没有使用 IDataErrorInfoINotifyDataErrorInfo,我的解决方案是将绑定从 UpdateSourceTrigger="PropertyChanged" 更改为 UpdateSourceTrigger="LostFocus 如果您在 DataGrid 列定义

中使用 ValidationRules,并且您需要在属性更改(在 UI 或属性中)时运行验证规则,请查看设置 ValidatesOnTargetUpdated= ValidationRule

XAML 示例中的“True”:

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

I am not using IDataErrorInfo or INotifyDataErrorInfo and my solution was to change my bindings from UpdateSourceTrigger="PropertyChanged" to UpdateSourceTrigger="LostFocus" This was the only thing that

If you are using ValidationRules in your DataGrid column defintion and you need the validation rules to run when ever the property changes (in the UI or the property) look into setting ValidatesOnTargetUpdated="True" on your ValidationRule

XAML Example:

<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>
疑心病 2024-10-25 15:13:52

我的场景是这样的:

  1. 模型实现 IDataErrorInfo
  2. 基于 WPF DataGrid 实用示例 - 使用 IDataErrorInfo 进行验证,使用 IDataErrorInfo 组合了模型中的所有错误。

    ;
        >
    
    
  3. 绑定内的

    ValidatesOnDataErrors=TrueValidatesOnExceptions=TrueNotifyOnValidationError=True(我开始使用的)

这导致对我的验证引擎的多次访问,并最终离开我的 DataGrid 处于不一致状态(即使行有效,行标题上也会出现错误通知)。

解决方案是从绑定中删除开关(第 3 点)。

我建议通读 也清除 DataGrid 行验证错误

My scenario was like this:

  1. Model implements IDataErrorInfo
  2. Custom Row Validation rule based on WPF DataGrid Practical Examples -Validation with IDataErrorInfo , that combined all errors from Model using IDataErrorInfo.

    <DataGrid.RowValidationRules>
        <local:RowDataInfoValidationRule ValidationStep="UpdatedValue" />
    </DataGrid.RowValidationRules>
    
  3. ValidatesOnDataErrors=True, ValidatesOnExceptions=True, NotifyOnValidationError=True within the binding (which I started with)

This caused multiple access to my validation Engine and eventualy left my DataGrid in inconsistent state (Error notification on row header even when row Valid).

The solution was to remove switches from the binding (point 3.)

I suggest reading through Clearing a DataGrid row validation error too.

难以启齿的温柔 2024-10-25 15:13:52

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

I have used this technique which do away with RowValidationRules and instead use the property validations in a viewmodel. This requires static variables and data annotations :

//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;
}       
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文