Reputation: 2828
I have a basic MVVM WPF app with a View, consisting of a texbox and submit button. Both controls are correctly binded to property and a command within ViewModel. The issue is that the CanSubmit is not triggered because CanExecuteChanged eventhandler (in DelegateCommand) is always null. Basically the question is how to properly notify Command to run CanExecute check when textox is updated.
public DelegateCommand SubmitCommand => new DelegateCommand(Submit, CanSubmit);
private string _company;
public string Company
{
get => _company;
set
{
SetProperty(ref _company, value);
SubmitCommand.RaiseCanExecuteChanged();
}
}
My delegate command
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action<object> execute) : this(execute, null) { }
public virtual bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if(CanExecuteChanged != null) <------ Always null
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
Upvotes: 0
Views: 947
Reputation: 7918
I'll complement @lidqy answer.
He is perfectly correct that the problem is with the creation of a new command every time the property is accessed.
The instance of the command must always be the same.
More often than not, Submit and CanSubmit will be instance methods.
BUT the field (property) initializer can only access static members.
There are two typical methods for initializing a command:
public DelegateCommand SubmitCommand { get; }
public ViewModel()
{
SubmitCommand = new DelegateCommand(Submit, CanSubmit);
}
private DelegateCommand _submitCommand;
public DelegateCommand SubmitCommand => _submitCommand
?? (_submitCommand = new DelegateCommand(Submit, CanSubmit));
Additional recommendation.
If you have a WPF Solution (not UWP), then you should subscribe the command to CommandManager.RequerySuggested.
In this case, upon changes in the GUI, commands validation will be automatically called.
And the need to do this in the property setter will disappear.
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
private readonly EventHandler requerySuggested;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
requerySuggested = (s, e) => RaiseCanExecuteChanged();
CommandManager.RequerySuggested += requerySuggested;
}
public DelegateCommand(Action<object> execute) : this(execute, null) { }
public virtual bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
private string _company;
public string Company { get => _company; set => SetProperty(ref _company, value); }
Also keep in mind that for a command bound to a WPF element in the GUI, the CanExecuteChanged event should always be called on the main thread of the application.
This also applies to calling it in the property setter, since the property can be changed not only from the GUI.
See also these implementations: BaseInpc, RelayCommand and RelayCommand<T> classes.
Upvotes: 4
Reputation: 2453
I think the problem is a very subtle nuance of the newer C# features for initializing get only properties. With the syntax you have:
public DelegateCommand SubmitCommand => new DelegateCommand(Submit, CanSubmit);
You return a new Command instance every time your SumitCommand getter is invoked. The SubmitCommand instance you bind in XAML is a different instance as the one in Company
setter: SubmitCommand.RaiseCanExecuteChanged();
The setter's SubmitCommand instance of course has no handler for CanExecuteChaned because its unknown to the UI. And no handler in code behind.
If you change that to:
public DelegateCommand SubmitCommand { get; } = new DelegateCommand(Submit, CanSubmit);
I think the prob will be fixed.
Because than you create the command just once and the buttons CanExecuteChanged handler will bind to the same SubmitCommand
instance the company setter invokes RaiseCanExecuteChanged for.
Upvotes: 4