Rilevamento degli errori di convalida di WPF


115

In WPF è possibile impostare la convalida in base agli errori generati nel livello dati durante l'associazione dati utilizzando ExceptionValidationRuleo DataErrorValidationRule.

Supponi di avere un gruppo di controlli impostati in questo modo e di avere un pulsante Salva. Quando l'utente fa clic sul pulsante Salva, è necessario assicurarsi che non vi siano errori di convalida prima di procedere con il salvataggio. Se ci sono errori di convalida, puoi urlargli contro.

In WPF, come scoprire se uno dei controlli associati ai dati presenta errori di convalida impostati?

Risposte:


137

Questo post è stato estremamente utile. Grazie a tutti coloro che hanno contribuito. Ecco una versione di LINQ che amerai o odierai.

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

1
Mi piace molto questa particolare soluzione!
ChristopheD

Sono appena inciampato in questo thread. Piccola funzione molto utile. Grazie!
Olav Haugen

C'è un modo per enumerare solo quegli oggetti DependencyObject che erano associati a un particolare DataContext? Non mi piace l'idea del treewalk. Potrebbe esserci una raccolta di associazioni collegate a una particolare origine dati.
ZAB

5
Mi chiedo solo, come si chiama la IsValidfunzione? Vedo che hai impostato un CanExecuteche immagino sia correlato al comando del pulsante Salva. Funzionerà se non sto usando i comandi? E in che modo il pulsante è correlato agli altri controlli che devono essere controllati? Il mio unico pensiero su come usarlo è chiamare IsValidogni controllo che deve essere convalidato. Modifica: sembra che tu stia convalidando quello senderche mi aspetto sia il pulsante di salvataggio. Non mi sembra giusto.
Nicholas Miller

1
@ Nick Miller Windowè anche un oggetto di dipendenza. Probabilmente lo sta configurando con una sorta di gestore di eventi su Window. In alternativa, puoi semplicemente chiamarlo direttamente con IsValid(this)dalla Windowclasse.
akousmata

47

Il codice seguente (dal libro Programming WPF di Chris Sell e Ian Griffiths) convalida tutte le regole vincolanti su un oggetto dipendenza e sui suoi figli:

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

}

Puoi chiamarlo nel gestore dell'evento clic del pulsante di salvataggio in questo modo nella tua pagina / finestra

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

    ....
   }
}

33

Il codice pubblicato non ha funzionato per me quando si utilizza un ListBox. L'ho riscritto e ora funziona:

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

1
Vota la tua soluzione per lavorare sul mio ItemsControl.
Jeff T.

1
Sto usando questa soluzione per verificare se il mio datagrid ha errori di convalida. Tuttavia, questo metodo viene chiamato sul mio metodo canexecute del comando viewmodel e penso che l'accesso agli oggetti della struttura ad albero visuale in qualche modo violi lo schema MVVM, vero? Eventuali alternative?
Igor Kondrasovas

16

Ha avuto lo stesso problema e ha provato le soluzioni fornite. Una combinazione delle soluzioni di H-Man2 e skiba_k ha funzionato quasi bene per me, per un'eccezione: My Window ha un TabControl. E le regole di convalida vengono valutate solo per il TabItem attualmente visibile. Quindi ho sostituito VisualTreeHelper con LogicalTreeHelper. Ora funziona.

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

7

Oltre alla fantastica implementazione LINQ di Dean, mi sono divertito a racchiudere il codice in un'estensione per DependencyObjects:

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

Questo lo rende estremamente piacevole considerando la riusabilità.


2

Vorrei offrire una piccola ottimizzazione.

Se lo fai molte volte sugli stessi controlli, puoi aggiungere il codice precedente per mantenere un elenco di controlli che hanno effettivamente regole di convalida. Quindi, ogni volta che è necessario verificare la validità, passare solo a quei controlli, invece dell'intero albero visivo. Ciò si rivelerebbe molto meglio se si dispone di molti di questi controlli.


2

Ecco una libreria per la convalida dei moduli in WPF. Pacchetto Nuget qui .

Campione:

<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'idea è che definiamo un ambito di convalida tramite la proprietà allegata indicandogli quali controlli di input tenere traccia. Quindi possiamo fare:

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

0

Puoi iterare su tutti i tuoi controlli in modo ricorsivo e controllare la proprietà allegata Validation.HasErrorProperty, quindi concentrarti sul primo che trovi in ​​esso.

puoi anche usare molte soluzioni già scritte puoi controllare questo thread per un esempio e ulteriori informazioni


0

Potresti essere interessato all'applicazione di esempio BookLibrary di WPF Application Framework (WAF) . Mostra come utilizzare la convalida in WPF e come controllare il pulsante Salva in caso di errori di convalida.


0

Nella forma di risposta aogan, invece di iterare esplicitamente attraverso le regole di convalida, meglio semplicemente invocare 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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.