强制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
这里有两个示例类,演示如何使用它:
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; }
现在,除了使用OnSave
和CanSave
方法注册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都从INotifyPropertyChanged
和INotifyDataErrorInfo
(或从这两个扩展的基类)扩展,当您将按钮绑定到上面的SaveCommand
,如果存在validation错误,WPF框架将自动禁用它。
希望这可以帮助。