Search code examples
xamlmvvmwinui-3winuiicommand

WinUI: ICommand implementation - CanExecuteChanged is null and UI is not refreshed


Please see the code examples. When starting up, the method CanExecuteButtonClick will be evaluated once. When executing the method CanExecuteButtonClick, the property CanClick is evaluated. I manually raise the CanExecuteChanged Event for the Command ButtonClick.

With a debugger I saw that the method RaiseCanExecuteChanged is called, but returns where the null-check if guard is implemented.

As a consequence, the UI is obviously not updated, as the event doesnt re-evaluate the according CanExecute method.

Has anyone encountered this problem yet, or can give me some feedback if I made a mistake with my implementation?

This is the (standard) implementation of ICommand, how I implemented it in my project. I implemented the RaiseCanExecuteChanged method inspired by this stackoverflow article.

public class RelayCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged == null) return;
        CanExecuteChanged(this, EventArgs.Empty);
    }

    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }
}

I implemented my commands like this: XAML:

<Button Content="Click me"
        Command="{x:Bind ViewModel.ButtonClick}">

ViewModel:

public RelayCommand ButtonClick => new RelayCommand((obj) => ExecuteButtonClick(null), (obj) => CanExecuteButtonClick(null));
private async Task ExecuteButtonClick(object commandParameter)
{
    //Some code
}

private bool CanExecuteButtonClick(object commandParameter) => CanClick;

//Another command, for simplicity only the execute method
private async Task ExecuteAnotherButtonClick(object commandParameter)
{
    //Some code
    CanClick = true;
    ButtonClick.RaiseCanExecuteChanged();
}

Solution

  • Might not a straight answer to your question but let me show you some samlple code using the CommunityToolkit.Mvvm NuGet package, which comes with several source generators. You'll be able to avoid a bunch of boilerplate code:

    // This class needs to be "partial" for the source generators.
    public partial class SomeViewModel : ObservableObject
    {
        // The source generator will generate a "IsReady" property for you.
        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(DoSomethingCommand))]
        private bool _isReady;
    
        public bool CanDoSomething() => IsReady;
    
        // The source generator will generate a "DoSomethingCommand" for you.
        [RelayCommand(CanExecute = nameof(CanDoSomething))]
        private async void DoSomething()
        {
        }
    }
    
    <ToggleSwitch IsOn="{x:Bind ViewModel.IsReady, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <Button
        Command="{x:Bind ViewModel.DoSomethingCommand}"
        Content="Do something" />