WPF CommandParameter ist beim ersten Aufruf von CanExecute NULL

86

Ich bin auf ein Problem mit WPF und Befehlen gestoßen, die an eine Schaltfläche in der DataTemplate eines ItemsControl gebunden sind. Das Szenario ist recht einfach. Das ItemsControl ist an eine Liste von Objekten gebunden, und ich möchte in der Lage sein, jedes Objekt in der Liste durch Klicken auf eine Schaltfläche zu entfernen. Die Schaltfläche führt einen Befehl aus, und der Befehl kümmert sich um das Löschen. Der CommandParameter ist an das Objekt gebunden, das ich löschen möchte. Auf diese Weise weiß ich, worauf der Benutzer geklickt hat. Ein Benutzer sollte nur in der Lage sein, seine "eigenen" Objekte zu löschen. Daher muss ich beim Aufruf "CanExecute" des Befehls einige Überprüfungen durchführen, um zu überprüfen, ob der Benutzer über die richtigen Berechtigungen verfügt.

Das Problem ist, dass der an CanExecute übergebene Parameter beim ersten Aufruf NULL ist. Daher kann ich die Logik zum Aktivieren / Deaktivieren des Befehls nicht ausführen. Wenn ich es jedoch immer aktiviert mache und dann auf die Schaltfläche klicke, um den Befehl auszuführen, wird der CommandParameter korrekt übergeben. Das bedeutet also, dass die Bindung gegen den CommandParameter funktioniert.

Die XAML für das ItemsControl und das DataTemplate sieht folgendermaßen aus:

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

Wie Sie sehen können, habe ich eine Liste von Kommentarobjekten. Ich möchte, dass der CommandParameter des DeleteCommentCommand an das Command-Objekt gebunden wird.

Meine Frage ist also: Hat jemand dieses Problem schon einmal erlebt? CanExecute wird in meinem Befehl aufgerufen, aber der Parameter ist beim ersten Mal immer NULL - warum ist das so?

Update: Ich konnte das Problem ein wenig eingrenzen. Ich habe einen leeren Debug ValueConverter hinzugefügt, damit ich eine Nachricht ausgeben kann, wenn der CommandParameter datengebunden ist. Es stellt sich heraus, dass das Problem darin besteht, dass die CanExecute-Methode ausgeführt wird, bevor der CommandParameter an die Schaltfläche gebunden wird. Ich habe versucht, den CommandParameter vor dem Command festzulegen (wie vorgeschlagen) - aber es funktioniert immer noch nicht. Irgendwelche Tipps zur Steuerung.

Update2: Gibt es eine Möglichkeit zu erkennen, wann die Bindung "abgeschlossen" ist, damit ich eine Neubewertung des Befehls erzwingen kann? Auch - ist es ein Problem, dass ich mehrere Schaltflächen habe (eine für jedes Element im ItemsControl), die an dieselbe Instanz eines Befehlsobjekts gebunden sind?

Update3: Ich habe eine Reproduktion des Fehlers auf mein SkyDrive hochgeladen: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip

Jonas Follesø
quelle
Ich habe genau das gleiche Problem mit einer ListBox.
Hadi Eskandari
Für dieses Problem gibt es derzeit einen offenen Fehlerbericht gegen WPF: github.com/dotnet/wpf/issues/316
UuDdLrLrSs

Antworten:

14

Ich bin auf ein ähnliches Problem gestoßen und habe es mit meinem vertrauenswürdigen TriggerConverter gelöst.

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
}

Dieser Wertekonverter verwendet eine beliebige Anzahl von Parametern und gibt den ersten als konvertierten Wert zurück. Bei Verwendung in einem MultiBinding in Ihrem Fall sieht es wie folgt aus.

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

Sie müssen TriggerConverter irgendwo als Ressource hinzufügen, damit dies funktioniert. Jetzt wird die Command-Eigenschaft erst festgelegt, wenn der Wert für den CommandParameter verfügbar ist. Sie können stattdessen auch an RelativeSource.Self und CommandParameter binden. um den gleichen Effekt zu erzielen.

David Liersch
quelle
2
Das hat bei mir funktioniert. Ich verstehe nicht warum. Kann jemand erklären?
TJKjaer
Funktioniert es nicht, weil der CommandParameter vor dem Command gebunden ist? Ich bezweifle, dass Sie den Konverter brauchen würden ...
MBoros
2
Dies ist keine Lösung. Das ist ein Hack? Was zur Hölle ist los? Das hat früher funktioniert?
Jordanien
Perfekt, funktioniert bei mir! Die Magie liegt in der Zeile <Binding />, die bewirkt, dass die Befehlsbindung aktualisiert wird, wenn sich die Datenvorlage ändert (die an den Befehlsparameter gebunden ist)
Andreas Kahler
56

