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.