Ответ 1
Используемая реализация INotifyDataErrorInfo несколько ошибочна. IMHO. Он полагается на ошибки, хранящиеся в состоянии (списке), прикрепленном к объекту. Проблема с сохраненным состоянием, иногда, в движущемся мире, у вас нет возможности обновлять ее, когда захотите. Вот еще одна реализация MVVM, которая не полагается на сохраненное состояние, но вычисляет состояние ошибки "на лету".
Вещи обрабатываются по-разному, так как вам нужно поместить код проверки в центральный метод GetErrors (вы можете создать методы проверки подлинности для каждого объекта из этого центрального метода), а не в настройках свойств.
public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get
{
return GetErrors(null).OfType<object>().Any();
}
}
public virtual void ForceValidation()
{
OnPropertyChanged(null);
}
public virtual IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
return Enumerable.Empty<object>();
}
protected void OnErrorsChanged([CallerMemberName] string propertyName = null)
{
OnErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
var handler = ErrorsChanged;
if (handler != null)
{
handler(sender, e);
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(sender, e);
}
}
}
И вот два примера классов, которые демонстрируют, как его использовать:
public class Customer : ModelBase
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Name))
{
if (string.IsNullOrWhiteSpace(_name))
yield return "Name cannot be empty.";
}
}
}
public class CustomerWithAge : Customer
{
private int _age;
public int Age
{
get
{
return _age;
}
set
{
if (_age != value)
{
_age = value;
OnPropertyChanged();
}
}
}
public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
foreach (var obj in base.GetErrors(propertyName))
{
yield return obj;
}
if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Age))
{
if (_age <= 0)
yield return "Age is invalid.";
}
}
}
Он работает как шарм с простым XAML следующим образом:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" />
(UpdateSourceTrigger не является обязательным, если вы его не используете, он будет работать только тогда, когда фокус потерян).
С этим базовым классом MVVM вам не нужно принудительно выполнять какие-либо проверки. Но если вам это нужно, я добавил в ModelBase образец образца ForceValidation, который должен работать (я тестировал его, например, значение члена, например _name, которое было бы изменено без прохождения через публичный сеттер).