Ich hatte das gleiche Problem, als ich versuchte, mich an einen Befehl in meinem Ansichtsmodell zu binden.

Ich habe es geändert, um eine relative Quellbindung zu verwenden, anstatt auf das Element mit Namen zu verweisen, und das hat den Trick getan. Die Parameterbindung hat sich nicht geändert.

Alter Code:

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

Neuer Code:

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

Update : Ich bin gerade auf dieses Problem gestoßen, ohne ElementName zu verwenden. Ich binde an einen Befehl in meinem Ansichtsmodell und mein Datenkontext der Schaltfläche ist mein Ansichtsmodell. In diesem Fall musste ich einfach das CommandParameter-Attribut vor das Command-Attribut in der Button-Deklaration (in XAML) verschieben.

CommandParameter="{Binding Groups}"
Command="{Binding StartCommand}"
Travis Weber
quelle
42
Das Verschieben des CommandParameters vor den Command ist die beste Antwort in diesem Thread.
BSick7
6
Das Verschieben der Reihenfolge der Attribute hat uns nicht geholfen. Es würde mich wundern, wenn sich dies auf die Reihenfolge der Ausführung auswirken würde.
Jack Ukleja
3
Ich weiß nicht, warum das funktioniert. Es fühlt sich so an, als ob es nicht sollte, aber es tut es total.
RMK
1
Ich hatte das gleiche Problem - RelativeSource hat nicht geholfen, die Reihenfolge der Attribute geändert. Danke für das Update!
Grant Crofton
13
Als eine Person , die religiös - Erweiterungen automatisch verschönern XAML verwendet (Split über Linien - Attribute, fix Einbuchtung, Neuordnungs Attribute) den Vorschlag der Reihenfolge ändern CommandParameterund Commandmacht mir Angst.
Guttsy
29

Ich habe festgestellt, dass die Reihenfolge, in der ich Command und CommandParameter einstelle, einen Unterschied macht. Durch das Festlegen der Command-Eigenschaft wird CanExecute sofort aufgerufen, sodass CommandParameter bereits zu diesem Zeitpunkt festgelegt werden soll.

Ich habe festgestellt, dass das Ändern der Reihenfolge der Eigenschaften in der XAML tatsächlich Auswirkungen haben kann, obwohl ich nicht sicher bin, ob es Ihr Problem lösen wird. Es ist jedoch einen Versuch wert.

Sie scheinen vorzuschlagen, dass die Schaltfläche niemals aktiviert wird, was überraschend ist, da ich erwarten würde, dass der CommandParameter in Ihrem Beispiel kurz nach der Command-Eigenschaft festgelegt wird. Wird durch das Aufrufen von CommandManager.InvalidateRequerySuggested () die Schaltfläche aktiviert?

Ed Ball
quelle
3
Versucht, den CommandParameter vor dem Befehl zu setzen - führt CanExecute immer noch aus, übergibt aber immer noch NULL ... Schade - aber danke für den Tipp. Rufen Sie außerdem CommandManager.InvalidateRequerySuggested () auf. macht keinen Unterschied.
Jonas Follesø
CommandManager.InvalidateRequerySuggested () hat ein ähnliches Problem für mich gelöst. Vielen Dank!
MJS
13

Ich habe mir eine andere Option ausgedacht, um dieses Problem zu umgehen, das ich teilen wollte. Da die CanExecute-Methode des Befehls ausgeführt wird, bevor die CommandParameter-Eigenschaft festgelegt wird, habe ich eine Hilfsklasse mit einer angehängten Eigenschaft erstellt, die erzwingt, dass die CanExecute-Methode erneut aufgerufen wird, wenn sich die Bindung ändert.

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

Und dann auf der Schaltfläche möchten Sie einen Befehlsparameter an ...

<Button 
    Content="Press Me"
    Command="{Binding}" 
    helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />

Ich hoffe, das hilft vielleicht jemand anderem bei dem Problem.

Ed Downs
quelle
Schön gemacht, danke. Ich kann nicht glauben, dass M $ dies nach 8 Jahren nicht behoben hat. Turrible!
McGarnagle
7

Dies ist ein alter Thread, aber da Google mich hierher gebracht hat, als ich dieses Problem hatte, werde ich mit einer Schaltfläche hinzufügen, was für mich für eine DataGridTemplateColumn funktioniert hat.

Ändern Sie die Bindung von:

CommandParameter="{Binding .}"

zu

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

Ich bin mir nicht sicher, warum es funktioniert, aber es hat bei mir funktioniert.

