WPF DataGrid验证错误未清除

s73*_*v3r 36 validation wpf datagrid idataerrorinfo wpfdatagrid

所以我有一个WPF DataGrid,它必然会被绑定ObservableCollection.该集合通过对其成员进行验证IDataErrorInfo.如果我以某种方式编辑单元格以使其无效,然后在击中输入之前将其标记离开它,然后返回并使其有效,单元格将停止显示无效,但是,"!" 在行的头部仍将存在,并且ToolTip将引用先前的无效值.

Fre*_*lad 23

Mode=TwoWay用于DataGridTextColumns解决问题的一个版本,但是由于其他原因,这个问题似乎无处不在.

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

同样的事情发生在我身上,DataGridComboBoxColumn所以我试着深入挖掘一下.

问题是不是BindingControl显示的ErrorTemplate内部DataGridHeaderBorder.它结合了VisibilityValidation.HasError始祖DataGridRow(正是因为它应该做的),而且部分工作.

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

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

在这里,我试图用一个来解决它DataTrigger是设置ValidationErrorTemplate{x:Null},如果Validation.Errors.Count没有1.对于第一次迭代的工作很好,但一旦我清除错误,第二次就又回来了.它不再有3个错误,它有7个!经过几次迭代后,它超过了10.

我也试图通过手动执行清除错误UpdateSource,并UpdateTargetBindingExpressions,但没有骰子.Validation.ClearInvalid也没有任何影响.通过工具包中的源代码查看并没有让我到处:)

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

到目前为止,我唯一的"解决方法"就是隐藏ErrorTemplateDataGridRowHeader

<DataGrid ...>
    <DataGrid.RowStyle>
        <Style TargetType="DataGridRow">
            <Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
        </Style>
    </DataGrid.RowStyle>
    <!-- ... -->
</DataGrid>
Run Code Online (Sandbox Code Playgroud)


MSL*_*MSL 5

我找到了最适合我的答案.请清楚你DataGridRowValidationErrorTemplate.

  1. 在代码中

    YourGrid.RowValidationErrorTemplate = new ControlTemplate();
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在Xaml

    <DataGrid.RowValidationErrorTemplate>
        <ControlTemplate>
        </ControlTemplate>
    </DataGrid.RowValidationErrorTemplate>`
    
    Run Code Online (Sandbox Code Playgroud)
  3. 然后制作自己的行验证错误模板.

    如果您的数据项是INotifyPropertyChanged

    ((INotifyPropertyChanged)i).PropertyChanged += this.i_PropertyChanged;`
    
    Run Code Online (Sandbox Code Playgroud)

    然后

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 按照自己喜欢的方式编写自己的IsValid方法


Ana*_*eus 5

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

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

  • 明确地说,当视觉树是重建一个DataGridCellDataGridCell.BuildVisualTree()所有旧BindingExpressionsBindingGroup属于该单元被删除,它的前Content属性更改为新的值
  • 明确地,当 的Content属性DataGridCell被更改(通过DataGridCell.BuildVisualTree()或其他方式)时,该BindingExpressionBase.Detach()方法将被调用用于旧属性值上的所有绑定,这也会BindingGroup在任何ValidationError有机会被删除之前删除对 的引用
  • 隐含地,因为大多数对和来自的引用BindingExpressionBase实际上都是WeakReferences,即使上述所有情况都不会导致引用的删除,但是当某些东西查找TargetElementof 时BindingExpressionBase,底层WeakReference返回null并且属性访问器有可能再次调用破碎的Detach()方法

有了上述发现,现在也很清楚为什么使用Mode=TwoWayforDataGridTextColumn有时可以解决问题。该DataGridTextColumn会成为只读和Content财产DataGridCell因此从来没有改变过。

我已经DependencyProperty为此使用附件编写了一个解决方法。

using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Controls.Primitives;

namespace Utilities
{
    public static class DataGridExtension
    {
    /// <summary>
    /// Identifies the FixBindingGroupValidationErrorsFor attached property. 
    /// </summary>
    public static readonly DependencyProperty FixBindingGroupValidationErrorsForProperty =
        DependencyProperty.RegisterAttached("FixBindingGroupValidationErrorsFor", typeof(DependencyObject), typeof(DataGridExtension),
            new PropertyMetadata(null, new PropertyChangedCallback(OnFixBindingGroupValidationErrorsForChanged)));

