Using data bound properties in the WPF ValidationRule
I recently needed to use to make sure that a number entered into a text box was less than the value of a property on my view model.
Arguably, this sort of business logic might be better off inside my view model; possibly let the property get set regardless, then verify and do something with IDataErrorInfo to notify the GUI if it's wrong before updating my model data if required. But in this case I am binding straight onto a property which is held inside a class I cannot modify, so I decided to try and use WPF's built-in binding validation mechanism.
So far so good; off I went deriving from ValidationRule and overriding Validate, but I soon came a cropper trying to access the view model. The problem is that ValidationRule isn't a DependencyObject so I can't add a dependency property to bind my view model to, and the specifics of my usage (this TextBox is dynamically generated in a DataGrid in code, but that's for another blog post), meant that the solution suggested here didn't help me, the binding fails at the point the TextBox is created so never gets updated when it's finally set.
So after a bit of a dig about in the MSDN I found that if you set
ValidationStop = ValidationStep.UpdatedValue
on the ValidationRule the value parameter in the Validate method is the BindingExpression itself, from which you can query DataItem to get the binding source object that the expression uses (which in my case is my view model).
Great stuff, but now how do I get my actual value from the BindingExpression? I can't use SourceValue, or SourceItem, because even though under the debugger they show my value, they are both internal.
So along comes .Net 4.5 to the rescue with a new ResolvedSource property for BindingExpression which will return the binding source object, in this case the TextBox text property which means I can do something like this:
public class NumberIsGreaterThanVmValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { var result = new ValidationResult(true, null); var vm = ((BindingExpression) value).DataItem as MyViewModel; var val = (int)((BindingExpression) value).ResolvedSource; if (val.Value < vm.ComparisonValue) result = new ValidationResult(false, "Number is less than allowed value"); return result; } }