Raj
Raj

Reputation: 4435

Working with DelegateCommand's CanExecute action

I've a ViewModel class like this in a Prism / WPF project.

public class ContentViewModel : ViewModelBase, IContentViewModel
{
    public ContentViewModel(IPersonService personService)
    {
        Person = personService.GetPerson();
        SaveCommand = new DelegateCommand(Save, CanSave);
    }

    public Person Person { get; set; }

    public DelegateCommand SaveCommand { get; set; }

    private void Save()
    {
        // Save actions here...
    }

    private bool CanSave()
    {
        return Person.Error == null;
    }
}

The person type used in the above ViewModel is defined as follows:

public class Person : INotifyPropertyChanged, IDataErrorInfo
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    // other properties are implemented in the same way as above...

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private string _error;
    public string Error
    {
        get
        {
            return _error;
        }
    }

    public string this[string columnName]
    {
        get
        {
            _error = null;
            switch (columnName)
            {
                // logic here to validate columns...
            }
            return _error;
        }
    }
}

An instance of ContentViewModel is set as DataContext of a View. Inside the View I've used binding to Person as follows:

<TextBox Text="{Binding Person.FirstName, ValidatesOnDataErrors=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />

When I make changes to TextBox which is binded to Person's properties like FirstName and click Save I could see the changes in ViewModel command handler. But if any of these properties fail in validation CanSave is never executed and button never gets disabled.

How do I disable a button based on DelegateCommand's CanExecute action handler in the above scenario?

Upvotes: 2

Views: 6837

Answers (3)

chopikadze
chopikadze

Reputation: 4239

In the nutshell - you should call yourDelegateCommand.RaiseCanExecuteChanged() when you think that your CanExecute() return value can be changed.

In your example, you should notify through INotifyPropertyChanged interface that your Person.Error property is changed, subscribes to Person.PropertyChanged event in your ContentViewModel class and call SaveCommand.RaiseCanExecuteChanged() each time when your Person.Error is changed. Please be careful - in your scenario Person.Error isn't recalculated automatically when, for example, Person.FirstName is changed - you should do this manually.

UPDATED:

public class ContentViewModel : ViewModelBase, IContentViewModel
{
    public ContentViewModel(IPersonService personService)
    {
        Person = personService.GetPerson();
        Person.PropertyChanged += Person_PropertyChanged;
        SaveCommand = new DelegateCommand(Save, CanSave);
    }

    private void PersonPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    {
        SaveCommand.RaiseCanExecuteChanged();
    }

    private void Save()
    {
        // Save actions here...
    }

    private bool CanSave()
    {
        return IsErrorPresented(Person);
    }

    private bool IsErrorPresented(object o)
    {
        if (!(o is IDataErrorInfo))
            return false;

        var propNames = o.GetType()
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Select(p => p.Name);

        var o2 = (o as IDataErrorInfo);

        var errors = propNames.Select(p => o2[p])
            .Where(p => !String.IsNullOrEmpty(p))
            .ToList();

        ValidationSummary.ErrorMessages = errors;

        return errors.Count > 0;
    }
}

<TextBox Text="{Binding Person.FirstName, 
                        UpdateSourceTrigger=PropertyChanged, 
                        ValidatesOnDataErrors=True, 
                        ValidatesOnExceptions=True, 
                        NotifyOnValidationError=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />

If you will also specify PropertyChanged as UpdateSourceTrigger, your save button will be updated during your typing..

Upvotes: 1

Jason Ridge
Jason Ridge

Reputation: 1868

try this with all the properties that can change error:

 public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        OnPropertyChanged("FirstName");

        OnPropertyChanged("Error");
    }
}

Alternatively

        switch (columnName)
        {
            // logic here to validate columns...

            OnPropertyChanged("Error");
        }

The problem you are having is that the OnPropertyChanged is not being called when the error changes.

The next step is to subscribe to the person's propertychanged event when its created, and create a handler that checks for the propertychanged and then changes the boolean variable that the command uses.

 public ContentViewModel(IPersonService personService)
{
    Person = personService.GetPerson();
    Person.PropertyChanged+= PersonPropertyChangedHandler;
    SaveCommand = new DelegateCommand(Save, personHasError);
}

bool personHasError = false;
void PersonPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if (e.PropertyName == "Error")
    {
        if(Person.Error == null)
           personHasError = true;
        else
           personHasError = false;
    } 
}

Hope this works. I built this by hand and didn't check it so let me know if its buggy or whatever and ill correct it

Upvotes: 2

Sreedharlal B Naick
Sreedharlal B Naick

Reputation: 156

In the constructor of ContentViewModel add this line

public ContentViewModel(IPersonService personService)
{
    //GetPerson
    Person.PropertyChanged +=person_PropertyChanged;
}

And write an method to handle that event in which you call either CommandManager.InvalidateRequerySuggested() or SaveCommand.RaiseCanExecuteChanged()

private void person_PropertyChanged(object sender, EventArgs args)
{
   CommandManager.InvalidateRequerySuggested();
   //SaveCommand.RaiseCanExecuteChanged()
}

Hope this works. :-)

Upvotes: 4

Related Questions