45

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

4

15 回答 15

26

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

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

同样的事情也发生在我身上,DataGridComboBoxColumn所以我试图更深入地挖掘。

问题不在于显示inside的Bindingin 。它正在将其绑定到祖先(正如它应该做的那样)并且该部分正在工作。ControlErrorTemplateDataGridHeaderBorderVisibilityValidation.HasErrorDataGridRow

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

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

在这里,我尝试使用将 ifDataTrigger设置ValidationErrorTemplate{x:Null}ifValidation.Errors.Count不是 1 来解决它。它在第一次迭代时效果很好,但是一旦我第二次清除错误,它又回来了。它不再有 3 个错误,而是有 7 个!经过几次迭代后,它超过了 10。

我还尝试通过在但没有骰子UpdateSource上手动清除错误。也没有任何效果。并且查看工具包中的源代码并没有让我到任何地方:)UpdateTargetBindingExpressionsValidation.ClearInvalid

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

到目前为止,我唯一的“解决方法”就是ErrorTemplateDataGridRowHeader

<DataGrid ...>
    <DataGrid.RowStyle>
        <Style TargetType="DataGridRow">
            <Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
        </Style>
    </DataGrid.RowStyle>
    <!-- ... -->
</DataGrid>
于 2011-09-17T01:08:01.923 回答
6

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

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

  • 明确地,当为 a 重建可视化树时,在其属性更改为新值之前,属于该单元格的DataGridCell所有DataGridCell.BuildVisualTree()BindingExpressions的都将被删除BindingGroupContent
  • 明确地,当 的Content属性DataGridCell被更改(通过DataGridCell.BuildVisualTree()或其他方式)时,BindingExpressionBase.Detach()会为旧属性值上的所有绑定调用该方法,这也会BindingGroup在任何ValidationError有机会被删除之前删除对 的引用
  • 隐含地,因为大多数对和 fromBindingExpressionBase的引用实际上都是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;
            }
        }
    }
}

然后将此属性通过绑定附加到 的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>
于 2020-05-05T12:59:32.467 回答
4

我找到了对我有用的最佳答案。只需清除您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 方法,用你喜欢的方式

于 2014-10-01T10:46:25.287 回答
3

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 属性。

于 2014-09-24T19:14:06.720 回答
2

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

RowHeaderWidth="20"DataGrid

于 2013-03-15T15:08:37.163 回答
1

尝试 从每个 Binding 元素中删除Mode=TwoWayfor each 。DataGridTextColumns

于 2011-09-16T17:44:15.497 回答
1

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

于 2011-09-22T14:39:20.917 回答
1

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

于 2013-01-18T11:28:44.777 回答
1

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

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

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

在 DataGrids XAML 中:

<DataGrid ... local:DataGridProps.ShowCellErrorBorder="False">
<DataGrid.Resources>
    <local:DataGridValidationConverter x:Key="DataGridValidationConverter" />
    <Style TargetType="TextBlock" x:Key="errTemplate">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                      Value="{Binding Path=(Validation.Errors)[0].ErrorContent}
                        RelativeSource={x:Static RelativeSource.Self}, "/>
                <Setter Property="Background" Value="LightSalmon"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</DataGrid.Resources>

<DataGrid.RowValidationErrorTemplate>
    <ControlTemplate>
            
    </ControlTemplate>
</DataGrid.RowValidationErrorTemplate>

