WPF上左键单击的上下文菜单

35

我有一个WPF应用程序,在Xaml文件中有一个Image控件。

在这个图像上右键单击会弹出一个上下文菜单。

我想要同样的菜单也可以在“左键单击”时显示。

我该如何以MVVM的方式实现?


1
虽然这是可能的,但在标准 Windows 期望中,在左键单击时显示上下文菜单是不合适的。 - Tim Lloyd
关于将其制作为MVVM,我认为XAML应该在您的“视图”中,Image_MouseDown C#代码应该在您的“视图模型”中,而您的“模型”不应该知道任何关于上下文菜单的内容。 - Ed Noepel
9个回答

54

这里是一个仅使用 XAML 的解决方案。 只需将此样式添加到您的按钮即可。 这将导致上下文菜单在左右单击时打开。享受吧!

<Button Content="Open Context Menu">
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Style.Triggers>
                <EventTrigger RoutedEvent="Click">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
                                    <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
                                </BooleanAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Style.Triggers>
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem />
                        <MenuItem />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </Button.Style>
</Button>

13
样式没问题,但是当我给菜单项(MenuItem)添加命令(Command)时,它没有触发事件。我已经确认过绑定是正确的。有什么想法吗? - utkarsh
3
我和 @utkarsh 遇到了同样的问题,当我尝试将命令绑定到 MenuItem 时,<MenuItem Header="Download" Command="{Binding DownloadButtonCommand}"/> 它无法正常工作,有什么建议吗? - Kerwen
6
似乎上下文菜单的数据绑定只有在右键单击时设置。在首次通过右键单击打开后,它也适用于左键单击。这就是为什么我最终使用了此解决方案的原因:https://dev59.com/YHRB5IYBdhLWcg3wro2B#29123964 - Norman
有一个问题,ContextMenuService.Placement 不起作用。 - CodingNinja

20

你可以通过像这样使用图像的MouseDown事件来实现此操作

<Image ... MouseDown="Image_MouseDown">
    <Image.ContextMenu>
        <ContextMenu>
            <MenuItem .../>
            <MenuItem .../>
        </ContextMenu>
    </Image.ContextMenu>
</Image>

然后在代码后台的 EventHandler 中显示 ContextMenu。

private void Image_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ChangedButton == MouseButton.Left)
    {
        Image image = sender as Image;
        ContextMenu contextMenu = image.ContextMenu;
        contextMenu.PlacementTarget = image;
        contextMenu.IsOpen = true;
        e.Handled = true;
    }
}

7
这个实现导致右键菜单弹出后立即消失。我在 DataTemplate 中使用了该控件及其右键菜单,不确定是否有影响…… - epalm
这似乎没有触发ContextMenuOpening事件,因此依赖于它的代码将无法运行。 - stijn
1
只需使用 MouseUp 事件。 - VARAK
1
如果您将此添加到按钮(而不是图像),则只需将类似于此的代码放置在Button_Click事件本身中。 - dodgy_coder
1
我爱你!!! - Jesse Roper

13

您可以发明自己的DependencyProperty,当图像被点击时打开上下文菜单,就像这样:

  <Image Source="..." local:ClickOpensContextMenuBehavior.Enabled="True">
      <Image.ContextMenu>...
      </Image.ContextMenu>
  </Image>

以下是用C#编写该属性的代码:

public class ClickOpensContextMenuBehavior
{
  private static readonly DependencyProperty ClickOpensContextMenuProperty =
    DependencyProperty.RegisterAttached(
      "Enabled", typeof(bool), typeof(ClickOpensContextMenuBehavior),
      new PropertyMetadata(new PropertyChangedCallback(HandlePropertyChanged))
    );

  public static bool GetEnabled(DependencyObject obj)
  {
    return (bool)obj.GetValue(ClickOpensContextMenuProperty);
  }

  public static void SetEnabled(DependencyObject obj, bool value)
  {
    obj.SetValue(ClickOpensContextMenuProperty, value);
  }

  private static void HandlePropertyChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs args)
  {
    if (obj is Image) {
      var image = obj as Image;
      image.MouseLeftButtonDown -= ExecuteMouseDown;
      image.MouseLeftButtonDown += ExecuteMouseDown;
    }

    if (obj is Hyperlink) {
      var hyperlink = obj as Hyperlink;
      hyperlink.Click -= ExecuteClick;
      hyperlink.Click += ExecuteClick;
    }
  }

  private static void ExecuteMouseDown(object sender, MouseEventArgs args)
  {
    DependencyObject obj = sender as DependencyObject;
    bool enabled = (bool)obj.GetValue(ClickOpensContextMenuProperty);
    if (enabled) {
      if (sender is Image) {
        var image = (Image)sender;
        if (image.ContextMenu != null)
          image.ContextMenu.IsOpen = true;
      }
    }
  } 

  private static void ExecuteClick(object sender, RoutedEventArgs args)
  {
    DependencyObject obj = sender as DependencyObject;
    bool enabled = (bool)obj.GetValue(ClickOpensContextMenuProperty);
    if (enabled) {
      if (sender is Hyperlink) {
        var hyperlink = (Hyperlink)sender;
        if(hyperlink.ContextMenu != null)
          hyperlink.ContextMenu.IsOpen = true;
      }
    }
  } 
}

