Detectando errores de validación de WPF

En WPF puede configurar la validación en función de los errores arrojados en su capa de datos durante la vinculación de datos utilizando la ExceptionValidationRule o DataErrorValidationRule .

Supongamos que tiene un grupo de controles configurados de esta manera y tiene un botón Guardar. Cuando el usuario hace clic en el botón Guardar, debe asegurarse de que no haya errores de validación antes de continuar con el guardado. Si hay errores de validación, quiere gritarles.

En WPF, ¿cómo averigua si alguno de sus controles de Data Bound tiene errores de validación?

Esta publicación fue extremadamente útil. Gracias a todos los que contribuyeron. Aquí hay una versión de LINQ que amarás u odiarás.

 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() .All(IsValid); } 

El siguiente código (del libro Programming WPF de Chris Sell e Ian Griffiths) valida todas las reglas de enlace en un objeto de dependencia y sus hijos:

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

Puede invocar esto en su controlador de evento de clic de guardar como este en su página / ventana

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

El código publicado no funcionó para mí cuando usé un ListBox. Lo reescribí y ahora funciona:

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

Tuve el mismo problema y probé las soluciones proporcionadas. Una combinación de las soluciones de H-Man2 y skiba_k funcionó casi bien para mí, por una sola excepción: My Window tiene un TabControl. Y las reglas de validación solo se evalúan para el TabItem que está actualmente visible. Así que reemplacé VisualTreeHelper por LogicalTreeHelper. Ahora funciona.

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

Además de la gran implementación de LINQ de Dean, me divertí envolviendo el código en una extensión para DependencyObjects:

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

Esto lo hace extremadamente agradable teniendo en cuenta la reutilización.

Yo ofrecería una pequeña optimización.

Si hace esto muchas veces sobre los mismos controles, puede agregar el código anterior para mantener una lista de controles que realmente tienen reglas de validación. Luego, cuando necesite verificar la validez, solo revise esos controles, en lugar de todo el árbol visual. Esto probaría ser mucho mejor si tiene muchos de esos controles.

Aquí hay una biblioteca para la validación de formularios en WPF. Nuget paquete aquí .

Muestra:

       

La idea es que definamos un scope de validación a través de la propiedad adjunta diciéndole qué controles de entrada seguir. Entonces podemos hacer:

        

Puede iterar sobre todos los árboles de control recursivamente y verificar la propiedad adjunta Validation.HasErrorProperty, luego enfocarse en la primera que encuentre en ella.

También puede usar muchas soluciones ya escritas. Puede consultar este hilo para ver un ejemplo y obtener más información.

Es posible que le interese la aplicación de ejemplo BookLibrary del WPF Application Framework (WAF) . Muestra cómo usar la validación en WPF y cómo controlar el botón Guardar cuando existen errores de validación.

En forma de respuesta aogan, en lugar de iterar explícitamente a través de reglas de validación, mejor solo invocar 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; } }