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