3
这很有帮助,但是我需要将ContextMenuPlacementTarget指定回依赖对象(在我这里是Button),这样菜单才能正确填充。这是为了动态上下文菜单,为ListView中的每个项目填充而做的。 - Eric P.
我在我的菜单项中使用了这个(如果被点击,将显示子菜单),但它没有起作用。我仍然需要右键单击才能显示菜单。 - ThEpRoGrAmMiNgNoOb
1
myFrameworkElement.ContextMenu.PlacementTarget = myFrameworkElement 对我很有帮助。谢谢 @EricP。 - Fredrik

3

您只需要将代码添加到Image_MouseDown函数中。

e.Handled = true;

这样它就不会消失了。


3
如果您想在不使用代码后台的情况下仅使用Xaml完成此操作,可以使用Expression Blend的触发器支持:
...
xmlns:i="schemas.microsoft.com/expression/2010/interactivity"
...

<Button x:Name="addButton">
    <Button.ContextMenu>
        <ContextMenu ItemsSource="{Binding Items}" />
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <ei:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=addButton}" PropertyName="PlacementTarget" Value="{Binding ElementName=addButton, Mode=OneWay}"/>
                <ei:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=addButton}" PropertyName="IsOpen" Value="True"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button.ContextMenu>
</Button>

你能在你的示例中指定XML命名空间吗? - jan
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" - Flatliner DOA
这个问题与epalm在另一个答案中提到的问题相同。 - Rhyous
2
我对这个答案的问题在于它没有完整的代码清单,所以“<ContextMenu ItemsSource ="{Binding Items}" />”没有意义。此外,如果我将其插入到我拥有的沙盒应用程序中,语法方面会出现错误。我正在使用.Net4.5,VS2013,所以非常确定不是那个问题! - Bertie
对我来说有效的右键单击 WindowsFormsHost - TargetObject 更改为 WindowsFormsHost(DockPanel)之外的元素。 - niyou

2

交互性已经过时,不再支持。其实现的新方法是:

 xmlns:b="http://schemas.microsoft.com/xaml/behaviors"

 <Button x:Name="ConvertVideoButton">
            <Button.ContextMenu>
                <ContextMenu VerticalContentAlignment="Top" >
                    <MenuItem Header="Convert  1"  Command="{Binding ConvertMkvCommand}" />
                    <MenuItem Header="Convert 2"  Command="{Binding ConvertMkvCommand}" />
                </ContextMenu>
            </Button.ContextMenu>

            <b:Interaction.Triggers>
                <b:EventTrigger  EventName="Click">
                    <b:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=ConvertVideoButton}" PropertyName="PlacementTarget" Value="{Binding ElementName=ConvertVideoButton, Mode=OneWay}"/>
                    <b:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=ConvertVideoButton}" PropertyName="IsOpen" Value="True"/>
                </b:EventTrigger>
            </b:Interaction.Triggers>
        </Button>

1

嘿,我遇到了同样的问题,寻找解决方案,但在这里没有找到。

我不知道MVVM的任何内容,所以它可能不符合MVVM标准,但对我有效。

步骤1:给你的上下文菜单命名。

<Button.ContextMenu>
    <ContextMenu Name="cmTabs"/>
</Button.ContextMenu>

步骤二:双击控件对象并插入此代码。顺序很重要!
Private Sub Button_Click_1(sender As Object, e As Windows.RoutedEventArgs)
        cmTabs.StaysOpen = True
        cmTabs.IsOpen = True
    End Sub

步骤三:享受

这个按钮使用了一个带有ControlTemplate的ImageBrush,可以响应左键和右键点击。


1
不必为您的上下文菜单命名。您可以从按钮中获取它,如下所示: private void cmdExportButton_Click(object sender, RoutedEventArgs e) { Button exportButton = (Button)sender; exportButton.ContextMenu.PlacementTarget = exportButton; exportButton.ContextMenu.Placement = PlacementMode.Bottom; exportButton.ContextMenu.StaysOpen = true; exportButton.ContextMenu.IsOpen = true; } - Vladislav

0

您可以将ContextMenu的IsOpen属性绑定到ViewModel中的属性,例如“IsContextMenuOpen”。 但问题在于您无法直接将ContextMenu绑定到ViewModel,因为它不是您的UserControl层次结构的一部分。因此,为了解决这个问题,您应该将标记属性绑定到视图的DataContext。

<Image Tag="{Binding DataContext, ElementName=YourUserControlName}">
<ContextMenu IsOpen="{Binding PlacementTarget.Tag.IsContextMenuOpen,Mode=OneWay}" >
.....
</ContextMenu>
<Image>

祝你好运。


-1

XAML

    <Button x:Name="b" Content="button"  Click="b_Click" >
        <Button.ContextMenu >
            <ContextMenu   >
                <MenuItem Header="Open" Command="{Binding OnOpen}" ></MenuItem>
                <MenuItem Header="Close" Command="{Binding OnClose}"></MenuItem>                    
            </ContextMenu>
        </Button.ContextMenu>
    </Button>

C#

    private void be_Click(object sender, RoutedEventArgs e)
        {
        b.ContextMenu.DataContext = b.DataContext;
        b.ContextMenu.IsOpen = true;            
        }

1
OP正在询问有关图像控件的单击事件。您提供了一个按钮控件的解决方案。 - Noel Widmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接