将 INotifyDataErrorInfo 与属性一起使用
Using INotifyDataErrorInfo with attributes
我正在视图模型上设置 INotifyDataErrorInfo,以处理属性验证。
我在 UI 中运行良好,文本框有一个漂亮的红色边框,鼠标悬停在事件上会显示错误。
但我不知道如何在 ViewModel 中计算出视图模型是否有效。我猜我必须设置 HasErrors。在我看到的示例中,它们有一个变量
private Dictionary<string, List<string>> _PropertyErrors = new Dictionary<string, List<string>>();
但是什么都不做设置。
如果视图模型有效,我想检查 Save() 方法。
public class CustomerViewModel : EntityBase, INotifyPropertyChanged, INotifyDataErrorInfo
{
public CustomerViewModel ()
{
//SET UP
}
private string _HomePhone;
[Required]
public string HomePhone
{
get { return _HomePhone; }
set
{
if (_HomePhone != value)
{
_HomePhone = value;
PropertyChanged(this, new PropertyChangedEventArgs("HomePhone"));
}
}
}
private void Save()
{
//Break point here
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get { return true; }
}
public IEnumerable GetErrors(string propertyName)
{
return null;
}
您可以查看 HasErrors
属性.
这是 INotifyDataErrorInfo
的示例实现,具有 ValidationAttribute
支持并提供示例 TrySave()
,它检查视图模型是否有任何验证错误:
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
// Usage example property which validates its value
// before applying it using a Lambda expression.
// Example uses System.ValueTuple.
private string userInput;
public string UserInput
{
get => this.userInput;
set
{
// Use Lambda
if (ValidateProperty(value, newValue => newValue.StartsWith("@") ? (true, string.Empty) : (false, "Value must start with '@'.")))
{
this.userInput = value;
OnPropertyChanged();
}
}
}
// Alternative usage example property which validates its value
// before applying it using a Method group.
// Example uses System.ValueTuple.
private string userInputAlternativeValidation;
public string UserInputAlternativeValidation
{
get => this.userInputAlternativeValidation;
set
{
// Use Method group
if (ValidateProperty(value, AlternativeValidation))
{
this.userInputAlternativeValidation = value;
OnPropertyChanged();
}
}
}
private (bool IsValid, string ErrorMessage) AlternativeValidation(string value)
{
return value.StartsWith("@")
? (true, string.Empty)
: (false, "Value must start with '@'.");
}
// Alternative usage example property which validates its value
// before applying it using a ValidationAttribute.
private string userInputAttributeValidation;
[Required(ErrorMessage = "Value is required.")]
public string UserInputAttributeValidation
{
get => this.userInputAttributeValidation;
set
{
// Use only the attribute (can be combined with a Lambda or Method group)
if (ValidateProperty(value))
{
this.userInputAttributeValidation = value;
OnPropertyChanged();
}
}
}
private bool TrySave()
{
if (this.HasErrors)
{
return false;
}
// View model has no errors. Save data.
return true;
}
// Constructor
public ViewModel()
{
this.Errors = new Dictionary<string, List<string>>();
}
// Example uses System.ValueTuple
public bool ValidateProperty(object value, Func<object, (bool IsValid, string ErrorMessage)> validationDelegate = null, [CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
this.Errors.Remove(propertyName);
OnErrorsChanged(propertyName);
// First validate using the delegate
(bool IsValid, string ErrorMessage) validationResult = validationDelegate?.Invoke(value) ?? (true, string.Empty);
if (!validationResult.IsValid)
{
AddError(propertyName, validationResult.ErrorMessage);
}
// Check if property is decorated with validation attributes
// using reflection
IEnumerable<Attribute> validationAttributes = GetType()
.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
?.GetCustomAttributes(typeof(ValidationAttribute)) ?? new List<Attribute>();
// Validate attributes if present
if (validationAttributes.Any())
{
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateProperty(value, new ValidationContext(this, null, null) { MemberName = propertyName }, validationResults))
{
foreach (ValidationResult attributeValidationResult in validationResults)
{
AddError(propertyName, attributeValidationResult.ErrorMessage);
}
validationResult = (false, string.Empty);
}
}
return validationResult.IsValid;
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if 'isWarning' is
// false. Raises the ErrorsChanged event if the Errors collection changes.
public void AddError(string propertyName, string errorMessage, bool isWarning = false)
{
if (!this.Errors.TryGetValue(propertyName, out List<string> propertyErrors))
{
propertyErrors = new List<string>();
this.Errors[propertyName] = propertyErrors;
}
if (!propertyErrors.Contains(errorMessage))
{
if (isWarning)
{
// Move warnings to the end
propertyErrors.Add(errorMessage);
}
else
{
propertyErrors.Insert(0, errorMessage);
}
OnErrorsChanged(propertyName);
}
}
public bool PropertyHasErrors(string propertyName) => this.Errors.TryGetValue(propertyName, out List<string> propertyErrors) && propertyErrors.Any();
#region INotifyDataErrorInfo implementation
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// Returns all errors of a property. If the argument is 'null' instead of the property's name,
// then the method will return all errors of all properties.
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? this.Errors.SelectMany(entry => entry.Value)
: this.Errors.TryGetValue(propertyName, out IEnumerable<string> errors)
? errors
: new List<string>();
// Returns if the view model has any invalid property
public bool HasErrors => this.Errors.Any();
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(string propertyName)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
// Maps a property name to a list of errors that belong to this property
private Dictionary<String, List<String>> Errors { get; }
}
此 link 包含解释和 link 更多示例:
我正在视图模型上设置 INotifyDataErrorInfo,以处理属性验证。
我在 UI 中运行良好,文本框有一个漂亮的红色边框,鼠标悬停在事件上会显示错误。
但我不知道如何在 ViewModel 中计算出视图模型是否有效。我猜我必须设置 HasErrors。在我看到的示例中,它们有一个变量
private Dictionary<string, List<string>> _PropertyErrors = new Dictionary<string, List<string>>();
但是什么都不做设置。
如果视图模型有效,我想检查 Save() 方法。
public class CustomerViewModel : EntityBase, INotifyPropertyChanged, INotifyDataErrorInfo
{
public CustomerViewModel ()
{
//SET UP
}
private string _HomePhone;
[Required]
public string HomePhone
{
get { return _HomePhone; }
set
{
if (_HomePhone != value)
{
_HomePhone = value;
PropertyChanged(this, new PropertyChangedEventArgs("HomePhone"));
}
}
}
private void Save()
{
//Break point here
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get { return true; }
}
public IEnumerable GetErrors(string propertyName)
{
return null;
}
您可以查看 HasErrors
属性.
这是 INotifyDataErrorInfo
的示例实现,具有 ValidationAttribute
支持并提供示例 TrySave()
,它检查视图模型是否有任何验证错误:
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
// Usage example property which validates its value
// before applying it using a Lambda expression.
// Example uses System.ValueTuple.
private string userInput;
public string UserInput
{
get => this.userInput;
set
{
// Use Lambda
if (ValidateProperty(value, newValue => newValue.StartsWith("@") ? (true, string.Empty) : (false, "Value must start with '@'.")))
{
this.userInput = value;
OnPropertyChanged();
}
}
}
// Alternative usage example property which validates its value
// before applying it using a Method group.
// Example uses System.ValueTuple.
private string userInputAlternativeValidation;
public string UserInputAlternativeValidation
{
get => this.userInputAlternativeValidation;
set
{
// Use Method group
if (ValidateProperty(value, AlternativeValidation))
{
this.userInputAlternativeValidation = value;
OnPropertyChanged();
}
}
}
private (bool IsValid, string ErrorMessage) AlternativeValidation(string value)
{
return value.StartsWith("@")
? (true, string.Empty)
: (false, "Value must start with '@'.");
}
// Alternative usage example property which validates its value
// before applying it using a ValidationAttribute.
private string userInputAttributeValidation;
[Required(ErrorMessage = "Value is required.")]
public string UserInputAttributeValidation
{
get => this.userInputAttributeValidation;
set
{
// Use only the attribute (can be combined with a Lambda or Method group)
if (ValidateProperty(value))
{
this.userInputAttributeValidation = value;
OnPropertyChanged();
}
}
}
private bool TrySave()
{
if (this.HasErrors)
{
return false;
}
// View model has no errors. Save data.
return true;
}
// Constructor
public ViewModel()
{
this.Errors = new Dictionary<string, List<string>>();
}
// Example uses System.ValueTuple
public bool ValidateProperty(object value, Func<object, (bool IsValid, string ErrorMessage)> validationDelegate = null, [CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
this.Errors.Remove(propertyName);
OnErrorsChanged(propertyName);
// First validate using the delegate
(bool IsValid, string ErrorMessage) validationResult = validationDelegate?.Invoke(value) ?? (true, string.Empty);
if (!validationResult.IsValid)
{
AddError(propertyName, validationResult.ErrorMessage);
}
// Check if property is decorated with validation attributes
// using reflection
IEnumerable<Attribute> validationAttributes = GetType()
.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
?.GetCustomAttributes(typeof(ValidationAttribute)) ?? new List<Attribute>();
// Validate attributes if present
if (validationAttributes.Any())
{
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateProperty(value, new ValidationContext(this, null, null) { MemberName = propertyName }, validationResults))
{
foreach (ValidationResult attributeValidationResult in validationResults)
{
AddError(propertyName, attributeValidationResult.ErrorMessage);
}
validationResult = (false, string.Empty);
}
}
return validationResult.IsValid;
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if 'isWarning' is
// false. Raises the ErrorsChanged event if the Errors collection changes.
public void AddError(string propertyName, string errorMessage, bool isWarning = false)
{
if (!this.Errors.TryGetValue(propertyName, out List<string> propertyErrors))
{
propertyErrors = new List<string>();
this.Errors[propertyName] = propertyErrors;
}
if (!propertyErrors.Contains(errorMessage))
{
if (isWarning)
{
// Move warnings to the end
propertyErrors.Add(errorMessage);
}
else
{
propertyErrors.Insert(0, errorMessage);
}
OnErrorsChanged(propertyName);
}
}
public bool PropertyHasErrors(string propertyName) => this.Errors.TryGetValue(propertyName, out List<string> propertyErrors) && propertyErrors.Any();
#region INotifyDataErrorInfo implementation
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// Returns all errors of a property. If the argument is 'null' instead of the property's name,
// then the method will return all errors of all properties.
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? this.Errors.SelectMany(entry => entry.Value)
: this.Errors.TryGetValue(propertyName, out IEnumerable<string> errors)
? errors
: new List<string>();
// Returns if the view model has any invalid property
public bool HasErrors => this.Errors.Any();
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(string propertyName)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
// Maps a property name to a list of errors that belong to this property
private Dictionary<String, List<String>> Errors { get; }
}
此 link 包含解释和 link 更多示例: