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


86

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


J'ai exactement le même problème, avec une ListBox.
Hadi Eskandari

Il existe un rapport de bogue actuellement ouvert contre WPF pour ce problème: github.com/dotnet/wpf/issues/316
UuDdLrLrSs

Réponses:


14

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.


2
Cela a fonctionné pour moi. Je ne comprends pas pourquoi. Quelqu'un peut-il expliquer?
TJKjaer

Cela ne fonctionne-t-il pas parce que CommandParameter est lié avant la commande? Je doute que vous ayez besoin du convertisseur ...
MBoros

2
Ce n'est pas une solution. C'est un hack? Que diable se passe-t-il? Cela fonctionnait?
Jordanie

Parfait, ça marche pour moi! La magie est dans la ligne <Binding />, ce qui provoque la mise à jour de la liaison de commande lorsque le modèle de données change (qui est lié au paramètre de commande)
Andreas Kahler

56

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:

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

Nouveau code:

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

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).

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

42
Déplacer le CommandParameter devant la Command est la meilleure réponse sur ce fil.
BSick7

6
Le déplacement de l'ordre des attributs ne nous a pas aidés. Je serais surpris si cela avait un effet sur l'ordre d'exécution.
Jack Ukleja du

3
Je ne sais pas pourquoi cela fonctionne. On a l'impression que ça ne devrait pas mais c'est totalement le cas.
RMK

1
J'ai eu le même problème - RelativeSource n'a pas aidé, changer l'ordre des attributs l'a fait. Merci pour la mise à jour!
Grant Crofton

14
En tant que personne qui utilise religieusement des extensions pour embellir automatiquement XAML (fractionner les attributs sur les lignes, corriger l'indentation, réorganiser les attributs), la proposition de changer l'ordre de CommandParameteret Commandme fait peur.
Guttsy

29

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?


3
J'ai essayé de définir le CommandParameter avant la commande - exécute toujours CanExecute, mais passe toujours NULL ... Bummer - mais merci pour le conseil. En outre, l'appel de CommandManager.InvalidateRequerySuggested (); ne fait aucune différence.
Jonas Follesø

CommandManager.InvalidateRequerySuggested () a résolu un problème similaire pour moi. Merci!
MJS

13

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.

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 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.


Bien fait, merci. Je ne peux pas croire que M $ n'a pas résolu ce problème après 8 ans. Turrible!
McGarnagle

8

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:

CommandParameter="{Binding .}"

à

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

Je ne sais pas pourquoi cela fonctionne, mais cela a fonctionné pour moi.


J'ai essayé les deux réponses à score élevé ci-dessus, mais celle-ci n'a fonctionné que pour moi. Il semble que ce soit un problème interne de contrôle lui-même, pas la liaison, mais beaucoup de gens l'ont encore fait travailler avec les réponses ci-dessus. Merci!
Javidan

6

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 Tagsolution de contournement basée sur le cas particulier du menu contextuel, la clé ici est de lier le CommandParameterrégulièrement, mais de lier le Commandavec le IsAsync=True. Cela retardera un peu la liaison de la commande réelle (et donc son CanExecuteappel), 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.


5

Vous pourrez peut-être utiliser mon CommandParameterBehaviorque j'ai posté sur les forums Prism hier. Il ajoute le comportement manquant où une modification de la CommandParametercause Commanddoit ê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.AddValueChangedsans appeler plus tard PropertyDescriptor.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 IDelegateCommandcontenu à 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 pas RoutedCommands ici (nous utilisons Prism's DelegateCommand<T>pour à peu près tout) donc ne me tenez pas responsable si mon appel à CommandManager.InvalidateRequerySuggesteddé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();
            }

        }
    }
}

est tombé sur votre rapport de bogue lors de la connexion. Avez-vous une chance de mettre à jour votre message ici avec votre dernier code? ou avez-vous depuis trouvé un meilleur travail?
Markus Hütter

Une solution plus simple peut être d'observer la propriété CommandParameter à l'aide d'une liaison au lieu d'un descripteur de propriété. Sinon, une excellente solution! Celui-ci résout en fait le problème sous-jacent au lieu de simplement introduire un hack ou une solution de contournement maladroit.
Sebastian Negraszus

1

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:

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

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

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


1

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:

public event EventHandler CanExecuteChanged;

Je l'ai changé en:

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

J'ai supprimé les deux méthodes suivantes car j'étais trop paresseux pour les réparer

public void RaiseCanExecuteChanged()

et

protected virtual void OnCanExecuteChanged()

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

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

J'ai trouvé que DispatcherPriority.Normalc'était trop élevé pour fonctionner de manière fiable (ou pas du tout, dans mon cas). L'utilisation DispatcherPriority.Loadedfonctionne 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).
Peter Duniho

0

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}"


Je fais exactement la même chose dans ma vue de liste. Dans ce cas, il s'agit d'un ItemsControl, il n'y a donc aucune propriété évidente à "lier" (dans l'arborescence visuelle). Je suppose que je dois trouver un moyen de détecter quand la liaison est terminée et de réévaluer CanExecute (parce que CommandParameter est lié, juste
trop


0

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

-1

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,


J'ai essayé d'utiliser Snoop - mais c'est vraiment difficile à déboguer car ce n'est que NULL lors du chargement initial. Si j'exécute Snoop dessus, Command et CommandParameter sont tous les deux seth ... Cela a à voir avec l'utilisation des commandes dans DataTemplate.
Jonas Follesø

-1

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é.


ce lien n'est plus valide
Peter Duniho

-2

À 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 .

private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)  
{
    // Your goes heres
}

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.