Eu tive um problema com o WPF e os comandos vinculados a um botão dentro do DataTemplate de um ItemsControl. O cenário é bastante simples. O ItemsControl está vinculado a uma lista de objetos e quero poder remover cada objeto da lista clicando em um botão. O Botão executa um Comando e o Comando cuida da exclusão. O CommandParameter está vinculado ao objeto que desejo excluir. Assim eu sei o que o usuário clicou. Um usuário só deve ser capaz de excluir seus "próprios" objetos - portanto, preciso fazer algumas verificações na chamada "CanExecute" do Comando para verificar se o usuário tem as permissões corretas.
O problema é que o parâmetro passado para CanExecute é NULL na primeira vez que é chamado - então não consigo executar a lógica para habilitar / desabilitar o comando. No entanto, se eu ativá-lo sempre e clicar no botão para executar o comando, o CommandParameter é passado corretamente. Isso significa que a vinculação ao CommandParameter está funcionando.
O XAML para ItemsControl e DataTemplate se parece com isto:
<ItemsControl
x:Name="commentsList"
ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
Width="Auto" Height="Auto">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button
Content="Delete"
FontSize="10"
Command="{Binding Path=DataContext.DeleteCommentCommand, ElementName=commentsList}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Como você pode ver, tenho uma lista de objetos Comentários. Eu quero que o CommandParameter do DeleteCommentCommand seja vinculado ao objeto Command.
Portanto, acho que 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 Debug ValueConverter vazio para que eu pudesse enviar uma mensagem quando o CommandParameter estiver vinculado aos dados. Acontece que o problema é que o método CanExecute é executado antes que CommandParameter seja vinculado 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 ligação está "concluída", 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 objeto 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
Respostas:
Me deparei com 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 pega qualquer número de parâmetros e passa o primeiro deles de volta como o valor convertido. Quando usado em uma MultiBinding em seu caso, tem a seguinte aparência.
<ItemsControl x:Name="commentsList" ItemsSource="{Binding Path=SharedDataItemPM.Comments}" Width="Auto" Height="Auto"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Button Content="Delete" FontSize="10" CommandParameter="{Binding}"> <Button.Command> <MultiBinding Converter="{StaticResource TriggerConverter}"> <Binding Path="DataContext.DeleteCommentCommand" ElementName="commentsList" /> <Binding /> </MultiBinding> </Button.Command> </Button> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
Você terá que adicionar TriggerConverter como um recurso em algum lugar para que isso funcione. Agora, a propriedade Command não é definida antes que o valor para CommandParameter esteja disponível. Você pode até mesmo vincular a RelativeSource.Self e CommandParameter em vez de. para obter o mesmo efeito.
fonte
Eu estava tendo o mesmo problema ao tentar vincular a um comando no meu modelo de exibição.
Eu mudei para usar uma associação de origem relativa em vez de me referir ao elemento pelo nome e isso resolveu o problema. A vinculação de parâmetros não mudou.
Código Antigo:
Novo Código:
Update : Acabei de me deparar com esse problema sem usar ElementName, estou vinculando a um comando em meu modelo de exibição e meu contexto de dados do botão é meu modelo de exibição. Nesse caso, tive que simplesmente mover o atributo CommandParameter antes do atributo Command na declaração de Button (em XAML).
fonte
CommandParameter
eCommand
me assusta.Descobri que a ordem em que defino Command e CommandParameter faz diferença. Definir a propriedade Command faz com que CanExecute seja chamado imediatamente, portanto, você deseja que CommandParameter já esteja definido nesse ponto.
Descobri que mudar a ordem das propriedades no XAML pode realmente ter um efeito, embora não tenha certeza de que isso resolverá seu problema. Mas vale a pena tentar.
Você parece estar sugerindo que o botão nunca é habilitado, o que é surpreendente, pois eu esperaria que o CommandParameter fosse definido logo após a propriedade Command em seu exemplo. Chamar CommandManager.InvalidateRequerySuggested () faz com que o botão seja habilitado?
fonte
Eu encontrei outra opção para contornar esse problema que gostaria de compartilhar. Como o método CanExecute do comando é executado antes que a propriedade CommandParameter seja definida, criei uma classe auxiliar com uma propriedade anexada que força o método CanExecute a ser chamado novamente quando a ligação muda.
E então no botão que você deseja vincular um parâmetro de comando a ...
<Button Content="Press Me" Command="{Binding}" helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
Espero que isso talvez ajude alguém com o problema.
fonte
Este é um tópico antigo, mas como o Google me trouxe aqui quando tive esse problema, adicionarei o que funcionou para mim para um DataGridTemplateColumn com um botão.
Altere a ligação de:
para
Não sei por que funciona, mas funcionou para mim.
fonte
Recentemente me deparei com o mesmo problema (para mim era para os itens de menu em um menu de contexto), mas embora possa não ser uma solução adequada para cada situação, encontrei uma maneira diferente (e muito mais curta!) De resolver isso problema:
<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />
Ignorando a
Tag
solução alternativa baseada em - para o caso especial de menu de contexto, a chave aqui é vincular oCommandParameter
regularmente, mas vincular oCommand
com o adicionalIsAsync=True
. Isso atrasará um pouco a vinculação do comando real (e, portanto, suaCanExecute
chamada), de modo que o parâmetro já estará disponível. Isso significa, porém, que por um breve momento, o estado ativado pode estar errado, mas para o meu caso, isso era perfeitamente aceitável.fonte
Você pode usar o
CommandParameterBehavior
que eu postei nos fóruns do Prism ontem. Ele adiciona o comportamento ausente onde uma mudança naCommandParameter
causa doCommand
com que seja novamente consultado.Há alguma complexidade aqui causada por minhas tentativas de evitar o vazamento de memória causado se você ligar
PropertyDescriptor.AddValueChanged
sem chamar mais tardePropertyDescriptor.RemoveValueChanged
. Tento consertar isso cancelando o registro do manipulador quando o ekement é descarregado.Você provavelmente precisará remover o
IDelegateCommand
material, a menos que esteja usando o Prism (e queira fazer as mesmas alterações que eu fiz na biblioteca do Prism). Observe também que geralmente não usamosRoutedCommand
s aqui (usamos PrismDelegateCommand<T>
para praticamente tudo), então, por favor, não me responsabilize se meu apelo paraCommandManager.InvalidateRequerySuggested
desencadear algum tipo de cascata de colapso de função de onda quântica que destrua o universo conhecido ou qualquer coisa.using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace Microsoft.Practices.Composite.Wpf.Commands { /// <summary> /// 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. /// </summary> public static class CommandParameterBehavior { /// <summary> /// Identifies the IsCommandRequeriedOnChange attached property /// </summary> /// <remarks> /// When a control has the <see cref="IsCommandRequeriedOnChangeProperty" /> /// attached property set to true, then any change to it's /// <see cref="System.Windows.Controls.Primitives.ButtonBase.CommandParameter" /> property will cause the state of /// the command attached to it's <see cref="System.Windows.Controls.Primitives.ButtonBase.Command" /> property to /// be reevaluated. /// </remarks> public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty = DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange", typeof(bool), typeof(CommandParameterBehavior), new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged))); /// <summary> /// Gets the value for the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property. /// </summary> /// <param name="target">The object to adapt.</param> /// <returns>Whether the update on change behavior is enabled.</returns> public static bool GetIsCommandRequeriedOnChange(DependencyObject target) { return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty); } /// <summary> /// Sets the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property. /// </summary> /// <param name="target">The object to adapt. This is typically a <see cref="System.Windows.Controls.Primitives.ButtonBase" />, /// <see cref="System.Windows.Controls.MenuItem" /> or <see cref="System.Windows.Documents.Hyperlink" /></param> /// <param name="value">Whether the update behaviour should be enabled.</param> 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); // N.B. 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(); } } } }
fonte
Há uma maneira relativamente simples de "consertar" esse problema com DelegateCommand, embora exija a atualização da fonte DelegateCommand e a recompilação do Microsoft.Practices.Composite.Presentation.dll.
1) Baixe o 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) No evento público EventHandler CanExecuteChanged, modifique para ler da seguinte forma:
3) Sob o vazio virtual protegido OnCanExecuteChanged (), modifique-o da seguinte forma:
4) Recompile a solução e navegue até a pasta Debug ou Release onde residem as DLLs compiladas. Copie Microsoft.Practices.Composite.Presentation.dll e .pdb (se desejar) para onde você faz referência aos assemblies externos e, em seguida, recompile seu aplicativo para obter as novas versões.
Depois disso, CanExecute deve ser disparado sempre que a IU renderiza elementos vinculados ao DelegateCommand em questão.
Se cuida Joe
árbitro no gmail
fonte
Depois de ler algumas boas respostas para perguntas semelhantes, mudei levemente em seu exemplo o DelegateCommand para fazê-lo funcionar. Ao invés de usar:
Eu mudei para:
Removi os dois métodos a seguir porque tinha preguiça de corrigi-los
e
E isso é tudo ... isso parece garantir que CanExecute será chamado quando o Binding mudar e após o método Execute
Ele não será acionado automaticamente se ViewModel for alterado, mas conforme mencionado neste thread, é possível chamar CommandManager.InvalidateRequerySuggested no thread da GUI
fonte
DispatcherPriority.Normal
é muito alto para funcionar de forma confiável (ou em tudo, no meu caso). UsarDispatcherPriority.Loaded
funciona bem e parece mais apropriado (ou seja, indica explicitamente que o delegado não deve ser invocado até que os elementos de interface do usuário associados ao modelo de exibição tenham de fato sido carregados).Ei Jonas, não tenho certeza se isso vai funcionar em um modelo de dados, mas aqui está a sintaxe de ligação que eu uso em um menu de contexto ListView para pegar o item atual como um parâmetro de comando:
CommandParameter = "{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu}, Path = PlacementTarget.SelectedItem, Mode = TwoWay}"
fonte
Registrei isso como um bug contra o WPF no .Net 4.0, pois o problema ainda existe no Beta 2.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
fonte
Algumas dessas respostas são sobre a vinculação ao DataContext para obter o próprio Comando, mas a questão era sobre o CommandParameter ser nulo quando não deveria ser. Nós também experimentamos isso. Seguindo um palpite, encontramos uma maneira muito simples de fazer isso funcionar em nosso ViewModel. Isso é especificamente para o problema nulo de CommandParameter relatado pelo cliente, com uma linha de código. Observe o Dispatcher.BeginInvoke ().
public DelegateCommand<objectToBePassed> CommandShowReport { get { // create the command, or pass what is already created. var command = _commandShowReport ?? (_commandShowReport = new DelegateCommand<object>(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; } }
fonte
É um tiro longo. para depurar isso, você pode tentar:
- verificar o evento PreviewCanExecute.
- use snoop / wpf mole para espiar dentro e ver o que o parâmetro de comando é.
HTH,
fonte
O commandManager.InvalidateRequerySuggested funciona para mim também. Acredito que o link a seguir fala sobre um problema semelhante, e 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 invocação do commandManager.InvalidateRequerySuggested. Isso deve ser chamado depois que a mudança de valor relevante for notificada.
fonte
Ao lado da sugestão de Ed Ball sobre a configuração de CommandParameter antes de Command , certifique-se de que seu método CanExecute tenha um parâmetro de tipo de objeto .
Espero que isso evite que alguém gaste tanto tempo que eu gastei tentando descobrir como receber SelectedItems como parâmetro CanExecute
fonte