    /// <summary>
    /// Gets the value of the FixBindingGroupValidationErrorsFor property
    /// </summary>
    public static DependencyObject GetFixBindingGroupValidationErrorsFor(DependencyObject obj)
    {
        return (DependencyObject)obj.GetValue(FixBindingGroupValidationErrorsForProperty);
    }

    /// <summary>
    /// Sets the value of the FixBindingGroupValidationErrorsFor property
    /// </summary>
    public static void SetFixBindingGroupValidationErrorsFor(DependencyObject obj, DependencyObject value)
    {
        obj.SetValue(FixBindingGroupValidationErrorsForProperty, value);
    }

    /// <summary>
    /// Handles property changed event for the FixBindingGroupValidationErrorsFor property.
    /// </summary>
    private static void OnFixBindingGroupValidationErrorsForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DependencyObject oldobj = (DependencyObject)e.OldValue;
        if (oldobj != null)
        {
            BindingGroup group = FindBindingGroup(d); //if d!=DataGridCell, use (DependencyObject)e.NewValue
            var leftOverErrors = group.ValidationErrors != null ?
                Validation.GetErrors(group.Owner).Except(group.ValidationErrors).ToArray() : Validation.GetErrors(group.Owner).ToArray();
            foreach (var error in leftOverErrors)
            {
                //HINT: BindingExpressionBase.Detach() removes the reference to BindingGroup, before ValidationErrors are removed.
                if (error.BindingInError is BindingExpressionBase binding && (binding.Target == null ||
                    TreeHelper.IsDescendantOf(binding.Target, oldobj)) && binding.BindingGroup == null &&
                    (binding.ValidationErrors == null || binding.ValidationErrors.Count == 0 || !binding.ValidationErrors.Contains(error)))
                {
                    typeof(Validation).GetMethod("RemoveValidationError", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] {error, group.Owner, group.NotifyOnValidationError});
                }
            }
        }
    }

    private static BindingGroup FindBindingGroup(DependencyObject obj)
    {
        do
        {
            if (obj is FrameworkElement fe)
            {
                return fe.BindingGroup;
            }
            if (obj is FrameworkContentElement fce)
            {
                return fce.BindingGroup;
            }
            obj = LogicalTreeHelper.GetParent(obj);
        } while (obj != null);
        return null;
    }

        private static class TreeHelper
        {
            private static DependencyObject GetParent(DependencyObject element, bool recurseIntoPopup)
            {
                if (recurseIntoPopup)
                {
                    // Case 126732 : To correctly detect parent of a popup we must do that exception case
                    Popup popup = element as Popup;

                    if ((popup != null) && (popup.PlacementTarget != null))
                        return popup.PlacementTarget;
                }

                Visual visual = element as Visual;
                DependencyObject parent = (visual == null) ? null : VisualTreeHelper.GetParent(visual);

                if (parent == null)
                {
                    // No Visual parent. Check in the logical tree.
                    parent = LogicalTreeHelper.GetParent(element);

                    if (parent == null)
                    {
                        FrameworkElement fe = element as FrameworkElement;

                        if (fe != null)
                        {
                            parent = fe.TemplatedParent;
                        }
                        else
                        {
                            FrameworkContentElement fce = element as FrameworkContentElement;

                            if (fce != null)
                            {
                                parent = fce.TemplatedParent;
                            }
                        }
                    }
                }

                return parent;
            }

            public static bool IsDescendantOf(DependencyObject element, DependencyObject parent)
            {
                return TreeHelper.IsDescendantOf(element, parent, true);
            }

            public static bool IsDescendantOf(DependencyObject element, DependencyObject parent, bool recurseIntoPopup)
            {
                while (element != null)
                {
                    if (element == parent)
                        return true;

                    element = TreeHelper.GetParent(element, recurseIntoPopup);
                }

                return false;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将此属性绑定到 的Content属性DataGridCell

<Window ...
        xmlns:utils="clr-namespace:Utilities">
     ...
     <DataGrid ...>
         <DataGrid.CellStyle>
            <Style BasedOn="{StaticResource {x:Type DataGridCell}}" TargetType="{x:Type DataGridCell}">
                <Setter Property="utils:DataGridExtension.FixBindingGroupValidationErrorsFor" Value="{Binding Content, RelativeSource={RelativeSource Self}}" />
            </Style>
        </DataGrid.CellStyle>
     </DataGrid>
     ...
</Window>
Run Code Online (Sandbox Code Playgroud)