1

The current step of learning MVVM is RelayCommand for me.

So i came up with this RelayCommand class:

Relay Command class

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public RelayCommand(Action<object> execute) : this(execute, null)
    {

    }
    public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute ?? (x => true);
    }



    public bool CanExecute(object parameter)
    {
        return _canExecute(parameter);
    }

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

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

    public void Refresh()
    {
        CommandManager.InvalidateRequerySuggested();
    }
}

View Code-Behind

To test if CanExecute is true or false, I created a Click Event which is calling the Command if CanExecute == true or Show an Error Message when CanExecute == false.

if (sender is Button button)
{
    if (_viewModel.MyCommand.CanExecute(button.Tag)) // Also testet to set this parameter `null`
        _viewModel.MyCommand.Execute(button.Tag);
    else
        ErrorMessage.Error("CanExecute = false");
}

ViewModel

In my ViewModel I created the Command and added a Thread.Sleep() to have time that canExecute can show me the ErrorMessage from the Code-Behind.

public ICommand MyCommand { get; set; }
public ViewModel()
{
    MyCommand = new RelayCommand(MyCommandMethod);
}

public async void MyCommandMethod(object obj)
{
    await Task.Run(() =>
    {
        Thread.Sleep(5000);
        ErrorMessage.Error(obj as string);
    });

}

The Problem now is, that if I click the Button 5 times for example, that MyCommandMetod() is used 5 times. So CanExecute will never change.

But why isn't it changing?

I understand RelayCommand as this:

  • 1st - Button is clicked
  • 2nd - canExecute = false (wait till process is finished)
  • 3rd - canExecute = true
  • 4th - Button can be executed again.

So that u can't spam Button clicks and crash the application if for example someone use SpeedClicker and clicks 1.000.000 times a seconds or so.

Pielroja
  • 35
  • 6
  • You don't set the _canExecute delegate somewhere in your sample, do you? You are using the constructor overload that accepts only an Action. So when do you expect CanExecute to return false? – mm8 Jun 19 '18 at 12:16
  • I thought this would do the job: `_canExecute = canExecute ?? (x => true);` – Pielroja Jun 19 '18 at 12:19
  • 1
    That creates a `Func` that always returns true when _canExecute is `null`. It's a basic conditional statement. – mm8 Jun 19 '18 at 12:21
  • Oh okay. So the problem is, that im not even setting canExecute somewhere as u said. I thought thats the job of a RelayCommand and this will be automatic check n' change. – Pielroja Jun 19 '18 at 12:23
  • How is the RelayCommand class supposed to know the CanExecute logic of each of your commands unless you (or the view model) tell it what the logic actually is? – mm8 Jun 19 '18 at 12:26
  • The class can't know then. I just call the `CanExecute` from the other classes but don't set it. Should I set `CanExecute` to `false` after calling the `viewModel.MyCommand.Execute()` Command ? But where in `RelayCommand` I can check that the Commands process is finished to set `CanExecute` to `true` again ? – Pielroja Jun 19 '18 at 12:31

1 Answers1

1

You have to pass some can-execute-logic to the command when creating it:

public ViewModel()
{
    MyCommand = new RelayCommand(MyCommandMethod, MyCanExecutePredicate);
}

private bool MyCanExecutePredicate( object commandParameter )
{
    // TODO: decide whether or not MyCommandMethod is allowed to execute right now
}

Example: if you want to allow only one command execution at a time, you could come up with something along these lines:

public async void MyCommandMethod(object obj)
{
    _myCanExecute = false;
    MyCommand.Refresh();
    await Task.Run(() =>
    {
        Thread.Sleep(5000);
        ErrorMessage.Error(obj as string);
    });
    _myCanExecute = true;
    MyCommand.Refresh();
}

private bool MyCanExecutePredicate( object commandParameter )
{
    return _myCanExecute;
}
Haukinger
  • 10,420
  • 2
  • 15
  • 28
  • The problem is, that my Commands are in the `ViewModel` class and the `CanExecute` etc. are in the `RelayCommand` class. So i can't get access to `_canExecute`. Any idea ? – Pielroja Jun 19 '18 at 14:16
  • @Pielroja: You pass in an implementation when you create the command..._canExecute will be set to whatever Func you pass in. – mm8 Jun 19 '18 at 14:43
  • So `.. = new RelayCommand(MyCommandMethod, false)` will set `_canExecute` to false ? And it will be automatic set to `true` after finish` or do I have to set this manually too? – Pielroja Jun 19 '18 at 15:11
  • make that `_ => false`, then it will be false. It will not be set to anything else automatically, of course, the result of `_ => false` is always false. You have to decide manually when `CanExecute` should be true or false and design appropriately the function you pass to the command. You could create a special `OneAtATimeRelayCommand` class whose `CanExecute` is always true unless a command is currently running, but the regular `RelayCommand` gives the user maximum freedom. – Haukinger Jun 20 '18 at 05:44