WPF CommandParameter é NULL primeira vez CanExecute é chamado

Eu tive um problema com o WPF e Comandos que estão vinculados a um Button dentro do DataTemplate de um ItemsControl. O cenário é bem direto. O ItemsControl está vinculado a uma lista de objects, e eu quero ser capaz de remover cada object na lista, clicando em um botão. O botão executa um comando e o comando cuida da exclusão. O CommandParameter está vinculado ao object que desejo excluir. Dessa forma, eu sei o que o usuário clicou. Um usuário só deve poder excluir seus objects “próprios” – por isso, preciso fazer algumas verificações na chamada “CanExecute” do comando para verificar se o usuário tem as permissions corretas.

O problema é que o parâmetro passado para CanExecute é NULL na primeira vez que é chamado – portanto, não posso executar a lógica para ativar / desativar o comando. No entanto, se eu fizer isso sempre ativado e, em seguida, clique no botão para executar o comando, o CommandParameter é passado corretamente. Então isso significa que a binding contra o CommandParameter está funcionando.

O XAML para o ItemsControl e o DataTemplate é semelhante a este:

    

Então, como você pode ver, eu tenho uma lista de objects Comentários. Eu quero o CommandParameter do DeleteCommentCommand para ser vinculado ao object de comando.

Então, eu acho que a minha pergunta é: alguém já experimentou esse problema antes? CanExecute é chamado no meu comando, mas o parâmetro é sempre NULL na primeira vez – por que isso?

Atualização: consegui reduzir um pouco o problema. Eu adicionei um vazio Debug ValueConverter para que eu pudesse produzir uma mensagem quando o CommandParameter está ligado a dados. Acontece que o problema é que o método CanExecute é executado antes do CommandParameter ser ligado ao botão. Eu tentei definir o CommandParameter antes do comando (como sugerido) – mas ainda não funciona. Alguma dica sobre como controlá-lo.

Update2: Existe alguma maneira de detectar quando a binding é “feita”, para que eu possa forçar a reavaliação do comando? Além disso – é um problema que eu tenho vários botões (um para cada item no ItemsControl) que se ligam à mesma instância de um object de comando?

Update3: Eu carreguei uma reprodução do bug no meu SkyDrive: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip

Eu tropecei em um problema semelhante e resolvi usando meu confiável TriggerConverter.

 public class TriggerConverter : IMultiValueConverter { #region IMultiValueConverter Members public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // First value is target value. // All others are update triggers only. if (values.Length < 1) return Binding.DoNothing; return values[0]; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } 

Este conversor de valor aceita qualquer número de parâmetros e passa o primeiro deles de volta como o valor convertido. Quando usado em um MultiBinding no seu caso, parece o seguinte.

          

Você terá que adicionar o TriggerConverter como um recurso em algum lugar para que isso funcione. Agora a propriedade Command é definida não antes do valor do CommandParameter se tornar disponível. Você pode até ligar para RelativeSource.Self e CommandParameter em vez de. para conseguir o mesmo efeito.

Eu estava tendo esse mesmo problema ao tentar vincular a um comando no meu modelo de modo de exibição.

Eu mudei para usar uma binding de origem relativa, em vez de referir-se ao elemento pelo nome e que fez o truque. A binding de parâmetro não mudou.

Código antigo:

 Command="{Binding DataContext.MyCommand, ElementName=myWindow}" 

Novo Código:

 Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=Views:MyView}}" 

Update : Acabei de me deparar com este problema sem usar ElementName, estou ligando para um comando no meu modelo de visão e meu contexto de dados do botão é o meu modelo de visão. Nesse caso, tive que simplesmente mover o atributo CommandParameter antes do atributo Command na declaração Button (em XAML).

 CommandParameter="{Binding Groups}" Command="{Binding StartCommand}" 

Descobri que a ordem na qual eu defini Command e CommandParameter faz a diferença. Definir a propriedade Command faz com que CanExecute seja chamado imediatamente, portanto, você deseja que o CommandParameter já esteja definido nesse ponto.

Eu descobri que mudar a ordem das propriedades no XAML pode realmente ter um efeito, embora eu não esteja confiante de que isso resolverá seu problema. Vale a pena tentar, no entanto.

Você parece estar sugerindo que o botão nunca fica habilitado, o que é surpreendente, já que eu esperaria que o CommandParameter fosse definido logo após a propriedade Command no seu exemplo. A chamada de CommandManager.InvalidateRequerySuggested () faz com que o botão seja ativado?

