强制INotifyDataErrorInfovalidation

我完全按照以下链接中的描述实现了INotifyDataErrorInfo:

Easy MVVM Example With INotifyPropertyChanged And INotifyDataErrorInfo

我有一个TextBox ,它绑定到我的模型中的字符串属性。

XAML

  

模型

 private string _fullName; public string FullName { get { return _fullName; } set { // Set raises OnPropertyChanged Set(ref _fullName, value); if (string.IsNullOrWhiteSpace(_fullName)) AddError(nameof(FullName), "Name required"); else RemoveError(nameof(FullName)); } } 

INotifyDataError代码

 private Dictionary<string, List> _errors = new Dictionary<string, List>(); public event EventHandler ErrorsChanged; // get errors by property public IEnumerable GetErrors(string propertyName) { if (_errors.ContainsKey(propertyName)) return _errors[propertyName]; return null; } public bool HasErrors => _errors.Count > 0; // object is valid public bool IsValid => !HasErrors; public void AddError(string propertyName, string error) { // Add error to list _errors[propertyName] = new List() { error }; NotifyErrorsChanged(propertyName); } public void RemoveError(string propertyName) { // remove error if (_errors.ContainsKey(propertyName)) _errors.Remove(propertyName); NotifyErrorsChanged(propertyName); } public void NotifyErrorsChanged(string propertyName) { // Notify if (ErrorsChanged != null) ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } 

现在这一切都运行正常,但只有在我的TextBox中输入内容时才会validation。 我想要一些方法来按需validation,甚至没有触摸文本框,比如按一下按钮。

我已尝试按照此问题中的描述为我的所有属性提升PropertyChanged,但它没有检测到错误。 我不知何故需要调用我的属性设置器,以便可以检测到错误。 我正在寻找一个MVVM解决方案。

你使用的INotifyDataErrorInfo实现有点瑕疵恕我直言。 它依赖于附加到对象的状态(列表)中保存的错误。 存储状态的问题有时在移动的世界中,您没有机会在需要时更新它。 这是另一个MVVM实现,它不依赖于存储状态,而是动态计算错误状态。

由于您需要将validation代码放在中央GetErrors方法中(您可以创建从此中心方法调用的每个属性validation方法),而不是在属性设置器中,因此处理的方式有所不同。

 public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo { public event PropertyChangedEventHandler PropertyChanged; public event EventHandler ErrorsChanged; public bool HasErrors { get { return GetErrors(null).OfType().Any(); } } public virtual void ForceValidation() { OnPropertyChanged(null); } public virtual IEnumerable GetErrors([CallerMemberName] string propertyName = null) { return Enumerable.Empty(); } 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的魅力就像这样:

   

(UpdateSourceTrigger是可选的,如果你不使用它,它只会在焦点丢失时起作用)。

使用此MVVM基类,您不必强制进行任何validation。 但是,如果你需要它,我在ModelBase中添加了一个ForceValidation示例方法应该可以工作(我已经测试了它,例如像_name的成员值,如果没有通过公共setter就会被更改)。

最好的办法是使用中继命令界面。 看看这个:

 public class RelayCommand : ICommand { Action _TargetExecuteMethod; Func _TargetCanExecuteMethod; public RelayCommand(Action executeMethod) { _TargetExecuteMethod = executeMethod; } public RelayCommand(Action executeMethod, Func canExecuteMethod) { _TargetExecuteMethod = executeMethod; _TargetCanExecuteMethod = canExecuteMethod; } public void RaiseCanExecuteChanged() { CanExecuteChanged(this, EventArgs.Empty); } #region ICommand Members bool ICommand.CanExecute(object parameter) { if (_TargetCanExecuteMethod != null) { return _TargetCanExecuteMethod(); } if (_TargetExecuteMethod != null) { return true; } return false; } public event EventHandler CanExecuteChanged = delegate { }; void ICommand.Execute(object parameter) { if (_TargetExecuteMethod != null) { _TargetExecuteMethod(); } } #endregion } 

您可以在视图模型中声明此relay命令,如:

 public RelayCommand SaveCommand { get; private set; } 

现在,除了使用OnSaveCanSave方法注册SaveCommand CanSave ,由于您从INotifyDataErrorInfo扩展,您也可以在构造函数中注册ErrorsChanged

 public YourViewModel() { SaveCommand = new RelayCommand(OnSave, CanSave); ErrorsChanged += RaiseCanExecuteChanged; } 

你需要这些方法:

 private void RaiseCanExecuteChanged(object sender, EventArgs e) { SaveCommand.RaiseCanExecuteChanged(); } public bool CanSave() { return !this.HasErrors; } private void OnSave() { //Your save logic here. } 

此外,每次调用PropertyChanged ,您都可以调用此validation方法:

  private void ValidateProperty(string propertyName, T value) { var results = new List(); ValidationContext context = new ValidationContext(this); context.MemberName = propertyName; Validator.TryValidateProperty(value, context, results); if (results.Any()) { _errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); } else { _errors.Remove(propertyName); } ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } 

使用此设置,如果您的viewmodel都从INotifyPropertyChangedINotifyDataErrorInfo (或从这两个扩展的基类)扩展,当您将按钮绑定到上面的SaveCommand ,如果存在validation错误,WPF框架将自动禁用它。

希望这可以帮助。