Home > Validation > Forwarding the Result of WPF Validation in MVVM

Forwarding the Result of WPF Validation in MVVM


A question that is frequently raised in connection with MVVM design is how one can access the result of WPF Validation (Validation.HasError) from the ViewModel. There is a way, but before I start explaining it, I’d like to ask an important question:

Why Would You Want to Do it?

Normally you would say: actually the ViewModel is responsible for validating the user input. So, if the input is invalid, the ViewModel should already know about it, isn’t it? Well, partially. There are some cases were the user input never reaches the ViewModel: when what the user entered cannot be converted to the correct data type, e.g. with date values or numbers. Catching these conversion errors in the ViewModel requires that you wrap each and every property of the data model that has one of these data types into a ViewModel property of type string, which bloats the ViewModel unnecessarily, and creates exactly the kind of glue code one tries to avoid by using the MVVM pattern in the first place.

One possibility to work around this is to create general, reusable ValidationRules for type checking and assign them to the Bindings. But then the ViewModel would have to know about the results of these checks in order to disable or enable commands or make other decisions based on the validity of its data. This would be the use case for the following technique:

How to Do it

The trick is to create a Sticky Property (which is a special case of an attached property) which, different from Validation.HasError, allows data bindings with direction to the source:

Public Shared ReadOnly MVVMHasErrorProperty As DependencyProperty = _
    DependencyProperty.RegisterAttached("MVVMHasError", GetType(Boolean), GetType(Window1), _
    New FrameworkPropertyMetadata(False, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, _
    Nothing, AddressOf CoerceMVVMHasError))
Public Shared Function GetMVVMHasError(ByVal d As DependencyObject) As Boolean
    Return d.GetValue(MVVMHasErrorProperty)
End Function
Public Shared Sub SetMVVMHasError(ByVal d As DependencyObject, ByVal value As Boolean)
    d.SetValue(MVVMHasErrorProperty, value)
End Sub

 

In the metadata of this property, I set a flag that tells WPF that this property expects to forward its value to the source, so we don’t have to set the Binding Mode on the bindings in XAML.

In order to reflect the value of Validation.HasError, we need to set up an event that fires whenever the value of this property changes. In order to find out that we need this event, the CoerceValue callback of the property checks whether the property is data bound:

Private Shared Function CoerceMVVMHasError(ByVal d As DependencyObject, ByVal baseValue As Object) As Object
    Dim result As Boolean = baseValue
    If BindingOperations.IsDataBound(d, MVVMHasErrorProperty) Then
        If GetHasErrorDescriptor(d) Is Nothing Then
            Dim desc As System.ComponentModel.DependencyPropertyDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(System.Windows.Controls.Validation.HasErrorProperty, d.GetType)
            desc.AddValueChanged(d, AddressOf OnHasErrorChanged)
            SetHasErrorDescriptor(d, desc)
            result = System.Windows.Controls.Validation.GetHasError(d)
        End If
    Else
        If GetHasErrorDescriptor(d) IsNot Nothing Then
            Dim desc As System.ComponentModel.DependencyPropertyDescriptor = GetHasErrorDescriptor(d)
            desc.RemoveValueChanged(d, AddressOf OnHasErrorChanged)
            SetHasErrorDescriptor(d, Nothing)
        End If
    End If
    Return result
End Function
Private Shared Sub OnHasErrorChanged(ByVal sender As Object, ByVal e As EventArgs)
    Dim d As DependencyObject = TryCast(sender, DependencyObject)
    If d IsNot Nothing Then
        d.SetValue(MVVMHasErrorProperty, d.GetValue(Validation.HasErrorProperty))
    End If
End Sub

Private Shared ReadOnly HasErrorDescriptorProperty As DependencyProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor", GetType(System.ComponentModel.DependencyPropertyDescriptor), GetType(Window1))
Private Shared Function GetHasErrorDescriptor(ByVal d As DependencyObject) As System.ComponentModel.DependencyPropertyDescriptor
    Return d.GetValue(HasErrorDescriptorProperty)
End Function
Private Shared Sub SetHasErrorDescriptor(ByVal d As DependencyObject, ByVal value As System.ComponentModel.DependencyPropertyDescriptor)
    d.SetValue(HasErrorDescriptorProperty, value)
End Sub

And that’s it… Notice that the CoerceValue callback uses a private DependencyProperty in order to store the DependencyPropertyDescriptor that is used to set up the change event. This has two purposes: it allows to remove the notification conveniently, and more important, it serves as a flag in order to see whether the notification is already in place or still needs to be set up.

You can download an example for this technique here. (You will have to change the extension to .zip in order to extract the contents).

Credits

I found the information about how to monitor changes in any object’s DependencyProperties in this article by Charles Petzold.

About these ads
Categories: Validation Tags: ,
  1. zp
    May 23, 2013 at 08:53

    looked for something like that in c# but that was good enough

    here is the C# version for those interested:

    //1.
    public static readonly DependencyProperty MVVMHasErrorProperty=
    DependencyProperty.RegisterAttached(“MVVMHasError”,typeof(bool),typeof(ProtocolSettingsLayout),new FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,null,CoerceMVVMHasError));

    //2.
    public static bool GetMVVMHasError(DependencyObject d)
    {
    return (bool)d.GetValue(MVVMHasErrorProperty);
    }

    //3.
    public static void SetMVVMHasError(DependencyObject d, bool value)
    {
    d.SetValue(MVVMHasErrorProperty, value);
    }

    //4.
    private static object CoerceMVVMHasError(DependencyObject d,Object baseValue)
    {
    bool ret=(bool)baseValue ;
    if(BindingOperations.IsDataBound(d,MVVMHasErrorProperty))
    {
    if(GetHasErrorDescriptor(d)==null)
    {
    DependencyPropertyDescriptor desc=DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType());
    desc.AddValueChanged(d,OnHasErrorChanged);
    SetHasErrorDescriptor(d, desc);
    ret = System.Windows.Controls.Validation.GetHasError(d);
    }
    }
    else
    {
    if(GetHasErrorDescriptor(d)!=null)
    {
    System.ComponentModel.DependencyPropertyDescriptor desc= GetHasErrorDescriptor(d);
    desc.RemoveValueChanged(d, OnHasErrorChanged);
    SetHasErrorDescriptor(d, null);
    }
    }
    return ret;
    }

    //5.
    private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached(“HasErrorDescriptor”, typeof(DependencyPropertyDescriptor), typeof(ProtocolSettingsLayout));

    //6.
    private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
    {
    var ret=d.GetValue(HasErrorDescriptorProperty);
    return ret as DependencyPropertyDescriptor;
    }

    //7.
    private static void OnHasErrorChanged(object sender, EventArgs e)
    {
    DependencyObject d = sender as DependencyObject;
    if (d != null)
    {
    d.SetValue(MVVMHasErrorProperty, d.GetValue(Validation.HasErrorProperty));
    }
    }

    //8.
    private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
    {
    var ret=d.GetValue(HasErrorDescriptorProperty);
    d.SetValue(HasErrorDescriptorProperty, value);
    }

    z.p

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: