Home > Enterprise >  WPF MVVM AsyncCommand CanExecute not working
WPF MVVM AsyncCommand CanExecute not working

Time:02-12

I am attempting to create a simple WPF application that have a combobox populated with server names and a button to connect to a server.

Expected behaviour: Button is disabled at first but become avaiable as soon as a server is selected.

I am using the AsyncCommand found this in this blog post that implement the ICommand methods for Async tasks.

My problem is that the button is working properly when using normal RelayCommand but doesnt work when I use AsynCommand. Am I missing something?

Here is the simplified code:

ConnectionWindow.xaml.cs:

<ComboBox Grid.Row="1" Grid.Column="1" 
             HorizontalAlignment="Left"
             x:Name="listSourceServer" 
             ItemsSource="{Binding ListSourceServer}" 
             SelectedValue="{Binding SelectedSourceServer}" 
             VerticalAlignment="Top" 
             Width="450"        
             RenderTransformOrigin="0.5,0.5"/>

<Button Grid.Row="1" Grid.Column="2"
            Content="Connect"
            HorizontalAlignment="Left" 
            VerticalAlignment="Top"
            Width="100"
            Height="25"
            FontFamily="Arial"
            Foreground="#FFFFFF"
            Background="#2e86de"
            Command="{Binding ButtonConnectSourceServer}">
    </Button>

ConnectionViewModel.cs:

private string _selectedSourceServer;
public string SelectedSourceServer
    {
        set
        {
            _selectedSourceServer = value;
            OnPropertyChanged(nameof(SelectedSourceServer));
        }
        get => _selectedSourceServer;
    }
private async Task ConnectSourceServerAsync()
    {
        await ConnectAsync(SelectedSourceServer);
    }

    private bool CanConnectOnSourceServer()
    {
        return !string.IsNullOrEmpty(SelectedSourceServer);
    }

public ConnectionViewModel() { 
    ButtonConnectSourceServer = new AsyncCommand(ConnectSourceServerAsync, CanConnectOnSourceServer);
}

AsyncCommand.cs:

    public interface IAsyncCommand : ICommand
{
    Task ExecuteAsync();
    bool CanExecute();
}

public class AsyncCommand : IAsyncCommand
{
    public event EventHandler CanExecuteChanged;

    private bool _isExecuting;
    private readonly Func<Task> _execute;
    private readonly Func<bool> _canExecute;
    private readonly IErrorHandler _errorHandler;

    public AsyncCommand(
        Func<Task> execute,
        Func<bool> canExecute = null,
        IErrorHandler errorHandler = null)
    {
        _execute = execute;
        _canExecute = canExecute;
        _errorHandler = errorHandler;
    }

    public bool CanExecute()
    {
        return !_isExecuting && (_canExecute?.Invoke() ?? true);
    }

    public async Task ExecuteAsync()
    {
        if (CanExecute())
        {
            try
            {
                _isExecuting = true;
                await _execute();
            }
            finally
            {
                _isExecuting = false;
            }
        }

        RaiseCanExecuteChanged();
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    #region Explicit implementations
    bool ICommand.CanExecute(object parameter)
    {
        return CanExecute();
    }

    void ICommand.Execute(object parameter)
    {
        ExecuteAsync().FireAndForgetSafeAsync(_errorHandler);
    }
    #endregion
}

CodePudding user response:

You need to call the RaiseCanExecuteChanged method when the SelectedSourceServer property changes.

public ConnectionViewModel()
{
    _buttonConnectSourceServer = new AsyncCommand(ConnectSourceServerAsync, CanConnectOnSourceServer);
}

private readonly AsyncCommand _buttonConnectSourceServer;
public IAsyncCommand ButtonConnectSourceServer => _buttonConnectSourceServer;

private string _selectedSourceServer;
public string SelectedSourceServer
{
    get => _selectedSourceServer;
    set
    {
        _selectedSourceServer = value;
        OnPropertyChanged(nameof(SelectedSourceServer));
        _buttonConnectSourceServer.RaiseCanExecuteChanged();
    }
}
  • Related