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机制,以便我能够:
- 禁用“保存”按钮(必须)。
- 将validation错误消息更改为更有意义的错误字符串(很高兴)。
我想找到一个通用的MVVM解决方案,而不在视图中添加代码。
感谢您的帮助
string int case不适用于MVVM,因为viewmodel因绑定exception而无法获取任何信息。
我看到了两种获得所需validation的方法:
- 只需在viewmodel中使用字符串属性,当您必须转到模型时,只需将字符串转换为模型类型即可。
- 创建行为或“特殊”控件,以便视图中的输入始终“可转换”为您的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(); }