WPF: Como actualizar o estado de controlos depois de um comando executar

Neste artigo vamos falar de dois assuntos: como desactivar controlos na interface quando há trabalho a ser executado em background e como re-activar esses controlos assim que o trabalho terminar sem ter de clickar algures na interface.

Vamos usar a seguinte abordagem. Primeiro precisamos de uma variável que nos diga se o view model está a executar algum trabalho:

protected bool _isBusy;

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

Vamos ligar esta variável ao estado de activação dos controlos.

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

Estou a usar o InvertBoolConverter para inverter o valor da proprieade booliana IsBusy. Não é necessário fazer desta maneira. Podem simplesmente criar uma propriedade com uma lógica diferente (e.g. IsUiEnabled), mas eu prefiro assim.

Para esta solução é útil ter um método genérico no view model base para permitir correr outros métodos em 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();
}

Todos os comandos iniciados a partir da interface vão chamar este método.

É bastante simples. Primeiro actualizamos o estado IsBusy, depois o método de trabalho é executado. De seguida voltamos a actualizar o estado IsBusy e chamamos o método InvalidateRequerySuggested do CommandManager.

Essa última chamada avisa os controlos que estão ligados a comandos para verificarem o seu estado de “pode executar”. Sem esta chamada deparamo-nos com o seguinte problema:

  • Clickamos num botão para começar algum trabalho
  • O botão é desactivado enquanto o trabalho está a ser feito
  • O trabalho termina mas o botão continua desactivado
  • Temos de clickar algures na interface para voltar a activar o botão

Com esta abordagem implementam-se os comandos assim:

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);
}

No primeiro parâmetro do constructor do RelayCommand chamamos o método assíncrono RunTask passando o nosso método de trabalho.

O segundo parâmetro define o estado de “pode executar” e fazemos uso da propriedade IsBusy para isso.

Artigos relacionados