Search code examples
wpfinotifydataerrorinfo

WPF How to set Validation.HasError property on controls manually?


I have a wpf window which fires validation when a user interacts with the control (got into the control and change the value which results in updated property) and upon property changed, validation fire and displayed as it should.

But I want to show all validation errors on the screen manually when a user clicks on the save button without traversing the controls, otherwise how it suppose to look if the user loads the screen and click on the save button.

Even if I create a method like IsValid() and call it upon clicking on the save button, it validates the whole form and tell me if it is valid or not but the red border around text boxes won't be showing(because Validation.HasError property is not being updated), which is I need because in a form of several controls I need to notify the user about the exact control that is causing the problem.

You can get the sample project with the problem from this link https://1drv.ms/u/s!AuCr-YEWkmWUiopdQ-eZ17IC7IAJnA


Solution

  • When we validate a property without traversing it. It won't update Validate.HasError property of the control. The solution to this was plain old simple NotifyPropertyChanged(propertyName).

    I was using NotifyPropertyChanged when my property value changes(in the set) but without traversing it, it never fires.

    So either we should call NotifyPropertyChanged when property's validation failed or we should call NotifyPropertyChanged(null) which notify all the control's to refresh their properties.

    Adding full implementation of my INotifyDataErrorInfo

        public class NotifyDataErrorInfoBase<T> : INotifyDataErrorInfo
    {
        public NotifyDataErrorInfoBase(T model)
        {
            Model = model;
        }
    
        public T Model { get; set; }
    
        protected void SetValue<TValue>(string propertyName, TValue value)
        {
            typeof(T).GetProperty(propertyName).SetValue(Model, value);
            ValidateProperty<TValue>(propertyName);
        }
    
        public bool ValidateAllProperties()
        {
    
            List<KeyValuePair<string, Type>> lstOfProperties = typeof(T).GetProperties().
                 Select(u => new KeyValuePair<string, Type>(u.Name, u.PropertyType)).ToList();
            foreach (var property in lstOfProperties)
            {
               Type currentType = property.Value;
                if (property.Value == typeof(string))
                {
                    ValidateProperty<string>(property.Key);
                }
                else if (property.Value == typeof(int))
                {
                    ValidateProperty<int>(property.Key);
                }
            }
            return !HasErrors;
        }
    
        private void ValidateProperty<TValue>([CallerMemberName]string propertyName = null)
        {
            ClearErrors(propertyName);
            var validationContext = new ValidationContext(Model) { MemberName = propertyName };
            List<ValidationResult> results = new List<ValidationResult>();
    
            var userName = GetValue<TValue>(propertyName);
            Validator.TryValidateProperty(userName, validationContext, results);
    
            if (results.Any())
            {
                foreach (var item in results)
                {
                    AddError(propertyName, item.ErrorMessage);
                }
            }
        }
    
        protected TValue GetValue<TValue>(string propertyName)
        {
            return (TValue)typeof(T).GetProperty(propertyName).GetValue(Model);
        }
    
        Dictionary<string, List<string>> _lstOfErrors = new Dictionary<string, List<string>>();
    
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    
        public bool HasErrors => _lstOfErrors.Any();
    
        public IEnumerable GetErrors(string propertyName)
        {
            return _lstOfErrors.ContainsKey(propertyName) ? _lstOfErrors[propertyName] : null;
        }
    
        protected void AddError(string propertyName, string errorMessage)
        {
            if (!_lstOfErrors.ContainsKey(propertyName))
            {
                _lstOfErrors[propertyName] = new List<string>();
            }
            _lstOfErrors[propertyName].Add(errorMessage);
        }
    
        protected void OnErrorsChanged(string propertyName)
        {
            ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
        }
    
        protected void ClearErrors(string propertyName)
        {
            if (_lstOfErrors.ContainsKey(propertyName))
                _lstOfErrors.Remove(propertyName);
        }
    }