Reputation: 1161
I have implemented the INotifyDataErrorInfo on my models. But I cant seem to use it when needed. For Example. It should not validate on errors on startup or as I am typing. Only when clicking on a button (save).
I have currently this in my XAML:
<TextBox Text="{Binding Car.Model, ValidatesOnNotifyDataErrors=False, UpdateSourceTrigger=Explicit}"/>
And in my ViewModel under the SaveCommand:
Car.Validate();
if (Car.HasErrors)
{
return;
}
//else save
My Model looks like this:
Public class Car:ValidateModelBase
{
private string _model;
[Required (ErrorMessage ="This field is required")]
public string Model
{
get { return _model; }
set { _model= value; RaisePropertyChanged(); }
}
}
And then my implementation of ValidateModelBase:
public class ValidateModelBase: INotifyDataErrorInfo, INotifyPropertyChanged
{
private ConcurrentDictionary<string, List<string>> _errors =
new ConcurrentDictionary<string, List<string>>();
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
ValidateAsync();
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return null;
}
List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);
return errorsForName;
}
public bool HasErrors
{
get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
}
public Task ValidateAsync()
{
return Task.Run(() => Validate());
}
private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, validationResults, true);
foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();
if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}
_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
}
The thing is its working as it normally should, but the textbox is already red with rquired when Window is opened. I want it to ignore validation on startup and only validate the moment I click Save. When clicking save, it would validate and see if there is any errors. When there is errors, the validation is set (now it should be marked in red) and the window stays open. How can I achieve this.
Upvotes: 1
Views: 512
Reputation: 25623
If your model should only validate when saving, I would have your model implement IEditableObject
and ensure that BeginEdit
is called prior to changes being made, and call EndEdit
prior to committing those changes (i.e., saving).
Have your base class track whether or not it's in 'edit mode', and if it is, suppress any validation. When EndEdit
is called, allow validation again and fire ErrorsChanged
. It's up to you to decide how to handle CancelEdit
.
Some controls, like data grids, have built-in support for IEditableObject
and will call BeginEdit
when you begin editing a row, and EndEdit
when committing a row. It's a pretty useful interface.
Upvotes: 0