WPF - Como forçar um comando para reavaliar 'CanExecute' através de suas CommandBindings

130

Eu tenho um em Menuque cada MenuItemhierarquia tem sua Commandpropriedade definida como RoutedCommanddefinida. O associado CommandBindingfornece um retorno de chamada para a avaliação da CanExecutequal controla o estado ativado de cada um MenuItem.

Isso quase funciona. Os itens de menu aparecem inicialmente com os estados ativados e desativados corretos. No entanto, quando os dados que meu CanExecuteretorno de chamada usa são alterados, preciso do comando para solicitar novamente um resultado do retorno de chamada para que esse novo estado seja refletido na interface do usuário.

Não parece haver quaisquer métodos públicas sobre RoutedCommandou CommandBindingpara esta.

Observe que o retorno de chamada é usado novamente quando clico ou digito no controle (acho que foi acionado na entrada porque o mouse não causa a atualização).

Drew Noakes
fonte

Respostas:

172

Não é a mais bonita do livro, mas você pode usar o CommandManager para invalidar todas as ligações de comando:

CommandManager.InvalidateRequerySuggested();

Veja mais informações no MSDN

Arcturus
fonte
1
Graças isso funcionou muito bem. Há um pequeno atraso na interface do usuário, mas não estou muito preocupado com isso. Além disso, votei sua resposta imediatamente e depois votei novamente para ver se funcionava. Agora que está funcionando, não posso reaplicar a votação novamente. Não sei por que o SO possui essa regra.
Drew Noakes
5
Editei sua resposta para reaplicar meu voto. Não mudei nada na edição. Obrigado novamente.
Drew Noakes
Eu tive o mesmo problema quando estava alterando o conteúdo de um Texbox a partir do code-behind. Se você editá-lo manualmente, funcionaria. Nesse aplicativo, eles tinham o texbox sendo editado por um controle que apareceria e, quando você salvasse o pop-up, ele alteraria a propriedade Texbox.Text. Isso resolveu o problema! Obrigado @Arcturus
Dzyann
10
Nota Só a partir de outra resposta ( stackoverflow.com/questions/783104/refresh-wpf-command ) "tem que ser chamado no segmento UI"
Samvel Siradeghyan
84

Para quem se deparar com isso mais tarde; Se você estiver usando MVVM e Prism, a DelegateCommandimplementação de Prism ICommandfornece um .RaiseCanExecuteChanged()método para fazer isso.

CodingWithSpike
fonte
12
Esse padrão também é encontrado em outras bibliotecas MVVM, por exemplo, MVVM Light.
Peter Lillevold 30/06
2
Ao contrário do Prism, o código-fonte do MVVM Light v5 indica RaiseCanExecuteChanged() apenas chamadas CommandManager.InvalidateRequerySuggested().
Peter
4
uma nota lateral para MVVM Light em WPF, você precisa usar o namespace GalaSoft.MvvmLight.CommandWpf desde GalaSoft.MvvmLight.Command vai causar problemas mvvmlight.net/installing/changes#v5_0_2
fuchs777
((RelayCommand)MyCommand).RaiseCanExecuteChanged();funcionou para mim, usando GalaSoft.MvvmLight.Command - MAS depois de mudar para CommandWPF, funcionou sem a necessidade de chamar nada. Obrigado @ fuchs777
Robin Bennett
1
E se você não estiver usando uma biblioteca de terceiros?
Vidar
28

Eu não poderia usar CommandManager.InvalidateRequerySuggested();porque estava sendo atingido pelo desempenho.

Eu usei o comando Delegating do MVVM Helper , que se parece com abaixo (eu ajustei um pouco para o nosso req). você tem que ligar command.RaiseCanExecuteChanged()da VM

public event EventHandler CanExecuteChanged
{
    add
    {
        _internalCanExecuteChanged += value;
        CommandManager.RequerySuggested += value;
    }
    remove
    {
        _internalCanExecuteChanged -= value;
        CommandManager.RequerySuggested -= value;
    }
}

/// <summary>
/// This method can be used to raise the CanExecuteChanged handler.
/// This will force WPF to re-query the status of this command directly.
/// </summary>
public void RaiseCanExecuteChanged()
{
    if (canExecute != null)
        OnCanExecuteChanged();
}

/// <summary>
/// This method is used to walk the delegate chain and well WPF that
/// our command execution status has changed.
/// </summary>
protected virtual void OnCanExecuteChanged()
{
    EventHandler eCanExecuteChanged = _internalCanExecuteChanged;
    if (eCanExecuteChanged != null)
        eCanExecuteChanged(this, EventArgs.Empty);
}
Bek Raupov
fonte
3
Apenas um FYI comentei CommandManager.RequerySuggested + = value; Eu estava recebendo uma avaliação quase constante / em loop do meu código CanExecute por algum motivo. Caso contrário, a solução funcionou conforme o esperado. Obrigado!
robaudas
15

Se você tiver implementado sua própria classe que implementa, ICommandpoderá perder muitas atualizações automáticas de status, forçando-o a confiar na atualização manual mais do que seria necessário. Também pode quebrar InvalidateRequerySuggested(). O problema é que uma ICommandimplementação simples falha ao vincular o novo comando ao CommandManager.

A solução é usar o seguinte:

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void RaiseCanExecuteChanged()
    {
        CommandManager.InvalidateRequerySuggested();
    }

Dessa forma, os assinantes se conectam à CommandManagerclasse e não à sua classe e podem participar adequadamente das alterações no status do comando.

Andrue Cope
fonte
2
Simples, direto ao ponto, e permite que as pessoas tenham controle sobre suas implementações ICommand.
Akoi Meexx
2

Eu implementei uma solução para lidar com a dependência de propriedade nos comandos, aqui o link https://stackoverflow.com/a/30394333/1716620

graças a isso, você terá um comando como este:

this.SaveCommand = new MyDelegateCommand<MyViewModel>(this,
    //execute
    () => {
      Console.Write("EXECUTED");
    },
    //can execute
    () => {
      Console.Write("Checking Validity");
       return PropertyX!=null && PropertyY!=null && PropertyY.Length < 5;
    },
    //properties to watch
    (p) => new { p.PropertyX, p.PropertyY }
 );
Não é importante
fonte
-3

Isto é o que funcionou para mim: Coloque o CanExecute antes do comando no XAML.

rmustakos
fonte