INotifyDataErrorInfo和绑定exception

我正在使用INotifyDataErrorInfo接口来实现一般的MVVMvalidation机制。 我通过调用OnValidate而不是OnPropertyChanged来实现接口:

public void OnValidate(dynamic value, [CallerMemberName] string propertyName = null) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); Validate(propertyName, value); } 

在validation方法中我生成validation错误,将它们添加到Dictionary并在发现或清除validation错误时引发ErrorsChanged事件:

 if (entry.Validate(strValue, out errorNumber, out errorString) == false) { _validationErrors[propertyName] = new List {errorString}; RaiseErrorsChanged(propertyName); } else if (_validationErrors.ContainsKey(propertyName)) { _validationErrors.Remove(propertyName); RaiseErrorsChanged(propertyName); } 

HasErrors属性通过查看错误字典来实现:

  public bool HasErrors { get { return _validationErrors.Any(kv => kv.Value != null && kv.Value.Count > 0); } } 

要防止在存在validation错误时启用保存按钮 – save命令canExecuteMethod查看HasErrors属性:

 private bool IsSaveEnabled() { return HasErrors == false; } 

一切正常,除了我遇到绑定错误的情况 – 如果绑定值是(例如)输入非整数的整数 – 文本框的ErrorContent更新为错误字符串:“ 值’某事’无法转换 ”。 但是没有更新INotifyDataErrorInfo机制。 尽管视图中存在错误,但HasErrors仍然为false并且已启用“保存”。 我想找到一种方法将绑定exception传播到INotifyDataErrorInfo机制,以便我能够:

  1. 禁用“保存”按钮(必须)。
  2. 将validation错误消息更改为更有意义的错误字符串(很高兴)。

我想找到一个通用的MVVM解决方案,而不在视图中添加代码。

感谢您的帮助

string int case不适用于MVVM,因为viewmodel因绑定exception而无法获取任何信息。

我看到了两种获得所需validation的方法:

  1. 只需在viewmodel中使用字符串属性,当您必须转到模型时,只需将字符串转换为模型类型即可。
  2. 创建行为或“特殊”控件,以便视图中的输入始终“可转换”为您的viewmodel类型。

顺便说一句,我使用第二种方法,因为我必须:)但第一种方法将始终有效,对我来说似乎更容易。

ValidatesOnExceptions = “真”

在你的Binding表达式中。

这是我找到的解决方案。 它使得InotifyDataErrorInfo在ViewModel中正常运行(当存在任何validation错误时 – HasError为true),并且它允许从viewModel添加validation错误。 除此之外,它不需要更改视图,绑定或转换器中的更改。

该解决方案涉及:

  • 添加自定义validation规则。
  • 添加基本​​用户控件(所有视图必须从中派生)。
  • 在ViewModel基础中添加一些代码。

添加自定义validation规则 – validation实体,它执行实际validation并在validation更改时引发事件:

 class ValidationEntity : ValidationRule { public string Key { get; set; } public string BaseName = "Base"; public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { var fullPropertyName = BaseName + "." + Key; ValidationEntry entry; var validationResult = new ValidationResult(true, null); if ((entry = ValidationManager.Instance.FindValidation(fullPropertyName)) != null) { int errorNumber; string errorString; var strValue = (value != null) ? value.ToString() : string.Empty; if (entry.Validate(strValue, out errorNumber, out errorString) == false) { validationResult = new ValidationResult(false, errorString); } } if (OnValidationChanged != null) { OnValidationChanged(Key, validationResult); } return validationResult; } public event Action OnValidationChanged; } 

