user1702369
user1702369

Reputation: 1161

INotifyDataErrorInfo only trigger when needed

I have implemented the INotifyDataErrorInfo on my models. But I cant seem to use it when needed. For Example. It should not validate on errors on startup or as I am typing. Only when clicking on a button (save).

I have currently this in my XAML:

<TextBox Text="{Binding Car.Model, ValidatesOnNotifyDataErrors=False, UpdateSourceTrigger=Explicit}"/>

And in my ViewModel under the SaveCommand:

Car.Validate();
if (Car.HasErrors)
{
 return;
}
//else save 

My Model looks like this:

 Public class Car:ValidateModelBase
 {
    private string _model;
    [Required (ErrorMessage ="This field is required")]
    public string Model
    {
        get { return _model; }
        set { _model= value; RaisePropertyChanged(); }
    }
 }

And then my implementation of ValidateModelBase:

public class ValidateModelBase: INotifyDataErrorInfo, INotifyPropertyChanged
{
    private ConcurrentDictionary<string, List<string>> _errors =
        new ConcurrentDictionary<string, List<string>>();

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged([CallerMemberName]string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
        ValidateAsync();
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public void OnErrorsChanged(string propertyName)
    {
        var handler = ErrorsChanged;
        if (handler != null)
            handler(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            return null;
        }
        List<string> errorsForName;
        _errors.TryGetValue(propertyName, out errorsForName);
        return errorsForName;
    }

    public bool HasErrors
    {
        get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
    }

    public Task ValidateAsync()
    {
        return Task.Run(() => Validate());
    }

    private object _lock = new object();
    public void Validate()
    {
        lock (_lock)
        {
            var validationContext = new ValidationContext(this, null, null);
            var validationResults = new List<ValidationResult>();
            Validator.TryValidateObject(this, validationContext, validationResults, true);

            foreach (var kv in _errors.ToList())
            {
                if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
                {
                    List<string> outLi;
                    _errors.TryRemove(kv.Key, out outLi);
                    OnErrorsChanged(kv.Key);
                }
            }

            var q = from r in validationResults
                    from m in r.MemberNames
                    group r by m into g
                    select g;

            foreach (var prop in q)
            {
                var messages = prop.Select(r => r.ErrorMessage).ToList();

                if (_errors.ContainsKey(prop.Key))
                {
                    List<string> outLi;
                    _errors.TryRemove(prop.Key, out outLi);
                }
                _errors.TryAdd(prop.Key, messages);
                OnErrorsChanged(prop.Key);
            }
        }
    }
}

The thing is its working as it normally should, but the textbox is already red with rquired when Window is opened. I want it to ignore validation on startup and only validate the moment I click Save. When clicking save, it would validate and see if there is any errors. When there is errors, the validation is set (now it should be marked in red) and the window stays open. How can I achieve this.

Upvotes: 1

Views: 512

Answers (1)

Mike Strobel
Mike Strobel

Reputation: 25623

If your model should only validate when saving, I would have your model implement IEditableObject and ensure that BeginEdit is called prior to changes being made, and call EndEdit prior to committing those changes (i.e., saving).

Have your base class track whether or not it's in 'edit mode', and if it is, suppress any validation. When EndEdit is called, allow validation again and fire ErrorsChanged. It's up to you to decide how to handle CancelEdit.

Some controls, like data grids, have built-in support for IEditableObject and will call BeginEdit when you begin editing a row, and EndEdit when committing a row. It's a pretty useful interface.

Upvotes: 0

Related Questions