You can use the CanExecute method, but it is good practice is actually to avoid this, and bind the button's enabled state to a separate boolean property of the view model. Most other solutions will have unexpected effects, or be suboptimal. Why?
CanExecute is a method. This means that it needs to be polled for the button state to change. You can force the control that's using the command to re-poll on a status change, but the code is much cleaner and more straightforward if you just use a property on the view model. This is because as a method, you can't use INotifyPropertyChanged to notify for changes, whereas with a property you can.
The danger in using CanExecute is that the user will manage to click the button after the method would return false, but before the button's enablement has changed.
Edit: Code to do what you want:
public class ViewModel : INotifyPropertyChanged
{
private int someValue;
private bool isEnabled;
public ViewModel()
{
MyCommand = new RelayActionCommand(Click);
}
private void Click(object obj)
{
//Do something.
}
/// <summary>
/// Bind this to the IsEnabled property of the button, and
/// also the background using a convertor or see ButtonBackground.
/// </summary>
public bool IsEnabled => SomeValue < 5;
/// <summary>
/// Option 2 - use this property to bind to the background of the button.
/// </summary>
public Brush ButtonBackground => IsEnabled ? Brushes.SeaShell : Brushes.AntiqueWhite;
public int SomeValue
{
get { return someValue; }
set
{
if (value == someValue) return;
someValue = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsEnabled));
OnPropertyChanged(nameof(ButtonBackground));
}
}
/// <summary>
/// Bind this to the command of the button.
/// </summary>
public RelayActionCommand MyCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged
([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Relay command fixed up a bit to avoid using CanExecute:
public class RelayActionCommand : ICommand
{
public RelayActionCommand(Action<object> executeAction)
{
ExecuteAction = executeAction;
}
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; }
/// <summary>
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ExecuteAction?.Invoke(parameter);
}
//Deliberately empty.
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
EDIT 2: Code to do what you want using a DelegateCommand
Note, this does not use InvalidateRequerySuggested - mainly because it refreshes all buttons when any CanExecute changes, which is a poor solution. As you can see, this is less immediately straightforward than putting the code in the view model directly, but whatever floats your boat I guess.
public sealed class ViewModel : INotifyPropertyChanged
{
private int calls;
public ViewModel()
{
SafeOnceCommand = new RelayCommand(DoItOnce, HasDoneIt);
}
private bool HasDoneIt()
{
return Calls == 0;
}
private void DoItOnce()
{
if (Calls > 0) throw new InvalidOperationException();
Calls++;
}
public int Calls
{
get { return calls; }
set
{
if (value == calls) return;
calls = value;
OnPropertyChanged();
SafeOnceCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand SafeOnceCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class RelayCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool> canExecute;
private readonly List<EventHandler> invocationList = new List<EventHandler>();
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute();
}
public void Execute(object parameter)
{
execute();
}
/// <summary>
/// Method to raise CanExecuteChanged event
/// </summary>
public void RaiseCanExecuteChanged()
{
foreach (var elem in invocationList)
{
elem(null, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged
{
add { invocationList.Add(value); }
remove { invocationList.Remove(value); }
}
}