wpfbuttonmvvmidataerrorinfo

Enable Disable save button during Validation using IDataErrorInfo & Update Button State


I am new to the WPF MVVM and wanted to ask a follow up question to this article: Enable Disable save button during Validation using IDataErrorInfo I am trying to enable/disable the button save/update if any of the many controls on the form validation failed/passed. I have the IsValid method, that checks the validation logic on the Model and returns True/False, that will be passed on to the DelegateCommand as a predicate. The question is: my button has the following property IsEnabled{binding IsValid}, this should check all fields to make sure that it matches the criteria in the model, returns true/false to the view model and then enables the button if all true. The problem is: Once the view model is instantiated, the DelegateCommand object is created with the validation (IsValid) at at a false state and it stays that way throughout the life of the object even though the user is filling data in the textboxes. How do I turn on the button once all the conditions are met? in other words, how to constantly keep validating and updating the IsValid in order to switch the button on once every textbox validate to true??

Thanks,

I have the following code: The Model

public class UserModel : ObservePropertyChanged,IDataErrorInfo
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; OnPropertyChanged("Name"); }
        }
        // if there is an error throw an exception
        public string Error
        {
            get { throw new NotImplementedException(); }
        }
        public string this[string columnName]
        {
            get
            {
                string result = null;
                if (columnName == "Name")
                {
                    if (string.IsNullOrEmpty(Name))
                        result = "Please enter a Name";
                }
                return result;
            }
        }

        // the Josh Way
        static readonly string[] ValidatedProperties =
        {
            "Name"
        };

        public bool IsValid
        {
            get
            {
                foreach (string property in ValidatedProperties)
                {

                    if (GetValidationError(property) != null) // there is an error
                        return false;
                }
                return true;
            }
        }

        // a method that checks validation error
        private string GetValidationError(string propertyName)
        {
            string error = null;

            switch (propertyName)
            {
                case "Name":
                    error = this.ValidateName();
                    break;

                default:
                    error = null;
                    throw new Exception("Unexpected property being validated on Service");
            }
            return error;
        }
        private string ValidateName()
        {
            string ErrorMsg = null;
            if (string.IsNullOrWhiteSpace(Name))
            {
                ErrorMsg = "Name can't be empty!";
            };
            return ErrorMsg;
        }
}

** the View Model **

public class UserViewModel:ObservePropertyChanged
    {
        UserModel model;
        public UserViewModel()
        {
            presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
            model = new UserModel();
        }

        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; OnPropertyChanged("Name"); }
        }
        private string info;

        public string Info
        {
            get { return info; }
            set { info = value; OnPropertyChanged("Info"); }
        }
        private DelegateCommand presentCommand;

        public DelegateCommand PresentCommand
        {
            get 
            {
                if (presentCommand==null)
                {
                    presentCommand = new DelegateCommand(param => PresentDataMethod(), param => CanSave);
                }
                return presentCommand; 
            }
        }
        private void PresentDataMethod() 
        {
            Info = $"Your Name is: {Name}.";
        }

        // The ViewModel then contains a CanSave Property that reads the IsValid property on the Model:
        protected bool CanSave
        {
            get
            {
                return model.IsValid;
            }
        }
    }

** The View**

<TextBox x:Name="Name" HorizontalAlignment="Left" Height="34" Margin="285,145,0,0" TextWrapping="Wrap" 
                 VerticalAlignment="Top" Width="248">
            <Binding Path="Name" 
                     ValidatesOnDataErrors="True" 
                     UpdateSourceTrigger="PropertyChanged" 
                     Mode="TwoWay">
            </Binding>
        </TextBox>
        <Button Content="Present" FontSize="20" HorizontalAlignment="Left" 
                Margin="285,184,0,0" VerticalAlignment="Top" Width="248" Height="35"
                Command="{Binding Path=PresentCommand}"
                IsEnabled="{Binding IsValid}"
                >
        </Button>

Solution

  • If all you want to do is refresh the button via the IsValid value, all you have to do is listen for any of the OTHER property changes in your ViewModel, and when that happens, tell it to refresh the IsValid binding as well (which is actually your CanSave property). Here is one way to do that:

    ** The View Model **

    // ...
    
    public UserViewModel()
    {
        // ...
        this.PropertyChanged += OnViewModelPropertyChanged;
    }
    
    public void OnViewModelPropertyChanged(object sender, PropertyEventArgs e)
    {
        // On any property that implements "OnPropertyChanged(propname)", refresh the CanSave binding too!
        OnPropertyChanged(nameof(this.CanSave));
    }
    
    // ...
    

    BTW, it is generally good coding practice to avoid magic words like OnPropertyChanged("Name") or OnPropertyChanged("Info") because you never know when a developer will have to rename their properties, and if that happens, you won't get a compile error here and it might be hard to debug. It is best practice to use the nameof, such as OnPropertyChanged(nameof(Name)) so that you'll know you'll get a compilation error if you ever decide to change the property to, say, FirstName instead of Name.