I hadn't really experimented with ValidationRules in WPF before, but I ran into a couple of cases lately where they seem useful. In one case I have a custom converter which converts a simple grammar for hours and minutes into a TimeSpan. There are many inputs which are invalid, in those cases my converter simply throws an ArgumentException. In order to show the validation status in the UI, I tried to use ExceptionValidationRule like this:
<TextBox.Text> <Binding> <Binding.Converter> <local:MyConverter /> </Binding.Converter> <Binding.ValidationRules> <ExceptionValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text>
Having read the following in the "Data Binding Overview" i assumed this would work.
If the binding has an ExceptionValidationRule associated with it and an exception is thrown during step 3 or 4, the binding engine checks to see if there is a UpdateSourceExceptionFilter. You have the option to use the UpdateSourceExceptionFilter callback to provide a custom handler for handling exceptions. If an UpdateSourceExceptionFilter is not specified on the Binding, the binding engine creates a ValidationError with the exception and adds it to the Validation.Errors collection of the bound element.
Step 3 involves calling the Converter (if one exists). While this would appear to indicate that an exception thrown by a converter would be caught if we apply an ExceptionValidationRule, what actually happens is described by the documentation for IValueConverter:
The data binding engine does not catch exceptions that are thrown by a user-supplied converter. Any exception that is thrown by the Convert method, or any uncaught exceptions that are thrown by methods that the Convert method calls, are treated as run-time errors. Handle anticipated problems by returning DependencyProperty.UnsetValue.
I have a GenericConverter abstract base class that I use to implement my converters, this gives me some type sanity at implementation time:
public abstract class GenericConverter<TIn, TOut, TParam> : IValueConverter
In light of the fact that converters should never throw exceptions I have reshaped Convert and ConvertBack into the Try pattern. GenericConverter's Convert implementation looks like this:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { TOut result; if (TryConvert((TIn)value, out result, (TParam)parameter, culture)) return result; else return value; }
TryConvert would then be overridden by a converter which supports conversion in this direction.
public virtual bool TryConvert(TIn value, out TOut result, TParam parameter, CultureInfo culture)
The problem, as highlighted in yellow, is that I'm still unsure what to do when the conversion fails. In the implementation above, I return the original value. The IValueConverter documentation doesn't say to do this but it is done by the sample implementation. In some trivial cases including the sample given in the documentation, this is enough to alert the ExceptionValidationRule that the conversion has failed. The answer to why this only works in some cases is in the IValueConverter documentation:
The data binding engine does not catch exceptions that are thrown by a user-supplied converter
The translation is that exceptions thrown by built in conversions are caught. If a user-supplied converter returns an inappropriate type (something for which the source or target in question is not assignable from) the binding system will attempt to locate and use a built in conversion, if that fails, the ExceptionValidationRule will be applied. If there is no built-in conversion the result of our custom converter is thrown away and the default value is set. That is a problem, what if we don't want a default value?
Instead of returning the original value, the best I can think to do for now is what the documentation says: return DependencyProperty.UnsetValue. This will prevent an invalid value from being assigned to the source or target, but it will not raise any validation errors. The best validation solution I can come up with for now is to implement a custom ValidationRule and run the conversion twice.
This is easy to add to the GenericConverter, but running the conversion twice just feels wrong! I'd also love to know why ValidationRule is an abstract class rather than an interface.
public abstract class GenericConverter<TIn, TOut, TParam> : ValidationRule, IValueConverter { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { return new ValidationResult( ConvertBack(value, typeof(TIn), null, cultureInfo) != DependencyProperty.UnsetValue, "[generic error message]"); } }
I would be perfectly happy if exceptions from user-supplied converters were treated in the same way as built in conversions, but for now it's yet another WPF design decision that I don't fully understand.
