【问题标题】:Passing origin of ContextMenu into WPF Command将 ContextMenu 的来源传递给 WPF 命令
【发布时间】:2009-06-17 21:19:56
【问题描述】:

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

我想触发一个命令来在我的控件 InsertRowCmd 中插入一行。该命令需要知道在哪里插入行。

我可以使用 Mouse.GetPosition(),但这会得到当前鼠标的位置,它会在菜单项上方。我想获取上下文菜单的来源。

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

示例代码:

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

我目前的想法如下:

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

-处理点击事件并保存上下文菜单的来源。将此保存的信息传递到命令中。我已经验证点击事件会在命令执行之前触发。

有什么想法吗?

编辑:

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

【问题讨论】:

    标签: wpf xaml mvvm


    【解决方案1】:

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

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

    另一种方法是附加行为,它会在打开ContextMenu 时自动更新类型为Point 的附加只读属性。用法如下所示:

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

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

    【讨论】:

    • 我想你的意思是开头的“CommandParameter”而不是“ConverterParameter”?
    • 您愿意详细说明所附的行为理念吗?
    • 我想我会尝试附加的行为。感谢您的建议。
    • 嗨,肯特,很棒的解决方案!如果能提供转换器代码和行为代码就好了。
    【解决方案2】:

    根据 Kent 的回答,我使用了他的附加属性建议并最终得到了这个(使用 Josh Smith 的 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

    【讨论】:

    • 我认为我在名称范围方面遇到了同样的问题。感谢您的帖子。
    • 消除 NameScope hack 需要的替代解决方案是使用 RelativeSource 绑定:CommandParameter="{Binding Path=(Common:TrackBehavior.OpenLocation), RelativeSource={RelativeSource AncestorType=ContextMenu}}" .这对我来说效果很好。
    • @Wilka,非常感谢您的代码效果很好!看看 Mike Post 评论,我认为解决了你的 Scope 问题(它对我有用)。
    • @MikePost,感谢您的精彩提示!一切都在第一枪!!!!
    【解决方案3】:

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

    【讨论】:

    • 嗯……这有一定的可能。
    • 我最终使用了您的两个建议。有时单击不在子控件上,因此我需要 Kent 关于带有附加行为的点转换的建议。当一个项目被选中时,我使用了那个项目。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2011-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-15
    • 1970-01-01
    • 2017-01-30
    相关资源
    最近更新 更多