Erkennen von WPF-Validierungsfehlern

115

In WPF können Sie die Validierung basierend auf Fehlern einrichten, die während der Datenbindung mit ExceptionValidationRuleoder in Ihrer Datenschicht ausgelöst wurden DataErrorValidationRule.

Angenommen, Sie haben eine Reihe von Steuerelementen auf diese Weise eingerichtet und eine Schaltfläche Speichern. Wenn der Benutzer auf die Schaltfläche Speichern klickt, müssen Sie sicherstellen, dass keine Validierungsfehler vorliegen, bevor Sie mit dem Speichern fortfahren. Wenn es Validierungsfehler gibt, möchten Sie diese anschreien.

Wie können Sie in WPF herausfinden, ob für eines Ihrer datengebundenen Steuerelemente Validierungsfehler festgelegt wurden?

Kevin Berridge
quelle

Antworten:

137

Dieser Beitrag war sehr hilfreich. Vielen Dank an alle, die dazu beigetragen haben. Hier ist eine LINQ-Version, die Sie entweder lieben oder hassen werden.

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);
}
Dean
quelle
1
Ich mag diese spezielle Lösung sehr!
ChristopheD
Bin gerade über diesen Thread gestolpert. Sehr nützliche kleine Funktion. Vielen Dank!
Olav Haugen
Gibt es eine Möglichkeit, nur die DependencyObjects aufzulisten, die an einen bestimmten DataContext gebunden waren? Ich mag die Idee des Treewalks nicht. Möglicherweise ist eine Sammlung von Bindungen vorhanden, die mit einer bestimmten Datenquelle verknüpft sind.
ZAB
5
Ich frage mich nur, wie nennt man die IsValidFunktion? Ich sehe, dass Sie eine eingerichtet haben, CanExecutedie vermutlich mit dem Befehl der Schaltfläche Speichern zusammenhängt. Funktioniert das, wenn ich keine Befehle verwende? Und in welcher Beziehung steht die Schaltfläche zu den anderen Steuerelementen, die überprüft werden müssen? Mein einziger Gedanke, wie diese zu verwenden , ist durch den Aufruf IsValidfür jede Kontrolle , dass der Bedarf validiert werden. Bearbeiten: Es scheint, als würden Sie das überprüfen, senderwas ich als Speicherschaltfläche erwarte. Das scheint mir nicht richtig zu sein.
Nicholas Miller
1
@ Nick Miller a Windowist auch ein Abhängigkeitsobjekt. Ich, er richtet es wahrscheinlich mit einer Art Event-Handler auf der ein Window. Alternativ können Sie es auch direkt mit IsValid(this)aus der WindowKlasse aufrufen .
Akousmata
47

Der folgende Code (aus dem Programming WPF-Buch von Chris Sell & Ian Griffiths) überprüft alle Bindungsregeln für ein Abhängigkeitsobjekt und seine untergeordneten Objekte:

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

}

Sie können dies in Ihrer Schaltfläche zum Klicken auf die Schaltfläche "Ereignisbehandlungsroutine" auf Ihrer Seite / Ihrem Fenster aufrufen

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

    ....
   }
}
Aogan
quelle
33

Der veröffentlichte Code hat bei Verwendung einer ListBox bei mir nicht funktioniert. Ich habe es umgeschrieben und jetzt funktioniert es:

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
quelle
1
Stimmen Sie Ihre Lösung für die Arbeit an meinem ItemsControl ab.
Jeff T.
1
Ich verwende diese Lösung, um zu überprüfen, ob mein Datagrid Validierungsfehler aufweist. Diese Methode wird jedoch in der canexecute-Methode meines Befehls viewmodel aufgerufen, und ich denke, der Zugriff auf visuelle Baumobjekte verstößt irgendwie gegen das MVVM-Muster, nicht wahr? Irgendwelche Alternativen?
Igor Kondrasovas
16

Hatte das gleiche Problem und versuchte die bereitgestellten Lösungen. Eine Kombination der Lösungen von H-Man2 und skiba_k hat für mich fast gut funktioniert, mit einer Ausnahme: Mein Fenster verfügt über ein TabControl. Die Validierungsregeln werden nur für das derzeit sichtbare TabItem ausgewertet. Also habe ich VisualTreeHelper durch LogicalTreeHelper ersetzt. Jetzt gehts.

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

quelle
7

Zusätzlich zur großartigen LINQ-Implementierung von Dean hatte ich Spaß daran, den Code in eine Erweiterung für DependencyObjects zu packen:

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

Dies macht es sehr schön, wenn man die Wiederverwendbarkeit berücksichtigt.

Matthias Loerke
quelle
2

Ich würde eine kleine Optimierung anbieten.

Wenn Sie dies mehrmals über dieselben Steuerelemente tun, können Sie den obigen Code hinzufügen, um eine Liste der Steuerelemente zu führen, für die tatsächlich Validierungsregeln gelten. Wenn Sie dann die Gültigkeit überprüfen müssen, gehen Sie nur diese Steuerelemente anstelle des gesamten visuellen Baums durch. Dies würde sich als viel besser erweisen, wenn Sie viele solcher Steuerelemente haben.

Sprite
quelle
2

Hier ist eine Bibliothek zur Formularvalidierung in WPF. Nuget-Paket hier .

Stichprobe:

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

Die Idee ist, dass wir einen Validierungsbereich über die angehängte Eigenschaft definieren und ihm mitteilen, welche Eingabesteuerelemente verfolgt werden sollen. Dann können wir tun:

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

Sie können alle Steuerelemente rekursiv durchlaufen und die angehängte Eigenschaft Validation.HasErrorProperty überprüfen. Konzentrieren Sie sich dann auf die erste, die Sie darin finden.

Sie können auch viele bereits geschriebene Lösungen verwenden. In diesem Thread finden Sie ein Beispiel und weitere Informationen

user21243
quelle
0

Möglicherweise interessieren Sie sich für die BookLibrary- Beispielanwendung des WPF Application Framework (WAF) . Es wird gezeigt, wie die Validierung in WPF verwendet wird und wie die Schaltfläche Speichern gesteuert wird, wenn Validierungsfehler vorliegen.

jbe
quelle
0

In Antwortform aogan, anstatt explizit durch Validierungsregeln zu iterieren, besser einfach aufrufen 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 spielt im Feuerlicht
quelle