Simon Smith
quelle
Ich habe beide Highscore-Antworten oben ausprobiert, aber diese hat nur für mich funktioniert. Es scheint, dass es ein internes Problem der Kontrolle selbst ist, nicht die Bindung, aber dennoch haben viele Leute es mit den obigen Antworten arbeiten lassen. Vielen Dank!
Javidan
5

Möglicherweise können Sie meine verwenden CommandParameterBehavior, die ich gestern in den Prisma-Foren gepostet habe. Es fügt das fehlende Verhalten hinzu, bei dem eine Änderung der CommandParameterUrsache Commanderneut abgefragt wird.

Es gibt hier eine gewisse Komplexität, die durch meine Versuche verursacht wird, den Speicherverlust zu vermeiden, der verursacht wird, wenn Sie PropertyDescriptor.AddValueChangedohne späteren Anruf anrufen PropertyDescriptor.RemoveValueChanged. Ich versuche das zu beheben, indem ich die Registrierung des Handlers beim Entladen des Ekements aufhebe.

Sie müssen das Material wahrscheinlich entfernen, es IDelegateCommandsei denn, Sie verwenden Prism (und möchten die gleichen Änderungen wie ich an der Prism-Bibliothek vornehmen). Beachten Sie auch, dass wir RoutedCommandhier im Allgemeinen nicht s verwenden (wir verwenden Prismen DelegateCommand<T>für so ziemlich alles). CommandManager.InvalidateRequerySuggestedMachen Sie mich also bitte nicht verantwortlich, wenn mein Aufruf, eine Art Kollaps-Kaskade für Quantenwellenfunktionen auszulösen, die das bekannte Universum oder irgendetwas zerstört.

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

        }
    }
}
Swythan
quelle
ist auf Ihren Fehlerbericht bei connect gestoßen. Gibt es eine Chance, dass Sie Ihren Beitrag hier mit Ihrem letzten Code aktualisieren können? oder hast du seitdem eine bessere umgehung gefunden?
Markus Hütter
Eine einfachere Lösung kann darin bestehen, die CommandParameter-Eigenschaft mithilfe einer Bindung anstelle eines Eigenschaftsdeskriptors zu beobachten. Ansonsten eine tolle Lösung! Dieser behebt tatsächlich das zugrunde liegende Problem, anstatt nur einen umständlichen Hack oder eine Problemumgehung einzuführen.
Sebastian Negraszus
5

Ich bin kürzlich auf das gleiche Problem gestoßen (für mich war es für die Menüpunkte in einem Kontextmenü), und obwohl es möglicherweise nicht für jede Situation eine geeignete Lösung ist, habe ich einen anderen (und viel kürzeren!) Weg gefunden, dies zu lösen Problem:

<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />

Ignorieren der Tag umgangene Problemumgehung für den speziellen Fall des Kontextmenüs , besteht der Schlüssel hier darin, die CommandParameterregelmäßig zu binden , aber die Commandmit der zusätzlichen zu binden IsAsync=True. Dies verzögert die Bindung des eigentlichen Befehls (und damit seines CanExecuteAufrufs) etwas, sodass der Parameter bereits verfügbar ist. Dies bedeutet jedoch, dass für einen kurzen Moment der aktivierte Zustand möglicherweise falsch ist, aber für meinen Fall war dies vollkommen akzeptabel.

Ralf Stauder
quelle
1

Es gibt eine relativ einfache Möglichkeit, dieses Problem mit DelegateCommand zu "beheben", obwohl die DelegateCommand-Quelle aktualisiert und die Microsoft.Practices.Composite.Presentation.dll neu kompiliert werden muss.

1) Laden Sie den Prism 1.2-Quellcode herunter und öffnen Sie die Datei CompositeApplicationLibrary_Desktop.sln. Hier ist ein Composite.Presentation.Desktop-Projekt, das die DelegateCommand-Quelle enthält.

2) Ändern Sie unter dem öffentlichen Ereignis EventHandler CanExecuteChanged Folgendes wie folgt:

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) Ändern Sie es unter "Geschützte virtuelle Leere OnCanExecuteChanged () wie folgt:

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

4) Kompilieren Sie die Lösung neu und navigieren Sie zum Ordner Debug oder Release, in dem sich die kompilierten DLLs befinden. Kopieren Sie die Dateien Microsoft.Practices.Composite.Presentation.dll und .pdb (falls gewünscht) an die Stelle, an der Sie auf Ihre externen Assemblys verweisen, und kompilieren Sie dann Ihre Anwendung neu, um die neuen Versionen abzurufen.

Danach sollte CanExecute jedes Mal ausgelöst werden, wenn die Benutzeroberfläche Elemente rendert, die an den betreffenden DelegateCommand gebunden sind.

