Domanda

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

Supponi di aver impostato un sacco di controlli 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, vuoi urlare contro di loro.

In WPF, come si fa a sapere se qualcuno dei controlli associati ai dati ha errori di convalida impostati?

È stato utile?

Soluzione

Questo post è stato estremamente utile. Grazie a tutti coloro che hanno contribuito. Ecco una versione LINQ che adorerai 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);
}

Altri suggerimenti

Il seguente codice (dal libro di programmazione WPF di Chris Sell e Ian Griffiths) convalida tutte le regole vincolanti su un oggetto di 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 di eventi click button save in questo modo nella tua pagina / finestra

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

    ....
   }
}

Il codice pubblicato non ha funzionato per me quando utilizzo 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;
}

Ha riscontrato lo stesso problema e provato le soluzioni fornite. Una combinazione delle soluzioni di H-Man2 e skiba_k ha funzionato quasi perfettamente per me, con un'eccezione: My Window ha un TabControl. E le regole di validazione vengono valutate solo per il TabItem che è attualmente visibile. Così 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;
    }

Oltre alla grande 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 reuseablità.

Vorrei offrire una piccola ottimizzazione.

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

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

Esempio:

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

Puoi iterare ricorsivamente su tutti i tuoi controlli e controllare la proprietà collegata 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

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

Nel modulo di risposta aogan, invece di iterare esplicitamente attraverso le regole di convalida, è meglio 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;
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top