-->

RelayCommand change canExecute automatic

2019-07-23 18:19发布

问题:

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.

回答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;
}