Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I handle a Validation.Error in my ViewModel instead of my View's code behind?

I'm trying to get WPF validation to work within the MVVM pattern.

In my View, I can validate a TextBox like this which gets handled by the code-behind method "HandleError", which works fine:

<TextBox Width="200"
         Validation.Error="HandleError">
    <TextBox.Text>
        <Binding Path="FirstName"
             NotifyOnValidationError="True"
             Mode="TwoWay">
            <Binding.ValidationRules>
                <validators:DataTypeLineIsValid/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

However, I would like to handle the validation in my ViewModel via a DelegateCommand but when I try it with the following code, I get the explicit error "'{Binding HandleErrorCommand}' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid."

Are there any workaround for this so that we can handle validations within a MVVM pattern?

View:

<TextBox Width="200"
         Validation.Error="{Binding HandleErrorCommand}">
    <TextBox.Text>
        <Binding Path="FirstName"
             NotifyOnValidationError="True"
             Mode="TwoWay">
            <Binding.ValidationRules>
                <validators:DataTypeLineIsValid/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

ViewModel:

#region DelegateCommand: HandleError
private DelegateCommand handleErrorCommand;

public ICommand HandleErrorCommand
{
    get
    {
        if (handleErrorCommand == null)
        {
            handleErrorCommand = new DelegateCommand(HandleError, CanHandleError);
        }
        return handleErrorCommand;
    }
}

private void HandleError()
{
    MessageBox.Show("in view model");
}

private bool CanHandleError()
{
    return true;
}
#endregion
like image 643
Edward Tanguay Avatar asked May 28 '09 15:05

Edward Tanguay


2 Answers

I don't know if this will help you, but I'll offer it all the same.

Also, I'm using Silverlight, not WPF.

I don't specify any validation in my Views, neither in the code behind nor the xaml. My View has only data bindings to properties on the ViewModel.

All my error checking/validation is handled by the ViewModel. When I encounter an error, I set a ErrorMessage property, which is bound to the view as well. The ErrorMessage textblock (in the view) has a value converter which hides it if the error is null or empty.

Doing things this way makes it easy to unit test input validation.

like image 78
Matt Brunell Avatar answered Nov 06 '22 00:11

Matt Brunell


Here's a way to do this using Expression Blend 3 behaviors. I wrote a ValidationErrorEventTrigger because the built-in EventTrigger doesn't work with attached events.

View:

<TextBox>
<i:Interaction.Triggers>
    <MVVMBehaviors:ValidationErrorEventTrigger>
        <MVVMBehaviors:ExecuteCommandAction TargetCommand="HandleErrorCommand" />
    </MVVMBehaviors:ValidationErrorEventTrigger>
</i:Interaction.Triggers>
<TextBox.Text>
    <Binding Path="FirstName"
             Mode="TwoWay"
             NotifyOnValidationError="True">
        <Binding.ValidationRules>
            <ExceptionValidationRule />
        </Binding.ValidationRules>
    </Binding>
</TextBox.Text>

ViewModel: (could be unchanged, but here's a look at how I dug into the validation arguments to find the error message when using the exception validation rule)

    public ICommand HandleErrorCommand
    {
        get
        {
            if (_handleErrorCommand == null)
                _handleErrorCommand = new RelayCommand<object>(param => OnDisplayError(param));
            return _handleErrorCommand;
        }
    }

    private void OnDisplayError(object param)
    {
        string message = "Error!";
        var errorArgs = param as ValidationErrorEventArgs;
        if (errorArgs != null)
        {
            var exception = errorArgs.Error.Exception;
            while (exception != null)
            {
                message = exception.Message;
                exception = exception.InnerException;
            }
        }
        Status = message;
    }

ValidationErrorEventTrigger:

public class ValidationErrorEventTrigger : EventTriggerBase<DependencyObject>
{
    protected override void OnAttached()
    {
        Behavior behavior = base.AssociatedObject as Behavior;
        FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;

        if (behavior != null)
        {
            associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
        }
        if (associatedElement == null)
        {
            throw new ArgumentException("Validation Error Event trigger can only be associated to framework elements");
        }
        associatedElement.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.OnValidationError));
    }
    void OnValidationError(object sender, RoutedEventArgs args)
    {
        base.OnEvent(args);
    }
    protected override string GetEventName()
    {
        return Validation.ErrorEvent.Name;
    }
}
like image 43
Ben Reierson Avatar answered Nov 06 '22 02:11

Ben Reierson