WPP CommandParameter est NULL la première fois que CanExecute est appelé

J’ai rencontré un problème avec WPF et les commandes liées à un bouton dans DataTemplate d’un ItemsControl. Le scénario est assez simple. ItemsControl est lié à une liste d’objects et je souhaite pouvoir supprimer chaque object de la liste en cliquant sur un bouton. Le bouton exécute une commande et la commande prend en charge la suppression. Le CommandParameter est lié à l’object que je veux supprimer. De cette façon, je sais ce que l’utilisateur a cliqué. Un utilisateur ne devrait pouvoir supprimer que ses “propres” objects. Je dois donc faire quelques vérifications dans l’appel “CanExecute” de la commande pour vérifier que l’utilisateur dispose des permissions appropriées.

Le problème est que le paramètre passé à CanExecute est NULL la première fois qu’il est appelé. Je ne peux donc pas exécuter la logique pour activer / désactiver la commande. Toutefois, si je l’activer, puis cliquez sur le bouton pour exécuter la commande, le paramètre CommandParameter est transmis correctement. Cela signifie donc que la liaison avec le CommandParameter fonctionne.

Le XAML pour le ItemsControl et le DataTemplate ressemble à ceci:

    

Donc, comme vous pouvez le voir, j’ai une liste d’objects Commentaires. Je souhaite que le CommandParameter de DeleteCommentCommand soit lié à l’object Command.

Je suppose donc que ma question est la suivante: est-ce que quelqu’un a déjà rencontré ce problème auparavant? CanExecute est appelé sur ma commande, mais le paramètre est toujours NULL la première fois – pourquoi?

Mise à jour: j’ai pu réduire un peu le problème. J’ai ajouté un Debug ValueConverter vide pour pouvoir afficher un message lorsque le paramètre CommandParameter est lié aux données. Le problème est que la méthode CanExecute est exécutée avant que le CommandParameter soit lié au bouton. J’ai essayé de définir le paramètre CommandParameter avant la commande (comme suggéré) – mais cela ne fonctionne toujours pas. Tous les conseils sur la façon de le contrôler.

Update2: Existe t-il un moyen de détecter le moment où la liaison est effectuée, afin de pouvoir forcer la réévaluation de la commande? En outre, est-ce un problème que j’ai plusieurs boutons (un pour chaque élément dans le ItemsControl) qui se lient à la même instance d’un object de commande?

Mise à jour 3: J’ai téléchargé une reproduction du bogue sur mon SkyDrive: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip

Je suis tombé sur un problème similaire et l’ai résolu en utilisant mon fidèle 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 sortingggers 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 } 

Ce convertisseur de valeur prend un nombre quelconque de parameters et transmet le premier d'entre eux comme valeur convertie. Lorsqu'il est utilisé dans un MultiBinding dans votre cas, il ressemble à ceci.

          

Vous devrez append TriggerConverter en tant que ressource quelque part pour que cela fonctionne. Maintenant, la propriété Command n'est pas définie avant que la valeur du paramètre CommandParameter ne soit disponible. Vous pouvez même vous lier à RelativeSource.Self et CommandParameter au lieu de. pour obtenir le même effet.

J’avais ce même problème en essayant de lier une commande sur mon modèle de vue.

Je l’ai changé pour utiliser une liaison de source relative plutôt que de faire référence à l’élément par nom et cela a fait l’affaire. La liaison des parameters n’a pas changé.

Ancien code:

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

Nouveau code:

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

Mise à jour : Je viens de trouver ce problème sans utiliser ElementName, je lie à une commande sur mon modèle de vue et mon contexte de données du bouton est mon modèle de vue. Dans ce cas, je devais simplement déplacer l’atsortingbut CommandParameter avant l’atsortingbut Command dans la déclaration Button (dans XAML).

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

J’ai trouvé que l’ordre dans lequel je définissais la commande et le paramètre de commande fait une différence. Si vous définissez la propriété Command, CanExecute est appelé immédiatement. Par conséquent, vous souhaitez que CommandParameter soit déjà défini à ce stade.

J’ai trouvé que changer l’ordre des propriétés dans le XAML peut effectivement avoir un effet, même si je ne suis pas certain que cela résoudra votre problème. Cela vaut la peine d’essayer.

Vous semblez suggérer que le bouton ne soit jamais activé, ce qui est surprenant, car je m’attendrais à ce que le CommandParameter soit défini peu de temps après la propriété Command dans votre exemple. L’appel de CommandManager.InvalidateRequerySuggested () entraîne-t-il l’activation du bouton?

Je sais que ce sujet est assez ancien, mais j’ai proposé une autre option pour contourner ce problème que je voulais partager. Étant donné que la méthode CanExecute de la commande est exécutée avant que la propriété CommandParameter soit définie, j’ai créé une classe d’assistance avec une propriété attachée qui oblige la méthode CanExecute à être appelée à nouveau lorsque la liaison change.

 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); } } 

Et puis sur le bouton vous voulez lier un paramètre de commande à …

  

J’espère que cela aidera peut-être quelqu’un d’autre avec le problème.

Ceci est un vieux sujet, mais depuis que Google m’a amené ici quand j’ai eu ce problème, je vais append ce qui a fonctionné pour moi pour un DataGridTemplateColumn avec un bouton.

