How can I handle a Validation.Error in my ViewMode

2019-01-15 02:10发布

问题:

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

回答1:

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.



回答2:

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;
    }
}