I'm using the INotifyDataErrorInfo interface to implement a general MVVM validation mechanism. I'm implementing the interface by calling OnValidate instead of OnPropertyChanged:
public void OnValidate(dynamic value, [CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
Validate(propertyName, value);
}
In the Validate Method I'm generating the validation errors, add them to a Dictionary and raise the ErrorsChanged event if a validation error was found or cleared:
if (entry.Validate(strValue, out errorNumber, out errorString) == false)
{
_validationErrors[propertyName] = new List<string> {errorString};
RaiseErrorsChanged(propertyName);
}
else if (_validationErrors.ContainsKey(propertyName))
{
_validationErrors.Remove(propertyName);
RaiseErrorsChanged(propertyName);
}
The HasErrors property is implemented by looking at the errors dictionary:
public bool HasErrors
{
get { return _validationErrors.Any(kv => kv.Value != null
&& kv.Value.Count > 0); }
}
To prevent the save button from being enabled when there is a validation error - The save command canExecuteMethod looks at the HasErrors property:
private bool IsSaveEnabled()
{
return HasErrors == false;
}
Everything works fine except the case where I'm having binding errors - if the binded value is (for example) an integer a non integer is entered - the textbox's ErrorContent is updated with an error string: "Value 'something' could not be converted". But the INotifyDataErrorInfo mechanism is not updated about this. The HasErrors remains false and Save is enabled although there is an error in the view. I would like to find a way to propagate the binding exception to the INotifyDataErrorInfo mechanism so I would be able to:
- Disable the Save button (must).
- Change the validation error message to a more meaningful error string (nice to have).
I would like to find a general MVVM solution without adding code behind in the view.
Thank you for the help
the string int case doesn't work with MVVM because your viewmodel doesn't get any information because of the binding exception.
I see two ways to get the validation you want:
Btw I use the second approach because I have to :) but the first will always work and seems easier to me.
Here is the solution that I have found. It makes the INotifyDataErrorInfo behave correctly in the ViewModel (When there is any validation error – the HasError is true), and it allows adding validation errors from the viewModel. Other than this, it does not require changes in the view, changes in the binding or converters.
This solution involves:
Adding a custom validation rule – Validation Entity which does the actual validation and raises an event when the validation changes:
Adding a base user control which keeps a list of the active textboxs, and adds the validation rule to each textbox binding: This is the code at the user control base:
When the ValidationChange event happens – the view is called to be notified about the validation error:
The ViewModel base keeps two lists:
When adding a validation error from the view – the _notifyvalidationErrors is updated with an empty value (just to denote there is a validation error) the error string is not added to the _notifyvalidationErrors. If we add it to there we would get the validation error string twice in the textbox ErrorContent. The validation error string is also added to _privateValidationErrors (Because we want to be able to keep it at the viewmodel) This is the code at the ViewModel base:
The INotifyDataErrorInfo implementation in the view:
The user has an option to add validation errors from the view by calling the ViewModelBase AddValidationError and ClearValidationError methods.
The view can get a list of all current validation errors from the ViewModel base by calling the GetValidationErrors and GetValidationErrorsString methods.
Set
ValidatesOnExceptions="True"
In your Binding expression.