将 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 更多示例: