检测WPFvalidation错误

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

假设你有一堆这样设置的控件,并且你有一个Savebutton。 当用户单击保存button时,在继续保存之前,您需要确保没有validation错误。 如果有validation错误,你想对他们进行调查。

在WPF中,如何查找是否有任何Data Bound控件有validation错误?

这篇文章是非常有帮助的。 感谢所有人的贡献。 这是一个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); } 

下面的代码(来自Chris Sell和Ian Griffiths编写的WPF书)validation依赖对象及其子对象的所有绑定规则:

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

你可以在你的页面/窗口中的保存button点击事件处理程序中调用它

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

发布的代码在使用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; } 

有同样的问题,并尝试提供的解决scheme。 H-Man2和skiba_k的解决scheme的组合对我来说几乎没有问题,只有一个例外:我的窗口有一个TabControl。 validation规则仅针对当前可见的TabItem进行评估。 所以我用LogicalTreeHelperreplace了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; } 

除了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()); } 

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

我会提供一个小优化。

如果你在相同的控件上做了这么多次,你可以添加上面的代码来保存一个实际上有validation规则的控件列表。 然后,无论何时您需要检查有效性,只能通过这些控件,而不是整个视觉树。 如果你有很多这样的控制,这将会更好。

这里是一个在WPF中进行表单validation的库 。 Nuget包在这里 。

样品:

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

我们的想法是,我们通过附加的属性定义一个validation范围,告诉它跟踪哪个input控件。 那我们可以这样做:

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

你可以迭代遍历你所有的控件树,并检查附加的属性Validation.HasErrorProperty,然后关注你在其中find的第一个。

你也可以使用许多已经编写的解决scheme,你可以检查这个线程的例子和更多的信息

您可能对WPF应用程序框架(WAF)BookLibrary示例应用程序感兴趣。 它演示了如何在WPF中使用validation,以及如何validation错误时如何控制Savebutton。

在回答formsaogan,而不是显式迭代validation规则,更好地调用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; } }