Détection des erreurs de validation WPF

115

Dans WPF, vous pouvez configurer la validation en fonction des erreurs générées dans votre couche de données lors de la liaison de données à l'aide de ExceptionValidationRuleou DataErrorValidationRule.

Supposons que vous ayez un tas de contrôles configurés de cette façon et que vous ayez un bouton Enregistrer. Lorsque l'utilisateur clique sur le bouton Enregistrer, vous devez vous assurer qu'il n'y a pas d'erreurs de validation avant de procéder à l'enregistrement. S'il y a des erreurs de validation, vous voulez les crier dessus.

Dans WPF, comment savoir si des erreurs de validation sont définies dans l'un de vos contrôles liés aux données?

Kevin Berridge
la source

Réponses:

137

Ce message a été extrêmement utile. Merci à tous ceux qui ont contribué. Voici une version LINQ que vous aimerez ou détesterez.

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);
}
doyen
la source
1
J'aime beaucoup cette solution particulière!
ChristopheD
Je viens de trébucher sur ce fil. Petite fonction très utile. Merci!
Olav Haugen
Existe-t-il un moyen d'énumérer uniquement les DependencyObjects qui étaient liés à un DataContext particulier? Je n'aime pas l'idée de la promenade dans les arbres. Il peut y avoir une collection de liaisons liées à une source de données particulière.
ZAB
5
Vous vous demandez simplement comment appeler la IsValidfonction? Je vois que vous avez configuré un CanExecutequi, je suppose, est lié à la commande du bouton Enregistrer. Cela fonctionnera-t-il si je n'utilise pas de commandes? Et comment le bouton est-il lié aux autres commandes qui doivent être vérifiées? Ma seule réflexion sur la façon de l'utiliser est d'appeler IsValidchaque contrôle qui doit être validé. Edit: Il semble que vous validez senderce que j'espère être le bouton de sauvegarde. Cela ne me semble pas correct.
Nicholas Miller
1
@Nick Miller a Windowest également un objet de dépendance. I il est probablement en train de le configurer avec une sorte de gestionnaire d'événements sur le Window. Vous pouvez également l'appeler directement IsValid(this)depuis la Windowclasse.
akousmata
47

Le code suivant (tiré du livre Programming WPF de Chris Sell & Ian Griffiths) valide toutes les règles de liaison sur un objet de dépendance et ses enfants:

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

}

Vous pouvez appeler cela dans votre gestionnaire d'événement de clic de bouton d'enregistrement comme ceci dans votre page / fenêtre

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

    ....
   }
}
Aogan
la source
33

Le code affiché ne fonctionnait pas pour moi lors de l'utilisation d'une ListBox. Je l'ai réécrit et maintenant ça marche:

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;
}
H-Man2
la source
1
Votez votre solution pour travailler sur mon ItemsControl.
Jeff T.26
1
J'utilise cette solution pour vérifier si ma grille de données comporte des erreurs de validation. Cependant, cette méthode est appelée sur ma méthode canexecute de commande viewmodel, et je pense que l'accès aux objets de l'arborescence visuelle viole en quelque sorte le modèle MVVM, n'est-ce pas? Des alternatives?
Igor Kondrasovas
16

Eu le même problème et essayé les solutions fournies. Une combinaison des solutions H-Man2 et skiba_k a fonctionné presque bien pour moi, à une exception près: Ma fenêtre a un TabControl. Et les règles de validation ne sont évaluées que pour le TabItem actuellement visible. J'ai donc remplacé VisualTreeHelper par LogicalTreeHelper. Maintenant ça marche.

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

la source
7

En plus de la grande implémentation LINQ de Dean, je me suis amusé à envelopper le code dans une extension pour DependencyObjects:

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

Cela le rend extrêmement agréable compte tenu de la réutilisation.

Matthias Loerke
la source
2

Je proposerais une petite optimisation.

Si vous faites cela plusieurs fois sur les mêmes contrôles, vous pouvez ajouter le code ci-dessus pour conserver une liste de contrôles qui ont en fait des règles de validation. Ensuite, chaque fois que vous avez besoin de vérifier la validité, passez uniquement en revue ces contrôles, au lieu de l'ensemble de l'arborescence visuelle. Cela s'avérerait beaucoup mieux si vous disposiez de nombreux contrôles de ce type.

lutin
la source
2

Voici une bibliothèque pour la validation de formulaire dans WPF. Paquet Nuget ici .

Échantillon:

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

L'idée est que nous définissons une portée de validation via la propriété attachée en lui indiquant les contrôles d'entrée à suivre. Ensuite, nous pouvons faire:

<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>
Johan Larsson
la source
0

Vous pouvez parcourir toutes vos arborescences de contrôles de manière récursive et vérifier la propriété attachée Validation.HasErrorProperty, puis vous concentrer sur la première que vous y trouvez.

vous pouvez également utiliser de nombreuses solutions déjà écrites, vous pouvez consulter ce fil pour un exemple et plus d'informations

user21243
la source
0

Vous pourriez être intéressé par l' exemple d'application BookLibrary de WPF Application Framework (WAF) . Il montre comment utiliser la validation dans WPF et comment contrôler le bouton Enregistrer en cas d'erreurs de validation.

jbe
la source
0

Dans la forme de réponse aogan, au lieu d'itérer explicitement à travers les règles de validation, mieux vaut simplement invoquer 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;
    }
}
Dan joue à la lueur du feu
la source