Changer la liaison de:

 CommandParameter="{Binding .}" 

à

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

Je ne sais pas pourquoi ça marche, mais ça a fait pour moi.

Vous pourrez peut-être utiliser mon CommandParameterBehavior que j’ai posté sur les forums Prism hier. Il ajoute le comportement manquant lorsqu’une modification du CommandParameter entraîne une nouvelle CommandParameter la Command .

Il y a une certaine complexité causée par mes tentatives pour éviter la fuite de mémoire provoquée si vous appelez PropertyDescriptor.AddValueChanged sans appeler plus tard PropertyDescriptor.RemoveValueChanged . J’essaie de résoudre ce problème en désinscrivant le gestionnaire lorsque l’ekement est déchargé.

Vous devrez probablement supprimer les éléments IDelegateCommand moins que vous n’utilisiez Prism (et que vous souhaitiez apporter les mêmes modifications à la bibliothèque Prism). Notez également que nous n’utilisons généralement pas les commandes RoutedCommand ici (nous utilisons DelegateCommand Prism à peu près tout) donc ne me tenez pas responsable si mon appel à CommandManager.InvalidateRequerySuggested déclenche une sorte de cascade d’effondrement des ondes quantiques. détruit l’univers connu ou n’importe quoi.

 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 /// sortinggger 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(); } } } } 

Il existe un moyen relativement simple de “corriger” ce problème avec DelegateCommand, bien qu’il nécessite la mise à jour de la source DelegateCommand et la recompilation de Microsoft.Practices.Composite.Presentation.dll.

1) Téléchargez le code source de Prism 1.2 et ouvrez CompositeApplicationLibrary_Desktop.sln. Voici un projet Composite.Presentation.Desktop qui contient la source DelegateCommand.

2) Sous l’événement public EventHandler CanExecuteChanged, modifiez pour lire comme suit:

 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) Sous le numéro virtuel protégé OnCanExecuteChanged (), modifiez-le comme suit:

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

4) Recomstackz la solution, puis naviguez vers le dossier Debug ou Release où les DLL compilées sont en ligne. Copiez les fichiers Microsoft.Practices.Composite.Presentation.dll et .pdb (si vous le souhaitez) à l’endroit où vous référencez vos assemblys externes, puis recomstackz votre application pour extraire les nouvelles versions.

Après cela, CanExecute doit être déclenché chaque fois que l’interface utilisateur rend des éléments liés à DelegateCommand en question.

Prends soin de toi, Joe

arbitre à gmail

Après avoir lu quelques bonnes réponses à des questions similaires, j’ai légèrement modifié l’exemple de DelegateCommand pour le faire fonctionner. À la place d’utiliser:

 public event EventHandler CanExecuteChanged; 

Je l’ai changé pour:

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

J’ai supprimé les deux méthodes suivantes parce que j’étais trop paresseux pour les corriger

 public void RaiseCanExecuteChanged() 

et

 protected virtual void OnCanExecuteChanged() 

Et c’est tout … cela semble assurer que CanExecute sera appelé lorsque la liaison change et après la méthode Execute

Il ne se déclenchera pas automatiquement si ViewModel est modifié, mais comme mentionné dans ce thread est possible en appelant le CommandManager.InvalidateRequerySuggested sur le thread d’interface graphique

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

Salut Jonas, je ne sais pas si cela fonctionnera dans un modèle de données, mais voici la syntaxe de liaison que j’utilise dans un menu contextuel ListView pour récupérer l’élément actuel en tant que paramètre de commande:

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

Je l’ai enregistré comme un bogue contre WPF dans .Net 4.0, car le problème persiste dans la version bêta 2.

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

Certaines de ces réponses concernent la liaison à DataContext pour obtenir la commande elle-même, mais la question concernait le paramètre CommandParameter étant nul lorsqu’il ne devrait pas l’être. Nous avons également vécu cela. Sur une intuition, nous avons trouvé un moyen très simple pour que cela fonctionne dans notre ViewModel. Ceci est spécifiquement pour le problème null CommandParameter signalé par le client, avec une ligne de code. Notez le 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; } } 

C’est un long coup. pour le déboguer, vous pouvez essayer:
– vérifier l’événement PreviewCanExecute.
– Utilisez snoop / wpf mole pour voir à l’intérieur de quel paramètre est le paramètre de commande.

HTH,

Le commandManager.InvalidateRequerySuggested fonctionne également pour moi. Je crois que le lien suivant parle d’un problème similaire, et M $ dev a confirmé la limitation dans la version actuelle, et la commande commandManager.InvalidateRequerySuggested est la solution de contournement. http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/

Ce qui est important, c’est le moment d’invoquer commandManager.InvalidateRequerySuggested. Cela doit être appelé après la notification de la modification de valeur correspondante.

À côté de la suggestion d’Ed Ball sur la configuration de CommandParameter avant la commande , assurez-vous que votre méthode CanExecute a un paramètre de type d’ object .

bool privé OnDeleteSelectedItemsCanExecute (object SelectedItems)
{

  // Your goes heres 

}

J’espère que cela empêche quelqu’un de passer le temps qu’il a fallu pour comprendre comment recevoir SelectedItems en tant que paramètre CanExecute