J'ai rencontré un problème avec WPF et les commandes liées à un bouton dans le DataTemplate d'un ItemsControl. Le scénario est assez simple. Le ItemsControl est lié à une liste d'objets, et je souhaite pouvoir supprimer chaque objet de la liste en cliquant sur un bouton. Le bouton exécute une commande et la commande se charge de la suppression. Le CommandParameter est lié à l'objet que je souhaite supprimer. De cette façon, je sais sur quoi l'utilisateur a cliqué. Un utilisateur ne doit pouvoir supprimer que ses "propres" objets - je dois donc faire quelques vérifications dans l'appel "CanExecute" de la commande pour vérifier que l'utilisateur dispose des bonnes autorisations.
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. Cependant, si je le fais toujours activé, puis que je clique sur le bouton pour exécuter la commande, le CommandParameter est transmis correctement. Cela signifie donc que la liaison avec CommandParameter fonctionne.
Le XAML pour le ItemsControl et le DataTemplate ressemble à ceci:
<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>
Donc, comme vous pouvez le voir, j'ai une liste d'objets Comments. Je veux que le CommandParameter de DeleteCommentCommand soit lié à l'objet Command.
Donc, je suppose que ma question est: est-ce que quelqu'un a déjà rencontré ce problème? CanExecute est appelé sur ma commande, mais le paramètre est toujours NULL la première fois - pourquoi?
Mise à jour: j'ai pu affiner un peu le problème. J'ai ajouté un Debug ValueConverter vide afin que je puisse sortir un message lorsque CommandParameter est lié aux données. Il s'avère que le problème est que la méthode CanExecute est exécutée avant que CommandParameter ne soit lié au bouton. J'ai essayé de définir le CommandParameter avant la commande (comme suggéré) - mais cela ne fonctionne toujours pas. Des conseils sur la façon de le contrôler.
Mise à jour2: Existe t-il un moyen de détecter quand la liaison est "terminée", afin que je puisse forcer la réévaluation de la commande? Est-ce aussi 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 objet de commande?
Update3: J'ai téléchargé une reproduction du bogue sur mon SkyDrive: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip
Réponses:
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 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 }
Ce convertisseur de valeur prend n'importe quel nombre de paramètres et renvoie le premier d'entre eux comme valeur convertie. Lorsqu'il est utilisé dans un MultiBinding dans votre cas, il ressemble à ce qui suit.
<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>
Vous devrez ajouter TriggerConverter en tant que ressource quelque part pour que cela fonctionne. Maintenant, la propriété Command est définie pas avant que la valeur de CommandParameter ne soit disponible. Vous pouvez même vous lier à RelativeSource.Self et CommandParameter au lieu de. pour obtenir le même effet.
la source
J'avais ce même problème en essayant de me lier à une commande sur mon modèle de vue.
Je l'ai changé pour utiliser une liaison source relative plutôt que de faire référence à l'élément par son nom et cela a fait l'affaire. La liaison des paramètres n'a pas changé.
Ancien code:
Nouveau code:
Mise à jour : Je viens de rencontrer ce problème sans utiliser ElementName, je suis lié à 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'attribut CommandParameter avant l'attribut Command dans la déclaration Button (en XAML).
la source
CommandParameter
etCommand
me fait peur.J'ai trouvé que l'ordre dans lequel je définis Command et CommandParameter fait une différence. La définition de la propriété Command entraîne l'appel immédiat de CanExecute, vous voulez donc que CommandParameter soit déjà défini à ce stade.
J'ai trouvé que changer l'ordre des propriétés dans le XAML peut en fait avoir un effet, même si je ne suis pas convaincu que cela résoudra votre problème. Cela vaut cependant la peine d'essayer.
Vous semblez suggérer que le bouton ne devient jamais activé, ce qui est surprenant, car je m'attendrais à ce que 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?
la source
J'ai trouvé 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 ne soit définie, j'ai créé une classe d'assistance avec une propriété attachée qui force la méthode CanExecute à être appelée à nouveau lorsque la liaison change.
Et puis sur le bouton auquel vous souhaitez lier un paramètre de commande ...
<Button Content="Press Me" Command="{Binding}" helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
J'espère que cela aidera peut-être quelqu'un d'autre à résoudre le problème.
la source
C'est un vieux fil de discussion, mais comme Google m'a amené ici lorsque j'ai eu ce problème, j'ajouterai ce qui a fonctionné pour moi pour un DataGridTemplateColumn avec un bouton.
Modifiez la liaison de:
à
Je ne sais pas pourquoi cela fonctionne, mais cela a fonctionné pour moi.
la source
Je suis récemment tombé sur le même problème (pour moi, c'était pour les éléments de menu dans un menu contextuel), et même si ce n'est peut-être pas une solution adaptée à chaque situation, j'ai trouvé une façon différente (et beaucoup plus courte!) De résoudre ce problème problème:
<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />
En ignorant la
Tag
solution de contournement basée sur le cas particulier du menu contextuel, la clé ici est de lier leCommandParameter
régulièrement, mais de lier leCommand
avec leIsAsync=True
. Cela retardera un peu la liaison de la commande réelle (et donc sonCanExecute
appel), de sorte que le paramètre sera déjà disponible. Cela signifie, cependant, que pendant un bref instant, l'état activé pourrait être faux, mais pour mon cas, c'était parfaitement acceptable.la source
Vous pourrez peut-être utiliser mon
CommandParameterBehavior
que j'ai posté sur les forums Prism hier. Il ajoute le comportement manquant où une modification de laCommandParameter
causeCommand
doit être interrogée.Il y a une certaine complexité ici causée par mes tentatives pour éviter la fuite de mémoire provoquée si vous appelez
PropertyDescriptor.AddValueChanged
sans appeler plus tardPropertyDescriptor.RemoveValueChanged
. J'essaie de résoudre ce problème en désenregistrant le gestionnaire lorsque l'ekement est déchargé.Vous devrez probablement supprimer le
IDelegateCommand
contenu à moins que vous n'utilisiez Prism (et que vous souhaitiez apporter les mêmes modifications que moi à la bibliothèque Prism). Notez également que nous n'utilisons généralement pasRoutedCommand
s ici (nous utilisons Prism'sDelegateCommand<T>
pour à peu près tout) donc ne me tenez pas responsable si mon appel àCommandManager.InvalidateRequerySuggested
déclencher une sorte de cascade d'effondrement de la fonction d'onde quantique qui détruit l'univers connu ou quoi que ce soit.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(); } } } }
la source
Il existe un moyen relativement simple de "résoudre" 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 le 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:
3) Sous le void virtuel protégé OnCanExecuteChanged (), modifiez-le comme suit:
4) Recompilez la solution, puis accédez au dossier Debug ou Release où se trouvent les DLL compilées. Copiez le Microsoft.Practices.Composite.Presentation.dll et .pdb (si vous le souhaitez) à l'endroit où vous référencez vos assemblys externes, puis recompilez 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 au DelegateCommand en question.
Prends soin de toi, Joe
refereejoe chez gmail
la source
Après avoir lu quelques bonnes réponses à des questions similaires, j'ai légèrement modifié dans votre exemple DelegateCommand pour le faire fonctionner. À la place d'utiliser:
Je l'ai changé en:
J'ai supprimé les deux méthodes suivantes car j'étais trop paresseux pour les réparer
et
Et c'est tout ... cela semble garantir que CanExecute sera appelé lorsque la liaison change et après la méthode Execute
Il ne se déclenchera pas automatiquement si le ViewModel est modifié, mais comme mentionné dans ce thread possible en appelant le CommandManager.InvalidateRequerySuggested sur le thread GUI
la source
DispatcherPriority.Normal
c'était trop élevé pour fonctionner de manière fiable (ou pas du tout, dans mon cas). L'utilisationDispatcherPriority.Loaded
fonctionne bien et semble plus appropriée (c'est-à-dire qu'elle indique explicitement que le délégué ne doit pas être appelé tant que les éléments d'interface utilisateur associés au modèle de vue n'ont pas été chargés).Hé 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 saisir l'élément actuel en tant que paramètre de commande:
CommandParameter = "{Relative RelativeSource = {RelativeSource AncestorType = ContextMenu}, Path = PlacementTarget.SelectedItem, Mode = TwoWay}"
la source
J'ai enregistré cela comme un bogue contre WPF dans .Net 4.0, car le problème existe toujours dans la bêta 2.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
la source
Certaines de ces réponses concernent la liaison au DataContext pour obtenir la commande elle-même, mais la question portait sur le fait que CommandParameter était nul alors qu'il ne devrait pas l'être. Nous avons également vécu cela. Sur une intuition, nous avons trouvé un moyen très simple de faire fonctionner cela dans notre ViewModel. Ceci est spécifiquement pour le problème nul CommandParameter signalé par le client, avec une ligne de code. Notez le 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; } }
la source
C'est un long coup. pour déboguer cela, vous pouvez essayer:
- de vérifier l'événement PreviewCanExecute.
- utilisez snoop / wpf mole pour jeter un œil à l'intérieur et voir quel est le paramètre de commande.
HTH,
la source
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 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 de l'appel de commandManager.InvalidateRequerySuggested. Cela doit être invoqué après la notification du changement de valeur concerné.
la source
À côté de la suggestion d'Ed Ball sur le réglage de CommandParameter avant Command , assurez-vous que votre méthode CanExecute a un paramètre de type d' objet .
J'espère que cela empêche quelqu'un de passer le temps énorme que j'ai consacré à comprendre comment recevoir SelectedItems en tant que paramètre CanExecute
la source