Pergunta
Alguém sabe como posso forçar CanExecute
de ser chamado em um comando personalizado (Josh Smith de RelayCommand
)?
Normalmente, CanExecute
é chamado sempre que a interação ocorre na interface do usuário. Se eu clicar em algo, meus comandos são atualizados.
Eu tenho uma situação onde a condição para CanExecute
está ficando ligado / desligado por um temporizador nos bastidores. Porque este não é impulsionado pela interação do usuário, CanExecute
não é chamado até que o usuário interage com a interface do usuário. O resultado final é que os meus Button
restos ativado / desativado até que o usuário clica sobre ele. Após o clique, ele é atualizado corretamente. Às vezes, as aparece Button
habilitado, mas quando o usuário clica ele muda para pessoas com deficiência em vez de disparar.
Como posso forçar uma atualização no código quando o temporizador altera a propriedade que afeta CanExecute
? Tentei disparar PropertyChanged
(INotifyPropertyChanged
) na propriedade que afeta CanExecute
, mas isso não ajuda.
Exemplo XAML:
<Button Content="Button" Command="{Binding Cmd}"/>
Exemplo de código por trás:
private ICommand m_cmd;
public ICommand Cmd
{
if (m_cmd == null)
m_cmd = new RelayCommand(
(param) => Process(),
(param) => EnableButton);
return m_cmd;
}
// Gets updated from a timer (not direct user interaction)
public bool EnableButton { get; set; }
Outras dicas
Eu estava ciente de CommandManager.InvalidateRequerySuggested () há muito tempo, e usou-o, mas não estava funcionando para mim às vezes. Eu finalmente descobri porque este era o caso! Mesmo que ele não jogue como algumas outras ações, você tem que chamá-lo no segmento principal.
Chamando-o em uma discussão de fundo aparecerá ao trabalho, mas às vezes deixam a interface do usuário desativado. Eu realmente espero que isso ajude alguém, e os salva as horas Eu apenas desperdiçados.
Uma solução para isso é vinculativo IsEnabled
a uma propriedade:
<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>
e, em seguida, implementar essa propriedade em seu ViewModel. Isso também faz com que seja um pouco mais fácil para o UnitTesting ao trabalho com as propriedades em vez de comandos para ver se o comando pode ser executado em um determinado ponto do tempo.
Eu, pessoalmente, achar mais conveniente.
Provavelmente esta variante vai servi-lo:
public interface IRelayCommand : ICommand
{
void UpdateCanExecuteState();
}
Implementação:
public class RelayCommand : IRelayCommand
{
public event EventHandler CanExecuteChanged;
readonly Predicate<Object> _canExecute = null;
readonly Action<Object> _executeAction = null;
public RelayCommand( Action<object> executeAction,Predicate<Object> canExecute = null)
{
_canExecute = canExecute;
_executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
if (_canExecute != null)
return _canExecute(parameter);
return true;
}
public void UpdateCanExecuteState()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
public void Execute(object parameter)
{
if (_executeAction != null)
_executeAction(parameter);
UpdateCanExecuteState();
}
}
Usando simples:
public IRelayCommand EditCommand { get; protected set; }
...
EditCommand = new RelayCommand(EditCommandExecuted, CanEditCommandExecuted);
protected override bool CanEditCommandExecuted(object obj)
{
return SelectedItem != null ;
}
protected override void EditCommandExecuted(object obj)
{
// Do something
}
...
public TEntity SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
//Refresh can execute
EditCommand.UpdateCanExecuteState();
RaisePropertyChanged(() => SelectedItem);
}
}
XAML:
<Button Content="Edit" Command="{Binding EditCommand}"/>
Obrigado rapazes para as pontas. Aqui está um pouco de código sobre como organizar essa chamada de um segmento BG para o segmento de interface do usuário:
private SynchronizationContext syncCtx; // member variable
No construtor:
syncCtx = SynchronizationContext.Current;
Na discussão de fundo, para acionar a repetição da consulta:
syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );
Espero que ajude.
- Michael
Para atualizar apenas um único GalaSoft.MvvmLight.CommandWpf.RelayCommand você poderia usar
mycommand.RaiseCanExecuteChanged();
e para mim eu criei um método de extensão:
public static class ExtensionMethods
{
public static void RaiseCanExecuteChangedDispatched(this RelayCommand cmd)
{
System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
}
public static void RaiseCanExecuteChangedDispatched<T>(this RelayCommand<T> cmd)
{
System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
}
}