如何在WPF/AvaloniaUI中动态添加ValidationAttributes到属性

gojuced7  于 2023-05-23  发布在  其他
关注(0)|答案(1)|浏览(121)

我想通过调用attributes IsValid函数,动态地将ValidationAttributes添加到一个属性中,从而对绑定到该特定属性的任何控件启用错误验证。我正在使用AvaloniaUI,但它应该与WPF完全相同。
我尝试实现this answer,通过使用TypeDescriptor.GetProperties可以看到相应的属性添加了新属性,但是ValidationAttribute的效果似乎不起作用,因为IsValid函数没有被调用。

pbgvytdp

pbgvytdp1#

下面是一些使用 predicate 进行验证的代码。
在基类中有相当一部分是与其他方面有关的,但无论如何,您或其他人可能会发现这很有趣。
基础视图模型:

public class BaseValidVM :  BaseNotifyUI, INotifyDataErrorInfo, INotifyPropertyChanged
    {
        // From Validation Error Event
        private RelayCommand<PropertyError> conversionErrorCommand;
        public RelayCommand<PropertyError> ConversionErrorCommand
        {
            get
            {
                return conversionErrorCommand
                    ?? (conversionErrorCommand = new RelayCommand<PropertyError>
                        (PropertyError =>
                        {
                            if (PropertyError.Added)
                            {
                                AddError(PropertyError.PropertyName, PropertyError.Error, ErrorSource.Conversion);
                            }
                            FlattenErrorList();
                        }));
            }
        }
        // From Binding SourceUpdate Event
        private RelayCommand<string> sourceUpdatedCommand;
        public RelayCommand<string> SourceUpdatedCommand
        {
            get
            {
                return sourceUpdatedCommand
                    ?? (sourceUpdatedCommand = new RelayCommand<string>
                        (Property =>
                        {
                            ValidateProperty(Property);
                        }));
            }
        }
        private RelayCommand validateCommand;
        public RelayCommand ValidateCommand
        {
            get
            {
                return validateCommand
                   ?? (validateCommand = new RelayCommand
                        (() =>
                        {
                            bool isOk = IsValid;
                            RaisePropertyChanged("IsValid");
                        }));
            }
        }

        private ObservableCollection<PropertyError> errorList = new ObservableCollection<PropertyError>();
        public ObservableCollection<PropertyError> ErrorList
        {
            get
            {
                return errorList;
            }
            set
            {
                errorList = value;
                RaisePropertyChanged();
            }
        }

        protected Dictionary<string, List<AnError>> errors = new Dictionary<string, List<AnError>>();

        protected bool isBusy = false;
        public bool IsBusy
        {
            get { return isBusy; }
            set { isBusy = value;  RaisePropertyChanged("IsBusy"); }
        }
    
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

         public IEnumerable GetErrors(string property)
        {
            if (string.IsNullOrEmpty(property))
            {
                return null;
            }
            if (errors.ContainsKey(property) && errors[property] != null && errors[property].Count > 0)
            {
                return errors[property].Select(x => x.Text).ToList();
            }
            return null;
        }
        public bool HasErrors
        {
            get { return errors.Count > 0; }
        }
        public void NotifyErrorsChanged(string propertyName)
        {
            if (ErrorsChanged != null)
            {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        public virtual Dictionary<string, List<PredicateRule>> ValiditionRules { get; set; }
        private List<string> lastListFailures = new List<string>();
        public bool IsValid {
            get
            {
                // Clear only the errors which are from object Validation
                // Conversion errors won't be detected here
                RemoveValidationErrorsOnly();

                var vContext = new ValidationContext(this, null, null);
                List<ValidationResult> vResults = new List<ValidationResult>();
                Validator.TryValidateObject(this, vContext, vResults, true);
                TransformErrors(vResults);

                // Iterating the dictionary allows you to check the rules for each property which has any rules
                if(ValiditionRules != null)
                {
                    foreach (KeyValuePair<string, List<PredicateRule>> ppty in ValiditionRules)
                    {
                        ppty.Value.Where(x => x.IsOK(this) == false)
                                   .ToList()
                                   .ForEach(x =>
                                         AddError(ppty.Key, x.Message, ErrorSource.Validation)
                                    );
                    }
                }

                var propNames = errors.Keys.ToList();
                propNames.Concat(lastListFailures)
                         .Distinct()
                         .ToList()
                         .ForEach(pn => NotifyErrorsChanged(pn));
                lastListFailures = propNames;

                FlattenErrorList();

                //foreach (var item in errors)
                //{
                //    Debug.WriteLine($"Errors on {item.Key}");
                //    foreach (var err in item.Value)
                //    {
                //        Debug.WriteLine(err.Text);
                //    }
                //}

                if (propNames.Count > 0)
                {
                    return false;
                }
                return true;
            }
        }
        private void RemoveValidationErrorsOnly()
        {
            foreach (KeyValuePair<string, List<AnError>> pair in errors)
            {
                List<AnError> _list = pair.Value;
                _list.RemoveAll(x => x.Source == ErrorSource.Validation);
            }

            var removeprops = errors.Where(x => x.Value.Count == 0)
                .Select(x => x.Key)
                .ToList();
            foreach (string key in removeprops)
            {
                errors.Remove(key);
            }
        }
        public void ValidateProperty(string propertyName)
        {
            errors.Remove(propertyName);
            lastListFailures.Add(propertyName);

            if(!propertyName.Contains("."))
            {
                var vContext = new ValidationContext(this, null, null);
                vContext.MemberName = propertyName;
                List<ValidationResult> vResults = new List<ValidationResult>();
                Validator.TryValidateProperty(this.GetType().GetProperty(propertyName).GetValue(this, null), vContext, vResults);

                TransformErrors(vResults);
            }

            // Apply Predicates
            // ****************
            if (ValiditionRules !=null && ValiditionRules.ContainsKey(propertyName))
            {
                ValiditionRules[propertyName].Where(x => x.IsOK(this) == false)
                                             .ToList()
                                             .ForEach(x =>
                                              AddError(propertyName, x.Message, ErrorSource.Validation)
                                               );
            }
            FlattenErrorList();
            NotifyErrorsChanged(propertyName);
            RaisePropertyChanged("IsValid");
        }
        private void TransformErrors(List<ValidationResult> results)
        {
            foreach (ValidationResult r in results)
            {
                foreach (string ppty in r.MemberNames)
                {
                    AddError(ppty, r.ErrorMessage, ErrorSource.Validation);
                }
            }
        }
        private void AddError(string ppty, string err, ErrorSource source)
        {
            List<AnError> _list;
            if (!errors.TryGetValue(ppty, out _list))
            {
                errors.Add(ppty, _list = new List<AnError>());
            }
            if (!_list.Any(x => x.Text == err))
            {
                _list.Add(new AnError { Text = err, Source = source });
            }
        }
        private void FlattenErrorList()
        {
            ObservableCollection<PropertyError> _errorList = new ObservableCollection<PropertyError>();
            foreach (var prop in errors.Keys)
            {
                List<AnError> _errs = errors[prop];
                foreach (AnError err in _errs)
                {
                    _errorList.Add(new PropertyError { PropertyName = prop, Error = err.Text });
                }
            }
            ErrorList = _errorList;
        }
        public void ClearErrors()
        {
            List<string> oldErrorProperties = errors.Select(x => x.Key.ToString()).ToList();
            errors.Clear();
            ErrorList.Clear();
            foreach (var p in oldErrorProperties)
            {
                NotifyErrorsChanged(p);
            }
            NotifyErrorsChanged("");
        }
    }
}

在继承自该视图模型的视图模型中,您可以添加驱动验证的 predicate :

// Empty string is validation which is not at all property specific
        // Otherwise.
        // Add an entry per property you need validation on with the list of PredicateRule containing the validation(s)
        // to apply.
        public override Dictionary<string, List<PredicateRule>> ValiditionRules { get; set; } 

        public UserControl1ViewModel()
        {
            // Constructor of the inheriting viewmodel adds any rules which do not suit annotations
            // Note
            // Two alternative styles of writing rules:
            ValiditionRules = new Dictionary<string, List<PredicateRule>>
            {
                {"Amount2",
                    new List<PredicateRule>
                    {
                      new PredicateRule
                      {
                          Message ="Amount2 must be greater than Amount",
                          IsOK = x => Amount2 > Amount
                      }
                    }
                },
                {"OrderDate",
                    new List<PredicateRule>
                    {
                      new PredicateRule
                      {
                          Message ="Amount must be greater than 1 if Order Date is in future",
                          IsOK = x =>
                          {
                              if(OrderDate.Date > DateTime.Now.Date)
                              {
                                  if(Amount <= 1)
                                  {
                                      return false;
                                  }
                              }
                              return true;
                          }
                      },
                      new PredicateRule
                      {
                          Message ="Order Date may only be a maximum of 31 days in the future",
                          IsOK = x => (OrderDate - DateTime.Now.Date).Days < 31
                      }
                    }
                }
            };
        }

这是从一个PoC的一些真实的世界代码。

相关问题