Detecting WPF Validation Errors

WpfValidationData Binding

Wpf Problem Overview


In WPF you can setup validation based on errors thrown in your Data Layer during Data Binding using the ExceptionValidationRule or DataErrorValidationRule.

Suppose you had a bunch of controls set up this way and you had a Save button. When the user clicks the Save button, you need to make sure there are no validation errors before proceeding with the save. If there are validation errors, you want to holler at them.

In WPF, how do you find out if any of your Data Bound controls have validation errors set?

Wpf Solutions


Solution 1 - Wpf

This post was extremely helpful. Thanks to all who contributed. Here is a LINQ version that you will either love or hate.

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

Solution 2 - Wpf

The following code (from Programming WPF book by Chris Sell & Ian Griffiths) validates all binding rules on a dependency object and its children:

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

}

You can call this in your save button click event handler like this in your page/window

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

Solution 3 - Wpf

The posted code did not work for me when using a ListBox. I rewrote it and now it works:

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

Solution 4 - Wpf

Had the same problem and tried the provided solutions. A combination of H-Man2's and skiba_k's solutions worked almost fine for me, for one exception: My Window has a TabControl. And the validation rules only get evaluated for the TabItem that is currently visible. So I replaced VisualTreeHelper by LogicalTreeHelper. Now it works.

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

Solution 5 - Wpf

In addition to the great LINQ-implementation of Dean, I had fun wrapping the code into an extension for DependencyObjects:

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

This makes it extremely nice considering reuseablity.

Solution 6 - Wpf

I would offer a small optimization.

If you do this many times over the same controls, you can add the above code to keep a list of controls that actually have validation rules. Then whenever you need to check for validity, only go over those controls, instead of the whole visual tree. This would prove to be much better if you have many such controls.

Solution 7 - Wpf

Here is a library for form validation in WPF. Nuget package here.

Sample:

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

The idea is that we define a validation scope via the attached property telling it what input controls to track. Then we can do:

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

Solution 8 - Wpf

You can iterate over all your controls tree recursively and check the attached property Validation.HasErrorProperty, then focus on the first one you find in it.

you can also use many already-written solutions you can check this thread for an example and more information

Solution 9 - Wpf

In answer form aogan, instead of explicitly iterate through validation rules, better just invoke 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;
	}
}

Solution 10 - Wpf

You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It shows how to use validation in WPF and how to control the Save button when validation errors exists.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevin BerridgeView Question on Stackoverflow
Solution 1 - WpfDeanView Answer on Stackoverflow
Solution 2 - WpfaoganView Answer on Stackoverflow
Solution 3 - WpfH-Man2View Answer on Stackoverflow
Solution 4 - Wpfuser195268View Answer on Stackoverflow
Solution 5 - WpfMatthias LoerkeView Answer on Stackoverflow
Solution 6 - WpfspriteView Answer on Stackoverflow
Solution 7 - WpfJohan LarssonView Answer on Stackoverflow
Solution 8 - Wpfuser21243View Answer on Stackoverflow
Solution 9 - Wpfskiba_kView Answer on Stackoverflow
Solution 10 - WpfjbeView Answer on Stackoverflow