Pass auf dich auf, Joe

Schiedsrichter bei Google Mail

Joe Bako
quelle
1

Nachdem ich einige gute Antworten auf ähnliche Fragen gelesen hatte, habe ich in Ihrem Beispiel den DelegateCommand leicht geändert, damit er funktioniert. Anstatt zu verwenden:

public event EventHandler CanExecuteChanged;

Ich habe es geändert in:

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

Ich habe die folgenden zwei Methoden entfernt, weil ich zu faul war, um sie zu beheben

public void RaiseCanExecuteChanged()

und

protected virtual void OnCanExecuteChanged()

Und das ist alles ... dies scheint sicherzustellen, dass CanExecute aufgerufen wird, wenn sich die Bindung ändert und nach der Execute-Methode

Es wird nicht automatisch ausgelöst, wenn das ViewModel geändert wird. Wie in diesem Thread erwähnt, ist dies jedoch durch Aufrufen des CommandManager.InvalidateRequerySuggested im GUI-Thread möglich

Application.Current?.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)CommandManager.InvalidateRequerySuggested);
kkCosmo
quelle
Ich habe festgestellt, dass dies DispatcherPriority.Normalzu hoch ist, um zuverlässig zu arbeiten (oder in meinem Fall überhaupt). Die Verwendung DispatcherPriority.Loadedfunktioniert gut und scheint angemessener zu sein (dh es wird ausdrücklich angegeben, dass der Delegat erst aufgerufen werden soll, wenn die dem Ansichtsmodell zugeordneten UI-Elemente tatsächlich geladen wurden).
Peter Duniho
0

Hey Jonas, ich bin mir nicht sicher, ob dies in einer Datenvorlage funktioniert, aber hier ist die Bindungssyntax, die ich in einem ListView-Kontextmenü verwende, um das aktuelle Element als Befehlsparameter abzurufen:

CommandParameter = "{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu}, Path = PlacementTarget.SelectedItem, Mode = TwoWay}"


quelle
In meiner Listenansicht mache ich genau das Gleiche. In diesem Fall handelt es sich um ein ItemsControl, sodass es keine offensichtliche Eigenschaft gibt, an die man sich "binden" kann (im visuellen Baum). Ich denke, ich muss einen Weg finden, um zu erkennen, wann die Bindung abgeschlossen ist, und CanExecute neu bewerten (weil CommandParameter nur zu spät gebunden wird)
Jonas Follesø
0

Bei einigen dieser Antworten geht es um die Bindung an den DataContext, um den Befehl selbst abzurufen. Die Frage lautete jedoch, dass der CommandParameter null ist, wenn dies nicht der Fall sein sollte. Das haben wir auch erlebt. Wir haben eine sehr einfache Möglichkeit gefunden, dies in unserem ViewModel zum Laufen zu bringen. Dies gilt speziell für das vom Kunden gemeldete CommandParameter-Nullproblem mit einer Codezeile. Beachten Sie den 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;
        }
    }
TravisWhidden
quelle
-1

Es ist ein langer Schuss. Um dies zu debuggen, können Sie
Folgendes versuchen: - Überprüfen Sie das PreviewCanExecute-Ereignis.
- Verwenden Sie snoop / wpf mole, um nach innen zu schauen und den Befehlsparameter zu sehen.

HTH,

Dennis
quelle
Versucht mit Snoop - aber es ist wirklich schwer zu debuggen, da es nur NULL ist, wenn es anfänglich geladen wird. Wenn ich Snoop darauf ausführe, sind Command und CommandParameter beide seth ... Es hat mit der Verwendung von Befehlen in DataTemplate zu tun.
Jonas Follesø
-1

Der BefehlManager.InvalidateRequerySuggested funktioniert auch für mich. Ich glaube, der folgende Link spricht über ein ähnliches Problem, und M $ dev hat die Einschränkung in der aktuellen Version bestätigt, und der BefehlManager.InvalidateRequerySuggested ist die Problemumgehung. http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/

Was wichtig ist, ist der Zeitpunkt des Aufrufs des commandManager.InvalidateRequerySuggested. Dies sollte aufgerufen werden, nachdem die entsprechende Wertänderung gemeldet wurde.


quelle
Dieser Link ist nicht mehr gültig
Peter Duniho
-2

Stellen Sie neben Ed Balls Vorschlag zum Festlegen von CommandParameter vor Command sicher, dass Ihre CanExecute- Methode einen Parameter vom Objekttyp enthält .

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

Ich hoffe, es verhindert, dass jemand so viel Zeit damit verbringt, herauszufinden, wie SelectedItems als CanExecute-Parameter empfangen werden

Julio Nobre
quelle