<DataGrid.RowHeaderTemplate>
    <DataTemplate>
        <Grid Margin="0,-2,0,-2" 
              Visibility="{Binding Path=DataContext.HasErrors,
                RelativeSource={RelativeSource Mode=FindAncestor,
                  AncestorType={x:Type DataGridRow}},
                Converter={StaticResource DataGridValidationConverter},
                FallbackValue=Hidden}">
            <Ellipse StrokeThickness="0" Fill="Red"
                    Width="{Binding Path=FontSize,
                      RelativeSource={RelativeSource Mode=FindAncestor,
                        AncestorType={x:Type DataGridRow}}}"
                    Height="{Binding Path=FontSize,
                      RelativeSource={RelativeSource Mode=FindAncestor,
                        AncestorType={x:Type DataGridRow}}}" />
            <TextBlock Text="!" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"
                    FontSize="{Binding Path=FontSize,
                      RelativeSource={RelativeSource Mode=FindAncestor,
                        AncestorType={x:Type DataGridRow}}}" />               
        </Grid>
    </DataTemplate>
</DataGrid.RowHeaderTemplate>

<DataGrid.Columns>
    <DataGridTextColumn Header="Vorname" ElementStyle="{StaticResource errTemplate}"
          Binding="{Binding Path=Vorname,
             ValidatesOnNotifyDataErrors=True,
             NotifyOnValidationError=True}" />
    ...
</DataGrid.Columns>
</DataGrid>

数据网格验证转换器:

public class DataGridValidationConverter : IValueConverter
{
       
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((bool)value)
            return Visibility.Visible;
        else
            return Visibility.Hidden;
    }

       
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

数据网格道具:

public class DataGridProps
{
public static readonly DependencyProperty ShowCellErrorBorderProperty = DependencyProperty.RegisterAttached(
            "ShowCellErrorBorder", typeof(bool), typeof(DataGridProps), new PropertyMetadata(true, ShowCellErrorBorderPropertyChangedCallback));

public static bool GetShowCellErrorBorder(DependencyObject element)
{
    return (bool)element.GetValue(ShowCellErrorBorderProperty);
}

public static void SetShowCellErrorBorder(DependencyObject element, bool value)
{
    element.SetValue(ShowCellErrorBorderProperty, value);
}

private static void ShowCellErrorBorderPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
    if (GetShowCellErrorBorder(dependencyObject)) return;
    var dg = dependencyObject as DataGrid;
    if (null != dg)
    {
        dg.Loaded += (sender, args) =>
        {
            var scrollView = dg.Template.FindName("DG_ScrollViewer", dg) as ScrollViewer;
            if (null == scrollView) return;
            var scrollContent = scrollView.Template.FindName("PART_ScrollContentPresenter", scrollView) as ScrollContentPresenter;
            if (null == scrollContent) return;
            scrollContent.AdornerLayer.Visibility = Visibility.Hidden;
        };
    }
}
}

模型的实现:

public Model()
{
    if (Vorname == null)
        Vorname = "";
    ...

    errorsByPropertyName = new Dictionary<string, List<string>>();
    this.PropertyChanged += Model_PropertyChanged;
    ForceRevalidation(null);
}

private string _vorname;
public string Vorname { get => _vorname; set => SetField(ref _vorname, value); }
...

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private Dictionary<string, List<string>> errorsByPropertyName;

public void ForceRevalidation(string propertyName)
{
    if (string.IsNullOrEmpty(propertyName))
    {
        foreach (PropertyInfo property in GetType().GetProperties())
        {
            ValidateProperty(property.Name);
        }
    }
    else
    {
        ValidateProperty(propertyName);
    }
}

protected virtual void OnErrorsChanged(string propertyName)
{
    ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    OnPropertyChanged(nameof(HasErrors));
}

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

public bool HasErrors => errorsByPropertyName.Any();

public System.Collections.IEnumerable GetErrors(string propertyName)
{
    if (propertyName == null)
        propertyName = "";
    return errorsByPropertyName.ContainsKey(propertyName) ? errorsByPropertyName[propertyName] : null;
}

private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    ValidateProperty(e.PropertyName);
}

protected virtual void ValidateProperty(string propertyName)
{
    if (propertyName == null)
        propertyName = "";

    ClearErrors(propertyName);

    switch (propertyName)
    {
        case nameof(Vorname):
            if (string.IsNullOrWhiteSpace(Vorname))
                AddError(propertyName, propertyName + " is empty");
            break;
        ...
        default:
            break;

    }
}

