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