1

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

Manvinder
  • 4,495
  • 16
  • 53
  • 100
  • Without a single piece of code, it's difficult to understand your problem (it seems like default WPF behavior should be able to do what you want if properly implemented), and therefore to help you. Do you have a reproducing project? – Simon Mourier Oct 05 '18 at 09:06
  • @SimonMourier I have attached the sample project – Manvinder Oct 05 '18 at 09:54
  • First of all, you should implement INotifyPropertyChanged on your model, WPF uses that for a lot of things. After that, I suggest you use on-demand implementation of INotifyDataErrorInfo like I demonstrated here https://stackoverflow.com/a/34722607/403671 instead of a collection that's never up to date when WPF requires it. Also, use the ErrorsChanged event to tell WPF you have errors when you have ones. Once that's done, you should explain how should do the sample behave, what it does today, and what you expect – Simon Mourier Oct 05 '18 at 18:24
  • I once used Fluent Validation library for validation and then display the textbox with red-color. Sample link can be found at https://gist.github.com/holymoo/11243164 Actual project link from GitHub can be found on this https://github.com/JeremySkinner/FluentValidation – G K Oct 06 '18 at 06:48
  • If you are looking out for something else, then let us know. – G K Oct 06 '18 at 06:48
  • @SimonMourier Thanks, the bug was easy to fix, I was using NotifyPropertyChanged when my property value changes but without traversing it, it never fires. Your code gives me the idea to fire NotifyPropertyChanged(null) when validating all the properties (on save button click) which update all required fields and results are as expected. Thanks again. – Manvinder Oct 07 '18 at 06:43
  • You should answer yourself then – Simon Mourier Oct 07 '18 at 16:33

1 Answers1

3

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);
    }
}
Manvinder
  • 4,495
  • 16
  • 53
  • 100