Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing origin of ContextMenu into WPF Command

Tags:

mvvm

wpf

xaml

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.

like image 419
Josh G Avatar asked Jun 17 '09 21:06

Josh G


People also ask

What is context menu in WPF?

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

How do I get the context menu from a click event?

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.

What is the purpose of setting the contextmenu object handled to true?

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.

How do I change the behavior of a contextmenu control?

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.


2 Answers

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.

like image 91
Kent Boogaart Avatar answered Sep 28 '22 07:09

Kent Boogaart


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.

like image 40
Wilka Avatar answered Sep 28 '22 07:09

Wilka