Eu sei que esse segmento é meio antigo, mas descobri outra opção para contornar esse problema que queria compartilhar. Como o método CanExecute do comando é executado antes de a propriedade CommandParameter ser definida, criei uma class auxiliar com uma propriedade anexada que força o método CanExecute a ser chamado novamente quando a binding for alterada.

 public static class ButtonHelper { public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached( "CommandParameter", typeof(object), typeof(ButtonHelper), new PropertyMetadata(CommandParameter_Changed)); private static void CommandParameter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { var target = d as ButtonBase; if (target == null) return; target.CommandParameter = e.NewValue; var temp = target.Command; // Have to set it to null first or CanExecute won't be called. target.Command = null; target.Command = temp; } public static object GetCommandParameter(ButtonBase target) { return target.GetValue(CommandParameterProperty); } public static void SetCommandParameter(ButtonBase target, object value) { target.SetValue(CommandParameterProperty, value); } } 

E então no botão você deseja ligar um parâmetro de comando para …

  

Espero que isso ajude alguém com o problema.

Este é um tópico antigo, mas desde que o Google me trouxe aqui quando eu tive esse problema, adicionarei o que funcionou para mim para um DataGridTemplateColumn com um botão.

Altere a binding de:

 CommandParameter="{Binding .}" 

para

 CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}" 

Não tenho certeza porque funciona, mas fez por mim.

Você pode usar meu CommandParameterBehavior que CommandParameterBehavior nos fóruns do Prism ontem. Ele adiciona o comportamento ausente quando uma alteração no CommandParameter faz com que o Command seja reexibido.

Há alguma complexidade aqui causada por minhas tentativas de evitar o memory leaks causado se você chamar PropertyDescriptor.AddValueChanged sem posteriormente chamar PropertyDescriptor.RemoveValueChanged . Eu tento corrigir isso cancelando o registro do manipulador quando o ekement é descarregado.

Você provavelmente precisará remover o material do IDelegateCommand , a menos que esteja usando o Prism (e queira fazer as mesmas alterações que eu na biblioteca Prism). Note também que nós geralmente não usamos RoutedCommand aqui (usamos DelegateCommand de Prism DelegateCommand para praticamente tudo) então por favor não me responsabilizem se minha chamada para CommandManager.InvalidateRequerySuggested desencadeia algum tipo de cascata de colapso quântico de wavefuntion que destrói o universo conhecido ou qualquer coisa.

 using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace Microsoft.Practices.Composite.Wpf.Commands { ///  /// This class provides an attached property that, when set to true, will cause changes to the element's CommandParameter to /// trigger the CanExecute handler to be called on the Command. ///  public static class CommandParameterBehavior { ///  /// Identifies the IsCommandRequeriedOnChange attached property ///  ///  /// When a control has the  /// attached property set to true, then any change to it's ///  property will cause the state of /// the command attached to it's  property to /// be reevaluated. ///  public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty = DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange", typeof(bool), typeof(CommandParameterBehavior), new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged))); ///  /// Gets the value for the  attached property. ///  /// The object to adapt. /// Whether the update on change behavior is enabled. public static bool GetIsCommandRequeriedOnChange(DependencyObject target) { return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty); } ///  /// Sets the  attached property. ///  /// The object to adapt. This is typically a , ///  or  /// Whether the update behaviour should be enabled. public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value) { target.SetValue(IsCommandRequeriedOnChangeProperty, value); } private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is ICommandSource)) return; if (!(d is FrameworkElement || d is FrameworkContentElement)) return; if ((bool)e.NewValue) { HookCommandParameterChanged(d); } else { UnhookCommandParameterChanged(d); } UpdateCommandState(d); } private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source) { return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"]; } private static void HookCommandParameterChanged(object source) { var propertyDescriptor = GetCommandParameterPropertyDescriptor(source); propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged); // NB Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected, // so we need to hook the Unloaded event and call RemoveValueChanged there. HookUnloaded(source); } private static void UnhookCommandParameterChanged(object source) { var propertyDescriptor = GetCommandParameterPropertyDescriptor(source); propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged); UnhookUnloaded(source); } private static void HookUnloaded(object source) { var fe = source as FrameworkElement; if (fe != null) { fe.Unloaded += OnUnloaded; } var fce = source as FrameworkContentElement; if (fce != null) { fce.Unloaded += OnUnloaded; } } private static void UnhookUnloaded(object source) { var fe = source as FrameworkElement; if (fe != null) { fe.Unloaded -= OnUnloaded; } var fce = source as FrameworkContentElement; if (fce != null) { fce.Unloaded -= OnUnloaded; } } static void OnUnloaded(object sender, RoutedEventArgs e) { UnhookCommandParameterChanged(sender); } static void OnCommandParameterChanged(object sender, EventArgs ea) { UpdateCommandState(sender); } private static void UpdateCommandState(object target) { var commandSource = target as ICommandSource; if (commandSource == null) return; var rc = commandSource.Command as RoutedCommand; if (rc != null) { CommandManager.InvalidateRequerySuggested(); } var dc = commandSource.Command as IDelegateCommand; if (dc != null) { dc.RaiseCanExecuteChanged(); } } } } 

Há uma maneira relativamente simples de “corrigir” esse problema com o DelegateCommand, embora seja necessário atualizar a fonte DelegateCommand e recompilar o arquivo Microsoft.Practices.Composite.Presentation.dll.

1) Faça o download do código-fonte do Prism 1.2 e abra o CompositeApplicationLibrary_Desktop.sln. Aqui está um projeto Composite.Presentation.Desktop que contém a fonte DelegateCommand.

2) Sob o evento público EventHandler CanExecuteChanged, modifique para ler da seguinte maneira:

 public event EventHandler CanExecuteChanged { add { WeakEventHandlerManager.AddWeakReferenceHandler( ref _canExecuteChangedHandlers, value, 2 ); // add this line CommandManager.RequerySuggested += value; } remove { WeakEventHandlerManager.RemoveWeakReferenceHandler( _canExecuteChangedHandlers, value ); // add this line CommandManager.RequerySuggested -= value; } } 

3) Em vazio virtual protegido OnCanExecuteChanged (), modifique-o da seguinte maneira:

 protected virtual void OnCanExecuteChanged() { // add this line CommandManager.InvalidateRequerySuggested(); WeakEventHandlerManager.CallWeakReferenceHandlers( this, _canExecuteChangedHandlers ); } 

4) Recompile a solução e navegue até a pasta Debug ou Release onde as DLLs compiladas residem. Copie o Microsoft.Practices.Composite.Presentation.dll e .pdb (se desejar) para onde você faz referência a seus assemblies externos e, em seguida, recompile seu aplicativo para extrair as novas versões.

Depois disso, CanExecute deve ser triggersdo toda vez que a interface do usuário renderizar elementos vinculados ao DelegateCommand em questão.

Tome cuidado, Joe

refereejoe no gmail

Depois de ler algumas boas respostas a perguntas semelhantes, mudei no seu exemplo o DelegateCommand para fazer com que funcionasse. Ao invés de usar:

 public event EventHandler CanExecuteChanged; 

Eu mudei para:

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

Eu removi os dois methods a seguir porque eu estava com preguiça de consertá-los

 public void RaiseCanExecuteChanged() 

e

 protected virtual void OnCanExecuteChanged() 

E isso é tudo … isso parece garantir que CanExecute será chamado quando as alterações de vinculação e após o método de execução

Ele não será acionado automaticamente se o ViewModel for alterado, mas, como mencionado neste thread, é possível chamar o CommandManager.InvalidateRequerySuggested no encadeamento da GUI

 Application.Current?.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)CommandManager.InvalidateRequerySuggested); 

