Pregunta

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

Supongamos que tiene varios 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, querrás gritarles.

En WPF, ¿cómo puede saber si alguno de sus controles vinculados a datos tiene errores de validación establecidos?

¿Fue útil?

Solución

Esta publicación fue extremadamente útil.Gracias a todos los que contribuyeron.Aquí tienes 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<DependencyObject>()
    .All(IsValid);
}

Otros consejos

El siguiente código (del libro Programación de WPF de Chris Sell e Ian Griffiths) valida todas las reglas vinculantes 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 llamar a esto en el controlador de eventos de clic del botón Guardar de esta manera en su página/ventana

private void saveButton_Click(object sender, RoutedEventArgs e)
{

  if (Validator.IsValid(this)) // is valid
   {

    ....
   }
}

El código publicado no me funcionó 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í, con una excepción:Mi ventana tiene un TabControl.Y las reglas de validación solo se evalúan para el TabItem que está visible actualmente.Entonces 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 excelente implementación 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<DependencyObject>().All(child => child.IsValid());
}

Esto lo hace extremadamente bueno considerando la reutilización.

Ofrecería una pequeña optimización.

Si hace esto muchas veces con 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, revise solo esos controles, en lugar de todo el árbol visual.Esto resultaría mucho mejor si tuviera muchos controles de este tipo.

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

Muestra:

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

La idea es que definimos un alcance de validación a través de la propiedad adjunta que le indica qué controles de entrada rastrear.Entonces podemos hacer:

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

Puede iterar sobre todo su árbol de controles de forma recursiva y verificar la propiedad adjunta Validation.HasErrorProperty, luego concentrarse en el primero que encuentre en él.

También puede usar muchas soluciones ya escritas que puede verificar este hilo para un ejemplo y más información

Quizás te interese el LibroBiblioteca aplicación de muestra de la Marco de aplicación WPF (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, en lugar de iterar explícitamente a través de las reglas de validación, es mejor simplemente 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;
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top