views:

37

answers:

1

In my application I have numerical (double or int) ViewModel properties that are bound to TextBoxes. The ViewModel implements IDataErrorInfo to check if the values entered fall within acceptable ranges for the 'business logic' (e.g. height can't be a negative value). I have a number of TextBoxes per page and have a button (think 'next' in a wizard) thats enabled property is bound to a ViewModel boolean that specifies whether there are any errors on the page as a whole. The enable/disable state of the button is properly updated with valid/invalid values according to the IDataErrorInfo rules I've written.

However, there is no way to let my viewmodel know when an exception has been thrown because an input value does not convert (i.e. "12bd39" is not a valid double) and as a result in the case of conversion exceptions my 'next' button will remain enabled despite bad input. The GUI however properly reflects the error with an adorner because of my binding:

<TextBox Text="{Binding Temperature, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>

How can I let the view know that a 'ValidatesOnExceptions' style error has occured. Josh Smith's take here seems to rely on making every ViewModel property a string and rolling your own exception checking which seems like a lot of additional work. I additionally began looking into Karl Shifflett's implementation here, but I cannot seem to capture the routed event I would expect when putting this code into the view's codebehind file:

public ViewClass()

{ this.InitializeComponent(); this.AddHandler(System.Windows.Controls.Validation.ErrorEvent, new RoutedEventHandler(ValidationErrorHandler)); }

    private void ValidationErrorHandler(object sender, RoutedEventArgs e)
    {
        var blah = e as System.Windows.Controls.ValidationErrorEventArgs;
        if (blah.Action == ValidationErrorEventAction.Added)
        {
        }
        else if (blah.Action == ValidationErrorEventAction.Removed)
        {    
        }
    }

Silverlight appears to have an event that you can subscribe too, but I cannot find the exact equivalent in WPF (3.5). Any help is appreciated!

A: 

I have a base class for the View that subscribes to Validation.ErrorEvent routed event

public class MVVMViewBase : UserControl
    {
        private RoutedEventHandler _errorEventRoutedEventHandler;
        public MVVMViewBase()
        {
            Loaded += (s, e) =>
                {
                    _errorEventRoutedEventHandler = new RoutedEventHandler(ExceptionValidationErrorHandler);
                    AddHandler(Validation.ErrorEvent, _errorEventRoutedEventHandler);
                };

            Unloaded += (s, e) =>
                {
                    if (_errorEventRoutedEventHandler != null)
                    {
                        RemoveHandler(Validation.ErrorEvent, _errorEventRoutedEventHandler);
                        _errorEventRoutedEventHandler = null;
                    }
                };
        }

        private void ExceptionValidationErrorHandler(object sender, RoutedEventArgs e)
        {
            ValidationErrorEventArgs args = (ValidationErrorEventArgs) e;
            if (!(args.Error.RuleInError is IUiValidation)) return;

            DataErrorInfoViewModelBase viewModelBase = DataContext as DataErrorInfoViewModelBase;
            if(viewModelBase == null) return;

            BindingExpression bindingExpression = (BindingExpression) args.Error.BindingInError;
            string dataItemName = bindingExpression.DataItem.ToString();
            string propertyName = bindingExpression.ParentBinding.Path.Path;

            e.Handled = true;
            if(args.Action == ValidationErrorEventAction.Removed)
            {
                viewModelBase.RemoveUIValidationError(new UiValidationError(dataItemName, propertyName, null));
                return;
            }

            string validationErrorText = string.Empty;
            foreach(ValidationError validationError in Validation.GetErrors((DependencyObject) args.OriginalSource))
            {
                if (validationError.RuleInError is IUiValidation)
                {
                    validationErrorText = validationError.ErrorContent.ToString();
                }
            }
            viewModelBase.AddUIValidationError(new UiValidationError(dataItemName, propertyName, validationErrorText));
        }
    }

and a base class for the ViewModel = DataErrorInfoViewModelBase that is informed by AddUIValidationError and RemoveUIValidationError

Also all my ValidationRule classes implement IUiValidation which is used just to mark the class as taking part of the UI errors propagation(no members). (you can use an attribute for the same purpose).

Claudiu