将ContextMenu的原点传递给WPF命令

时间:2009-06-17 21:19:57

标签: wpf xaml mvvm

与从上下文菜单项触发命令有关的有趣问题......

我想触发一个命令在我的控件InsertRowCmd中插入一行。此命令需要知道插入行的位置。

我可以使用Mouse.GetPosition(),但这会让我获得当前鼠标的位置,这将位于菜单项上。我希望得到上下文菜单的来源。

是否有任何人对如何将上下文菜单的来源作为参数传递给命令有任何建议?

示例代码:

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

我目前的想法如下:

- 使用点击处理程序,以便我可以在代码中找到原点。问题是我必须处理启用/禁用。

-Handle单击事件并保存上下文菜单的来源。将此保存的信息传递给命令。我已经验证了在执行命令之前触发了click事件。

有什么想法吗?

修改

我正在使用Josh Smith的CommandSinkBinding将命令处理路由到我的ViewModel类。因此,处理命令执行的代码对视图一无所知。

3 个答案:

答案 0 :(得分:5)

您需要使用TranslatePointContextMenu的左上角(0,0)转换为包含网格中的坐标。您可以通过将CommandParameter绑定到ContextMenu并使用转换器来执行此操作:

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

另一种方法是附加行为,只要打开Point,就会自动更新ContextMenu类型的附加只读属性。用法看起来像这样:

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

因此TrackOpenLocation附加属性会在ContextMenu打开时附加到OpenLocation并更新第二个附加属性(ContextMenu)。然后,MenuItem可以绑定到OpenLocation,以获取上次ContextMenu开启的位置。

答案 1 :(得分:5)

根据肯特的回答,我使用了他附属的财产建议并最终得到了这个(使用约什史密斯的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));
  }
 }
}

然后在Xaml中使用,你只需要:

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

但是,我还需要添加:

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

到我的视图的构造函数,否则CommandParameter的绑定无法查找ElementName=menu

答案 2 :(得分:1)

除了肯特的回答,想一想“标准方式”。 F.E.当ListBox具有ContextMenu时,您不需要菜单的位置,因为在弹出菜单之前设置了所选项。因此,如果您的控件在右键单击中有某些内容被“选中”...