添加一个基本用户控件,用于保存活动文本框的列表,并将validation规则添加到每个文本框绑定中:这是用户控件库中的代码:

 private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { _textBoxes = FindAllTextBoxs(this); var vm = DataContext as ViewModelBase; if (vm != null) vm.UpdateAllValidationsEvent += OnUpdateAllValidationsEvent; foreach (var textbox in _textBoxes) { var binding = BindingOperations.GetBinding(textbox, TextBox.TextProperty); if (binding != null) { var property = binding.Path.Path; var validationEntity = new ValidationEntity {Key = property}; binding.ValidationRules.Add(validationEntity); validationEntity.ValidationChanged += OnValidationChanged; } } } private List FindAllTextBoxs(DependencyObject fe) { return FindChildren(fe); } private List FindChildren(DependencyObject dependencyObject) where T : DependencyObject { var items = new List(); if (dependencyObject is T) { items.Add(dependencyObject as T); return items; } var count = VisualTreeHelper.GetChildrenCount(dependencyObject); for (var i = 0; i < count; i++) { var child = VisualTreeHelper.GetChild(dependencyObject, i); var children = FindChildren(child); items.AddRange(children); } return items; } 

当ValidationChange事件发生时 – 调用视图以通知validation错误:

 private void OnValidationChanged(string propertyName, ValidationResult validationResult) { var vm = DataContext as ViewModelBase; if (vm != null) { if (validationResult.IsValid) { vm.ClearValidationErrorFromView(propertyName); } else { vm.AddValidationErrorFromView(propertyName, validationResult.ErrorContent as string); } } } 

ViewModel基础保留两个列表:

  • _notifyvalidationErrors由INotifyDataErrorInfo接口用于显示validation错误。
  • _privateValidationErrors用于将validation规则生成的错误显示给用户。

从视图添加validation错误时 – 使用空值更新_notifyvalidationErrors(仅表示存在validation错误),错误字符串不会添加到_notifyvalidationErrors。 如果我们将它添加到那里,我们将在文本框ErrorContent中获得两次validation错误字符串。 validation错误字符串也被添加到_privateValidationErrors(因为我们希望能够将它保存在viewmodel中)这是ViewModel基础上的代码:

 private readonly Dictionary> _notifyvalidationErrors = new Dictionary>(); private readonly Dictionary> _privateValidationErrors = new Dictionary>(); public void AddValidationErrorFromView(string propertyName, string errorString) { _notifyvalidationErrors[propertyName] = new List(); // Add the error to the private dictionary _privateValidationErrors[propertyName] = new List {errorString}; RaiseErrorsChanged(propertyName); } public void ClearValidationErrorFromView(string propertyName) { if (_notifyvalidationErrors.ContainsKey(propertyName)) { _notifyvalidationErrors.Remove(propertyName); } if (_privateValidationErrors.ContainsKey(propertyName)) { _privateValidationErrors.Remove(propertyName); } RaiseErrorsChanged(propertyName); } 

视图中的INotifyDataErrorInfo实现:

 public bool HasErrors { get { return _notifyvalidationErrors.Any(kv => kv.Value != null); } } public event EventHandler ErrorsChanged; public void RaiseErrorsChanged(string propertyName) { var handler = ErrorsChanged; if (handler != null) handler(this, new DataErrorsChangedEventArgs(propertyName)); } public IEnumerable GetErrors(string propertyName) { List errorsForProperty; _notifyvalidationErrors.TryGetValue(propertyName, out errorsForProperty); return errorsForProperty; } 

用户可以选择通过调用ViewModelBase AddValidationError和ClearValidationError方法从视图中添加validation错误。

 public void AddValidationError(string errorString, [CallerMemberName] string propertyName = null) { _notifyvalidationErrors[propertyName] = new List{ errorString }; RaiseErrorsChanged(propertyName); } public void ClearValidationError([CallerMemberName] string propertyName = null) { if (_notifyvalidationErrors.ContainsKey(propertyName)) { _notifyvalidationErrors.Remove(propertyName); RaiseErrorsChanged(propertyName); } } 

该视图可以通过调用GetValidationErrors和GetValidationErrorsString方法从ViewModel基础获取所有当前validation错误的列表。

 public List GetValidationErrors() { var errors = new List(); foreach (var key in _notifyvalidationErrors.Keys) { errors.AddRange(_notifyvalidationErrors[key]); if (_privateValidationErrors.ContainsKey(key)) { errors.AddRange(_privateValidationErrors[key]); } } return errors; } public string GetValidationErrorsString() { var errors = GetValidationErrors(); var sb = new StringBuilder(); foreach (var error in errors) { sb.Append("● "); sb.AppendLine(error); } return sb.ToString(); }