WPF: force all the validation rules attached to an object to be executed

Print Content | More

I’m working on a WPF application that have some validation performed through the standard ValidationRule system employed by WPF at the UI level.

Recently I needed a way to validate (that is check the state and force the execution of any validation rule) full portions of the Visual Tree. The idea was to call a function that, given a DependencyObject would traverse the tree and force the execution of any validation rule attached to a any Binding we can find.

The first step is then find out how to get all the Dependency Properties that an object exposes; I’ve ‘googled’ a bit and found out many solution that use the DependencyObject.GetLocalValueEnumerator() method...unfortunately this one is not useful in my case, cause it doesn’t return values set in templates.

In the end I was forced to use reflection to get the dependency properties of an object and for performances reasons I decided to cache the result of those reflection calls in a dictionary.

The rest of the function is straightforward and it’s a modified version of a routine you can find on the ‘Programming WPF’ book. To force the validation to be performed instead of calling the Validation.MarkInvalid() function I choose to get the binding expression and call it’s update methods which guarantee that the associated error collection of an object is cleared if the state becomes valid again.

Here’s some code:

public static class Validator
{
   private static Dictionary<Type, List<DependencyProperty>> PropertiesReflectionChace = new Dictionary<Type, List<DependencyProperty>>();
 
   private static List<DependencyProperty> GetDPs(Type t)
   {
      if (PropertiesReflectionChace.ContainsKey(t))
         return PropertiesReflectionChace[t];
      FieldInfo[] properties = t.GetFields(BindingFlags.Public | BindingFlags.GetProperty |
           BindingFlags.Static | BindingFlags.FlattenHierarchy);
      // we cycle and store only the dependency properties
      List<DependencyProperty> dps = new List<DependencyProperty>();
 
      foreach (FieldInfo field in properties)
         if (field.FieldType == typeof(DependencyProperty))
            dps.Add((DependencyProperty)field.GetValue(null));
      PropertiesReflectionChace.Add(t, dps);
 
      return dps;
   }
 
   /// <summary>
   /// checks all the validation rule associated with objects,
   /// forces the binding to execute all their validation rules
   /// </summary>
   /// <param name="parent"></param>
   /// <returns></returns>
   public static bool IsValid(DependencyObject parent)
   {
      // Validate all the bindings on the parent
      bool valid = true;
      // get the list of all the dependency properties, we can use a level of caching to avoid to use reflection
      // more than one time for each object
      foreach (DependencyProperty dp in GetDPs(parent.GetType()))
      {
         if (BindingOperations.IsDataBound(parent, dp))
         {
            Binding binding = BindingOperations.GetBinding(parent, dp);
            if (binding.ValidationRules.Count > 0)
            {
               BindingExpression expression = BindingOperations.GetBindingExpression(parent, dp);
               switch (binding.Mode)
               {
                  case BindingMode.OneTime:
                  case BindingMode.OneWay:
                     expression.UpdateTarget();
                     break;
                  default:
                     expression.UpdateSource();
                     break;
               }
               if (expression.HasError) 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;
   }
}


Binding, Error, Validation, Validation rule, Wpf

8 comments

Related Post

  1. #1 da Guardian - Saturday March 2010 alle 01:15

    I haven't found any way to execute the validation rules without calling an Update on the BindingExpression yet.. Btw, can you guys email me the fixes you made to this code? I'll appreciate it.

  2. #2 da El Guapo - Saturday March 2010 alle 01:15

    Nevermind above comment, I think that is wrong about Binding mode. In general there is a problem with this approach - calling UpdateSource/Target to trigger the binding is having a lot of bad side effects. I want to trigger validation without actually updating the value on either side. Is it possible?

  3. #3 da El Guapo - Saturday March 2010 alle 01:15

    You did not account for the fact that validation can be on the model through IDataErrorInfo and not using validation rules. It wasn't hard to fix though.

  4. #4 da El Guapo - Saturday March 2010 alle 01:15

    Another problem - you must UpdateTarget when BindingMode = BindingMode.Default!! Do not UpdateSource! This caused a very subtle bug (needless db roundtrips when using a masked input control).

  5. #5 da Rob - Saturday March 2010 alle 01:15

    Hi Guardian Great post: it's exactly what I need. Have you thought about putting this code up on the MSDN code gallery? It's not as heavyweight as starting a codeplex project, but it will let people log issues against the code and submit patches for review. I think your code is valuable enough for that! Cheers - Rob

  6. #6 da Roman - Saturday March 2010 alle 01:15

    Hi, your solution does not handle multi bindings! but this was not so difficult to extend... Roman

  7. #7 da jlspublic - Saturday March 2010 alle 01:15

    There seem to be two approaches out there: 1) Using GetLocalValueEnumerator 2) Reflection (your approach) Approach 1 doesn't handle templates correctly and approach 2 doesn't handle attached properties correctly. As a result, I have found that I need to combine the two. I always use approach 1 and, if the control is from a template, then I also use approach 2. There is still a chance that attached properties in templates will be missed but, God help us if your templates are getting that complicated. You can determine if a control is from a template by checking whether its TemplatedParent property is set.

  8. #8 da Evgeny - Tuesday April 2010 alle 03:40

    Thank You Very Much

    Very Helpfully

All fields are required and you must provide valid data in order to be able to comment on this post.


(will not be published)
(es: http://www.mysite.com)