views:

1799

answers:

4

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

Events are tricky with MVVM, but not impossible. The typical method is to use an attached behavior to handle this. Caliburn has a usable solution for this in the Message.Attach behavior.

wekempf
+3  A: 

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.

Matt Brunell
Nice to know, this was the direction I was going to take since validation in WPF currently doesn't seem as straight-forward or feature-full as I thought it would be.
Edward Tanguay
A: 

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;
    }
}
Ben Reierson
A: 

I gave up on the built-in error validation and opted for a just exposing an error property in my ViewModel.

Here's an example of simple error validation in WPF MVVM, similar to Matt Brunell's answer. You can download a simple project using this technique at the link above

Jeffrey Knight