Interesting problem related to firing commands from context menu items...
I want to fire a command to insert a row in my control, InsertRowCmd. This command needs to know where to insert the row.
I could use Mouse.GetPosition(), but that would get me the position of the mouse currently, which would be over the menu item. I want to get the origin of the context menu instead.
Does any one have any suggestions on how to pass the origin of the context menu as a parameter to the command?
Sample code:
<UserControl x:Name="MyControl">
<!--...-->
<ContextMenu x:Name="menu">
<MenuItem Header="Insert Row" Command="{x:Static customCommands:MyCommands.InsertRowCmd}" CommandParameter="?"/>
</ContextMenu>
</UserControl>
My current ideas are as follows:
-Use click handler instead so that I can find the origin in code. The problem is that I would then have to handle enabling/disabling.
-Handle click event and save the origin of the context menu. Pass this saved information into the command. I have verified that click events fire before the command is executed.
Any ideas?
EDIT:
I'm using Josh Smith's CommandSinkBinding to route the command handling into my ViewModel class. So the code that handles the command execution knows nothing about the view.
WPF - Contextmenu. ContextMenu is a pop-up menu that enables a control to expose functionality that is specific to the context of the control. It appears whenever the context menu is requested through a user interface from within this element. The hierarchical inheritance of ContextMenu class is as follows −.
The general technique is to get the source of the event, which is the specific control that was right-clicked, and get the ContextMenu property from it. You typically want to check the Items collection to see what context menu items already exist in the menu, and then add or remove appropriate new MenuItem items to or from the collection.
The typical reason for setting Handled to true in the event data is to replace the menu entirely with a new ContextMenu object, which sometimes requires canceling the operation and starting a new open.
For example, you can change the behavior of parts of the control by using properties, or you can add parts to, or change the layout of, a ContextMenu. The following examples show several ways to add styles to ContextMenu controls.
You'll need to use TranslatePoint
to translate the top-left (0, 0) of the ContextMenu
to a coordinate in the containing grid. You could do so by binding the CommandParameter
to the ContextMenu
and use a converter:
CommandParameter="{Binding IsOpen, ElementName=_menu, Converter={StaticResource PointConverter}}"
Another approach would be an attached behavior that automatically updates an attached readonly property of type Point
whenever the ContextMenu
is opened. Usage would look something like this:
<ContextMenu x:Name="_menu" local:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="..." CommandParameter="{Binding Path=(local:TrackBehavior.OpenLocation), ElementName=_menu}"/>
</ContextMenu>
So the TrackOpenLocation
attached property does the work of attaching to the ContextMenu
and updating a second attached property (OpenLocation
) whenever the ContextMenu
is opened. Then the MenuItem
can just bind to OpenLocation
to get the location at which the ContextMenu
was last opened.
Following on from Kent's answer, I used his attached property suggestion and ended up with this (using Josh Smith's example for attached behaviors):
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));
}
}
}
and then to use in the Xaml, you just need:
<ContextMenu x:Name="menu" Common:TrackBehavior.TrackOpenLocation="True">
<MenuItem Command="{Binding SomeCommand}" CommandParameter="{Binding Path=(Common:TrackBehavior.OpenLocation), ElementName=menu}" Header="Menu Text"/>
</ContextMenu>
However, I also needed to add:
NameScope.SetNameScope(menu, NameScope.GetNameScope(this));
to the constructor of my view, otherwise the binding for the CommandParameter
couldn't lookup ElementName=menu
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With