【问题标题】:Detecting WPF Validation Errors检测 WPF 验证错误
【发布时间】:2010-09-12 18:02:21
【问题描述】:

在 WPF 中,您可以使用 ExceptionValidationRuleDataErrorValidationRule 根据数据绑定期间数据层中抛出的错误设置验证。

假设您以这种方式设置了一堆控件,并且有一个保存按钮。当用户单击“保存”按钮时,您需要确保在继续保存之前没有验证错误。如果有验证错误,你想对它们大喊大叫。

在 WPF 中,如何确定是否有任何数据绑定控件设置了验证错误?

【问题讨论】:

    标签: wpf validation data-binding


    【解决方案1】:

    这篇文章非常有帮助。感谢所有做出贡献的人。这是一个你会喜欢或讨厌的 LINQ 版本。

    private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = IsValid(sender as DependencyObject);
    }
    
    private bool IsValid(DependencyObject obj)
    {
        // The dependency object is valid if it has no errors and all
        // of its children (that are dependency objects) are error-free.
        return !Validation.GetHasError(obj) &&
        LogicalTreeHelper.GetChildren(obj)
        .OfType<DependencyObject>()
        .All(IsValid);
    }
    

    【讨论】:

    • 我非常喜欢这个特殊的解决方案!
    • 刚刚偶然发现了这个线程。很实用的小功能。谢谢!
    • 有没有办法只枚举那些绑定到特定 DataContext 的 DependencyObjects?我不喜欢treewalk的想法。可能存在链接到特定数据源的绑定集合。
    • 只是想知道,您如何调用IsValid 函数?我看到您已经设置了一个CanExecute,我猜这与保存按钮的命令有关。如果我不使用命令,这会起作用吗?按钮与需要检查的其他控件有什么关系?我对如何使用它的唯一想法是为每个需要验证的控件调用IsValid编辑: 看来您正在验证 sender,我希望它是保存按钮。这对我来说似乎不合适。
    • @Nick Miller a Window 也是一个依赖对象。我他可能正在使用Window 上的某种事件处理程序来设置它。或者,您可以直接使用 Window 类中的 IsValid(this) 调用它。
    【解决方案2】:

    以下代码(来自 Chris Sell 和 Ian Griffiths 的 Programming WPF 书籍)验证依赖对象及其子对象上的所有绑定规则:

    public static class Validator
    {
    
        public static bool IsValid(DependencyObject parent)
        {
            // Validate all the bindings on the parent
            bool valid = true;
            LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
            while (localValues.MoveNext())
            {
                LocalValueEntry entry = localValues.Current;
                if (BindingOperations.IsDataBound(parent, entry.Property))
                {
                    Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                    foreach (ValidationRule rule in binding.ValidationRules)
                    {
                        ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
                        if (!result.IsValid)
                        {
                            BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
                            System.Windows.Controls.Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
                            valid = false;
                        }
                    }
                }
            }
    
            // Validate all the bindings on the children
            for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                if (!IsValid(child)) { valid = false; }
            }
    
            return valid;
        }
    
    }
    

    您可以在页面/窗口中像这样在保存按钮单击事件处理程序中调用它

    private void saveButton_Click(object sender, RoutedEventArgs e)
    {
    
      if (Validator.IsValid(this)) // is valid
       {
    
        ....
       }
    }
    

    【讨论】:

    • 我喜欢这个答案,因为代码还验证了未触及的绑定。
    【解决方案3】:

    使用 ListBox 时,发布的代码对我不起作用。我重写了它,现在它可以工作了:

    public static bool IsValid(DependencyObject parent)
    {
        if (Validation.GetHasError(parent))
            return false;
    
        // Validate all the bindings on the children
        for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
        {
            DependencyObject child = VisualTreeHelper.GetChild(parent, i);
            if (!IsValid(child)) { return false; }
        }
    
        return true;
    }
    

    【讨论】:

    • 对我的 ItemsControl 的解决方案投赞成票。
    • 我正在使用这个解决方案来检查我的数据网格是否有验证错误。但是,这个方法是在我的 viewmodel 命令 canexecute 方法上调用的,我认为访问可视化树对象以某种方式违反了 MVVM 模式,不是吗?有什么选择吗?
    【解决方案4】:

    遇到了同样的问题并尝试了提供的解决方案。 H-Man2 和skiba_k 的解决方案的组合对我来说几乎可以正常工作,但有一个例外:我的窗口有一个TabControl。并且验证规则仅针对当前可见的 TabItem 进行评估。所以我用LogicalTreeHelper替换了VisualTreeHelper。现在可以了。

        public static bool IsValid(DependencyObject parent)
        {
            // Validate all the bindings on the parent
            bool valid = true;
            LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
            while (localValues.MoveNext())
            {
                LocalValueEntry entry = localValues.Current;
                if (BindingOperations.IsDataBound(parent, entry.Property))
                {
                    Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                    if (binding.ValidationRules.Count > 0)
                    {
                        BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
                        expression.UpdateSource();
    
                        if (expression.HasError)
                        {
                            valid = false;
                        }
                    }
                }
            }
    
            // Validate all the bindings on the children
            System.Collections.IEnumerable children = LogicalTreeHelper.GetChildren(parent);
            foreach (object obj in children)
            {
                if (obj is DependencyObject)
                {
                    DependencyObject child = (DependencyObject)obj;
                    if (!IsValid(child)) { valid = false; }
                }
            }
            return valid;
        }
    

    【讨论】:

      【解决方案5】:

      除了 Dean 出色的 LINQ 实现之外,我还很高兴将代码包装到 DependencyObjects 的扩展中:

      public static bool IsValid(this DependencyObject instance)
      {
         // Validate recursivly
         return !Validation.GetHasError(instance) &&  LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
      }
      

      考虑到可重用性,这使得它非常好。

      【讨论】:

        【解决方案6】:

        我会提供一个小的优化。

        如果您对相同的控件多次执行此操作,则可以添加上述代码以保留实际具有验证规则的控件列表。然后,每当您需要检查有效性时,只检查那些控件,而不是整个可视化树。 如果您有许多这样的控件,这将证明会好得多。

        【讨论】:

          【解决方案7】:

          这是一个library,用于 WPF 中的表单验证。 Nuget package here.

          示例:

          <Border BorderBrush="{Binding Path=(validationScope:Scope.HasErrors),
                                        Converter={local:BoolToBrushConverter},
                                        ElementName=Form}"
                  BorderThickness="1">
              <StackPanel x:Name="Form" validationScope:Scope.ForInputTypes="{x:Static validationScope:InputTypeCollection.Default}">
                  <TextBox Text="{Binding SomeProperty}" />
                  <TextBox Text="{Binding SomeOtherProperty}" />
              </StackPanel>
          </Border>
          

          我们的想法是我们通过附加属性定义一个验证范围,告诉它要跟踪哪些输入控件。 然后我们可以这样做:

          <ItemsControl ItemsSource="{Binding Path=(validationScope:Scope.Errors),
                                              ElementName=Form}">
              <ItemsControl.ItemTemplate>
                  <DataTemplate DataType="{x:Type ValidationError}">
                      <TextBlock Foreground="Red"
                                 Text="{Binding ErrorContent}" />
                  </DataTemplate>
              </ItemsControl.ItemTemplate>
          </ItemsControl>
          

          【讨论】:

            【解决方案8】:

            您可以递归地遍历所有控件树并检查附加属性 Validation.HasErrorProperty,然后专注于您在其中找到的第一个。

            您还可以使用许多已经编写好的解决方案 您可以查看this 线程以获取示例和更多信息

            【讨论】:

              【解决方案9】:

              您可能对 WPF Application Framework (WAF)BookLibrary 示例应用程序感兴趣。它展示了如何在 WPF 中使用验证,以及在存在验证错误时如何控制“保存”按钮。

              【讨论】:

                【解决方案10】:

                在aogan的回答表单中,与其显式地遍历验证规则,不如直接调用expression.UpdateSource():

                if (BindingOperations.IsDataBound(parent, entry.Property))
                {
                    Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                    if (binding.ValidationRules.Count > 0)
                    {
                        BindingExpression expression 
                            = BindingOperations.GetBindingExpression(parent, entry.Property);
                        expression.UpdateSource();
                
                        if (expression.HasError) valid = false;
                    }
                }
                

                【讨论】:

                  猜你喜欢
                  • 2013-02-18
                  • 2011-06-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-03-10
                  • 2020-08-17
                  • 2015-07-22
                  • 2015-07-17
                  相关资源
                  最近更新 更多