WPF: How to update control state after command finishes

This article will address two issues: how to disable controls in the UI when work is being done in the background and how to re-enable them after work is finished without having to click somewhere in the interface.

This is the approach we are going to take. First we need a property that tells us if the view model is doing any work:

protected bool _isBusy;

public bool IsBusy
{
    get { return _isBusy; }
    
    set
    {
        if (value != _isBusy)
        {
            _isBusy = value;
            OnPropertyChanged("IsBusy");
        }
    }
}

We will bind the enabled property of the controls to this variable.

<Button
    Content="Do work!"
    IsEnabled="{Binding IsBusy, Converter={my:InvertBoolConverter}}"
    Command="{Binding DoWorkCommand}" />

I am using the InvertBoolConverter to invert the value of the boolean property IsBusy. You don’t need to do it this way, you can simply make a property with a different logic (e.g. IsUiEnabled), but I prefer this one.

I find it useful to have a general method in my base view model class that allows me to run other methods in the background:

protected async Task RunTask(Action work)
{
    IsBusy = true;
    await Task.Run(() =>
    {
        work();
    });
    IsBusy = false;
    
    // update buttons and controls enabled status (can execute) after finishing work
    CommandManager.InvalidateRequerySuggested();
}

All commands that are initiated from the UI will call this method.

It’s pretty straightforward. First we set the IsBusy status, then work is done. After that we update the IsBusy status and, importantly, we call the InvalidateRequerySuggested on the CommandManager.

This last call is what tells controls that are bound to commands to check their “can execute” status. Without this call you run into the following issue:

  • You click a button to start some work
  • The button is disabled while work is being done
  • Work finishes but the button status is not updated
  • Only when you click somewhere will the button re-enable

Using this approach you implement a command like this:

RelayCommand _doWorkCommand;

public ICommand DoWorkCommand
{
    get
    {
        if (_doWorkCommand == null)
        {
            _doWorkCommand = new RelayCommand(
                async (param) => await RunTask(DoWorkMethod),
                (param) => !IsBusy);
        }
        
        return _doWorkCommand;
    }
}

protected void DoWorkMethod()
{
    Thread.Sleep(3000);
}

In the first parameter to the RelayCommand constructor we use our async RunTask method to call the appropriate work.

The second parameter defines the “can execute” status and we make use of the IsBusy property for that.

Nuno Freitas
Posted by Nuno Freitas on April 11, 2014

Related articles