Ei Jonas, não tenho certeza se isso funcionará em um modelo de dados, mas aqui está a syntax de binding que eu uso em um menu de contexto ListView para pegar o item atual como um parâmetro de comando:

CommandParameter = “{RelativeSource = {RelativeSource AncestorType = ContextMenu}, Caminho = PlacementTarget.SelectedItem, Mode = TwoWay}”

Eu registrei isso como um bug contra o WPF no .net 4.0, como o problema ainda existe no Beta 2.

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976

Algumas dessas respostas são sobre a vinculação ao DataContext para obter o comando em si, mas a questão era sobre o parâmetro CommandParameter sendo nulo quando não deveria ser. Nós também experimentamos isso. Em um palpite, encontramos uma maneira muito simples de fazer isso funcionar em nosso ViewModel. Isso é especificamente para o problema nulo CommandParameter relatado pelo cliente, com uma linha de código. Observe o Dispatcher.BeginInvoke ().

 public DelegateCommand CommandShowReport { get { // create the command, or pass what is already created. var command = _commandShowReport ?? (_commandShowReport = new DelegateCommand(OnCommandShowReport, OnCanCommandShowReport)); // For the item template, the OnCanCommand will first pass in null. This will tell the command to re-pass the command param to validate if it can execute. Dispatcher.BeginInvoke((Action) delegate { command.RaiseCanExecuteChanged(); }, DispatcherPriority.DataBind); return command; } } 

É um tiro no escuro. para depurar isso, você pode tentar:
– verificando o evento PreviewCanExecute.
– use snoop / wpf mole para espiar dentro e ver o que é o commandparameter.

HTH,

O commandManager.InvalidateRequerySuggested também funciona para mim. Eu acredito que o seguinte link fala sobre um problema similar, e o M $ dev confirmou a limitação na versão atual, e o commandManager.InvalidateRequerySuggested é a solução alternativa. http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/

O importante é o tempo de invocar o commandManager.InvalidateRequerySuggested. Isso deve ser chamado depois que a alteração de valor relevante for notificada.

Ao lado da sugestão de Ed Ball sobre a configuração de CommandParameter antes do comando , verifique se o método CanExecute tem um parâmetro do tipo de object .

private bool OnDeleteSelectedItemsCanExecute (object SelectedItems)
{

  // Your goes heres 

}

Espero que isso evite que alguém gaste a enorme quantidade de tempo que fiz para descobrir como receber o parâmetro SelectedItems as CanExecute