/ / Übergabe des Ursprungs von ContextMenu an den WPF-Befehl - wpf, xaml, mvvm

Übergabe des Ursprungs von ContextMenu an WPF-Befehl - wpf, xaml, mvvm

Interessantes Problem beim Auslösen von Befehlen aus Kontextmenüpunkten ...

Ich möchte einen Befehl auslösen, um eine Zeile in mein Steuerelement, InsertRowCmd, einzufügen. Dieser Befehl muss wissen, wo die Zeile eingefügt werden soll.

Ich könnte Mouse.GetPosition () verwenden, aber das würde mir die aktuelle Position der Maus anzeigen, die sich über dem Menüpunkt befinden würde. Ich möchte stattdessen den Ursprung des Kontextmenüs ermitteln.

Hat jemand Vorschläge, wie der Ursprung des Kontextmenüs als Parameter an den Befehl übergeben werden kann?

Beispielcode:

<UserControl x:Name="MyControl">
<!--...-->
<ContextMenu x:Name="menu">
<MenuItem Header="Insert Row" Command="{x:Static customCommands:MyCommands.InsertRowCmd}" CommandParameter="?"/>
</ContextMenu>
</UserControl>

Meine aktuellen Ideen lauten wie folgt:

-Verwenden Sie stattdessen den Click-Handler, damit ich den Ursprung im Code finde. Das Problem ist, dass ich dann mit dem Aktivieren / Deaktivieren umgehen müsste.

-Behandle click event und speichere den Ursprung des Kontextmenüs. Übergeben Sie diese gespeicherten Informationen an den Befehl. Ich habe überprüft, dass Klickereignisse ausgelöst werden, bevor der Befehl ausgeführt wird.

Irgendwelche Ideen?

BEARBEITEN:

Ich benutze Josh Smiths CommandSinkBinding um die Befehlsverarbeitung in meine ViewModel-Klasse zu leiten. Der Code, der die Befehlsausführung verarbeitet, weiß also nichts über die Ansicht.

Antworten:

5 für die Antwort № 1

Sie müssen verwenden TranslatePoint um den linken oberen Rand (0, 0) des ContextMenu zu einer Koordinate im umschließenden Gitter. Sie können dies tun, indem Sie das CommandParameter zum ContextMenu und benutze einen Konverter:

CommandParameter="{Binding IsOpen, ElementName=_menu, Converter={StaticResource PointConverter}}"

Ein weiterer Ansatz wäre ein angefügtes Verhalten, bei dem eine angefügte schreibgeschützte Eigenschaft des Typs automatisch aktualisiert wird Point wann immer die ContextMenu ist geöffnet. Die Verwendung würde ungefähr so ​​aussehen:

<ContextMenu x:Name="_menu" local:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="..." CommandParameter="{Binding Path=(local:TrackBehavior.OpenLocation), ElementName=_menu}"/>
</ContextMenu>

Also die TrackOpenLocation Angehängte Eigenschaft erledigt die Arbeit des Anhängens an die ContextMenu und Aktualisieren einer zweiten angehängten Eigenschaft (OpenLocation) wann immer die ContextMenu ist geöffnet. Dann ist die MenuItem kann nur binden an OpenLocation um den Ort zu ermitteln, an dem die ContextMenu wurde zuletzt geöffnet.


5 für die Antwort № 2

Nach Kents Antwort habe ich seinen beigefügten Vorschlag für eine Immobilie verwendet und bin zu diesem Ergebnis gekommen (unter Verwendung von Josh Smiths) Beispiel für angehängte Verhaltensweisen):

public static class TrackBehavior
{
public static readonly DependencyProperty TrackOpenLocationProperty = DependencyProperty.RegisterAttached("TrackOpenLocation", typeof(bool), typeof(TrackBehavior), new UIPropertyMetadata(false, OnTrackOpenLocationChanged));

public static bool GetTrackOpenLocation(ContextMenu item)
{
return (bool)item.GetValue(TrackOpenLocationProperty);
}

public static void SetTrackOpenLocation(ContextMenu item, bool value)
{
item.SetValue(TrackOpenLocationProperty, value);
}

public static readonly DependencyProperty OpenLocationProperty = DependencyProperty.RegisterAttached("OpenLocation", typeof(Point), typeof(TrackBehavior), new UIPropertyMetadata(new Point()));

public static Point GetOpenLocation(ContextMenu item)
{
return (Point)item.GetValue(OpenLocationProperty);
}

public static void SetOpenLocation(ContextMenu item, Point value)
{
item.SetValue(OpenLocationProperty, value);
}

static void OnTrackOpenLocationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var menu = dependencyObject as ContextMenu;
if (menu == null)
{
return;
}

if (!(e.NewValue is bool))
{
return;
}

if ((bool)e.NewValue)
{
menu.Opened += menu_Opened;

}
else
{
menu.Opened -= menu_Opened;
}
}

static void menu_Opened(object sender, RoutedEventArgs e)
{
if (!ReferenceEquals(sender, e.OriginalSource))
{
return;
}

var menu = e.OriginalSource as ContextMenu;
if (menu != null)
{
SetOpenLocation(menu, Mouse.GetPosition(menu.PlacementTarget));
}
}
}

und dann in der Xaml verwenden, brauchen Sie nur:

<ContextMenu x:Name="menu" Common:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="{Binding SomeCommand}" CommandParameter="{Binding Path=(Common:TrackBehavior.OpenLocation), ElementName=menu}" Header="Menu Text"/>
</ContextMenu>

Ich musste jedoch auch hinzufügen:

NameScope.SetNameScope(menu, NameScope.GetNameScope(this));

an den konstruktor meiner ansicht, ansonsten die bindung für die CommandParameter konnte nicht nachschlagen ElementName=menu.


1 für die Antwort № 3

Denken Sie zusätzlich zu Kents Antwort an a"Standard Weg". F.e. Wenn eine ListBox ein ContextMenu hat, brauchen Sie keine Menüposition, da das ausgewählte Element gesetzt wird, bevor das Menü auftaucht. Wenn Ihr Steuerelement also etwas hat, das mit der rechten Maustaste "ausgewählt" wird ...