sunefred
sunefred

Reputation: 4248

How does NotifyOnValidationError end up calling CanExecute (and why doesn't it work with MVVM Light RelayCommand)

Maybe the longest question title of all time! Because this is a two part question.

(1) I do not understand how setting NotifyOnValidationError="True" can trigger updates to my CanExecute. There is a bit of magic involved here that I need to understand. Someone(thing) subscribes to the CanExecuteChanged event of my ICommand but the call stack points to External code, so I can not figure out what is going on.

(2) Maybe the most important follow up questions is: Why does it not work in MVVM Light RelayCommand! The CanExecute is only called once at initialization and then never again. Looking at the source code for RelayCommand in MVVM Light does not reveal any chocking differences compared to my own implementation. I should mention that Prism's DelegateCommand does not seem to work either.

(Bonus) Maybee I am approaching this problem the wrong way? I just basically want to enable/disable buttons based on Validation failiures.

XAML (snippet):

    <TextBox Grid.Column="1" Grid.Row="0">
        <Binding Path="X" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
            <Binding.ValidationRules>
                <ExceptionValidationRule></ExceptionValidationRule>
            </Binding.ValidationRules>
        </Binding>
    </TextBox>

    <Button Grid.Column="1" Grid.Row="3" Command="{Binding CalculateCommand}">
        Calculate
    </Button>

RelayCommand:

public class MyRelayCommand : ICommand
{
    readonly Action<object> Execute_;
    readonly Predicate<object> CanExecute_;

    public MyRelayCommand(Action<object> Execute, Predicate<object> CanExecute)
    {
        if (Execute == null)
            throw new ArgumentNullException("No action to execute for this command.");

        Execute_ = Execute;
        CanExecute_ = CanExecute;
    }

    public bool CanExecute(object parameter)
    {
        return (CanExecute_ == null) ? true : CanExecute_(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        Execute_(parameter);
    }
}

ViewModel:

    private DelegateCommand _calculateCommmand;
    public DelegateCommand CalculateCommand
    {
        get
        {
            return _calculateCommmand ?? (_calculateCommmand = new DelegateCommand(
            () =>
            {
                Sum = X + X;
            },
            () =>
            {
                try
                {
                    Convert.ChangeType(X, TypeCode.Byte);
                    return true;
                }
                catch
                {
                    return false;
                }
            }));
        }
    }

PS: If you wanna buy my X + X program when it is done email me at [email protected]

Upvotes: 1

Views: 713

Answers (3)

sunefred
sunefred

Reputation: 4248

(1) I think it goes like this.

  1. When we bind a RelayCommand : ICommand to Button.Command, the binding process will also attach an eventhandler to the ICommand.CanExecuteChanged. This is default behavior.
  2. The eventhandler passed to CanExecutedChanged will be passed along and attached to the static event CommandManager.RequerySuggested.
  3. When a validation error occurs and the NotifyOnValidationError is set an external force, or a jedi, will raise the RequerySuggested event which will broadcast to ALL active commands.
  4. The Button recieves the event and consequently calls CanExecute to know if it shold disable/enable the button.

I would like to know more about the third bullet point above, so I will keep the question open for a little longer to give the experts a chance to chime in.

Upvotes: 0

sunefred
sunefred

Reputation: 4248

(2) I figured this one out myself. You can choose to include RelayCommand from two different namespaces, make sure you use

using GalaSoft.MvvmLight.CommandWpf;

I am still looking for a good answer to (1), how the plumbing works that raises CanExecutetChanged based on validation error.

Upvotes: 1

Kim Homann
Kim Homann

Reputation: 3248

I don't think it depends on the ICommand implementation. In yours, I see a public event EventHandler CanExecuteChanged, where you tell the CommandManager to handle the invocation of your command's CanExecute() method. Without the CommandManager, you would have to handle this yourself, e.g. by providing your ICommand implementation with a public void RaiseCanExecuteChanged() method that your ViewModel has to call for every command it considers to be needed to recalculated, e.g. inside the ViewModel's OnPropertyChanged. Example: https://codereview.stackexchange.com/questions/124361/mvvm-am-i-doing-it-right

So the CommandManager does the magic for you. As soon as you invoke your ViewModel's PropertyChanged event, the "external code" handles the affected commands and asks them for a fresh CanExecute() value.

Upvotes: 0

Related Questions