protected void AddError(string propertyName, string error)
{
    if (!errorsByPropertyName.ContainsKey(propertyName))
        errorsByPropertyName[propertyName] = new List<string>();

    if (!errorsByPropertyName[propertyName].Contains(error))
    {
        errorsByPropertyName[propertyName].Add(error);
        OnErrorsChanged(propertyName);
    }
}

protected void ClearErrors(string propertyName)
{
    if (errorsByPropertyName.ContainsKey(propertyName))
    {
        errorsByPropertyName.Remove(propertyName);
        OnErrorsChanged(propertyName);
    }
}

我从我更大的 Model-Base-Class 中创建了这个最小的例子,希望我在这里得到了这方面的所有重要信息。

于 2020-11-08T13:57:15.347 回答
0

我的场景是这样的:

  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 行验证错误

于 2011-12-12T00:22:20.830 回答
0

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

于 2012-05-15T10:54:15.850 回答
0

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

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

于 2013-05-13T15:28:26.240 回答
0

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

UpdateSourceTrigger=PropertyChanged

对我来说,它适用于这两个定义:

<DataGridTextColumn                                         
Header="Time, min" 
x:Name="uiDataGridTextColumnTime"
Width="Auto"                                            
CellStyle="{StaticResource ResourceKey=DataGridCellText}"                                            
IsReadOnly="False">
<DataGridTextColumn.Binding>
    <Binding Path="fTime" StringFormat="{}{0:0.00}">
        <Binding.ValidationRules>
            <Validation:CellDataInfoValidationRule ValidationStep="UpdatedValue"/>
        </Binding.ValidationRules>
    </Binding>
</DataGridTextColumn.Binding>

<DataGridTextColumn                                         
Header="Time, min" 
x:Name="uiDataGridTextColumnTime"
Width="Auto"                                            
CellStyle="{StaticResource ResourceKey=DataGridCellText}"     
Binding="{Binding fTime, StringFormat={}\{0:0.00\}, ValidatesOnDataErrors=True}" 
IsReadOnly="False">

验证:CellDataInfoValidationRule 是自定义类并在此处获取

public class CellDataInfoValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        // obtain the bound business object
        BindingExpression expression = value as BindingExpression;
        IDataErrorInfo info = expression.DataItem as IDataErrorInfo;

        // determine the binding path
        string boundProperty = expression.ParentBinding.Path.Path;

        // obtain any errors relating to this bound property
        string error = info[boundProperty];
        if (!string.IsNullOrEmpty(error))
        {
            return new ValidationResult(false, error);
        }

        return ValidationResult.ValidResult;
    }
}

并且您的数据对象必须实现 IDataErrorInfo

于 2013-09-06T11:30:07.163 回答
0

我没有使用IDataErrorInfoorINotifyDataErrorInfo我的解决方案是将绑定从UpdateSourceTrigger="PropertyChanged"更改UpdateSourceTrigger="LostFocus"

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

XAML 示例:

<DataGridTextColumn Header="Name"
    CellStyle="{StaticResource DGCellStyle}"
    ElementStyle="{StaticResource DGTextColValidationStyle}"
    EditingElementStyle="{StaticResource DGTextColEditValidationStyle}">
    <DataGridTextColumn.Binding>
        <Binding Path="Name" UpdateSourceTrigger="LostFocus">
            <Binding.ValidationRules>
                <ValidationResource:YourValidationRule ValidationStep="UpdatedValue" ValidatesOnTargetUpdated="True" />
            </Binding.ValidationRules>
        </Binding>
    </DataGridTextColumn.Binding>
</DataGridTextColumn>
于 2013-12-06T19:40:10.577 回答
0

我使用了这种技术,它取消了 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;
}       
于 2016-10-05T11:26:04.030 回答