首页 > 解决方案 > 如何在此代码中将 contextMenu 放在按钮下?

问题描述

我想获得像 AX 中的按钮,其中一些按钮只打开一个菜单,下面有更多菜单项。我找到了下面的代码,它工作正常。但是当我左键单击按钮时,它会显示鼠标指针所在的上下文菜单。当我右键单击按钮时,它会很好地在按钮下方显示上下文菜单。我希望左键单击像右键单击一样工作。

问题是ContextMenuService.Placement="Bottom"只有在我右键单击时才有效。

<Button Name="MainButton" Content="Button with ContextMenu" Width="150" Height="30" ContextMenuService.Placement="Bottom">
    <Button.ContextMenu>
        <ContextMenu x:Name="MainContextMenu" PlacementRectangle="{Binding RelativeSource={RelativeSource self}}">
            <MenuItem Header="Do A" />
            <MenuItem Header="Do B" />
            <MenuItem Header="Do C" />
        </ContextMenu>
    </Button.ContextMenu>

    <Button.Triggers>
        <EventTrigger SourceName="MainButton" RoutedEvent="Button.Click">
            <BeginStoryboard>
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MainContextMenu" Storyboard.TargetProperty="(ContextMenu.IsOpen)">
                        <DiscreteObjectKeyFrame KeyTime="0:0:0">
                            <DiscreteObjectKeyFrame.Value>
                                <system:Boolean>True</system:Boolean>
                            </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

标签: wpfcontextmenu

解决方案


使用 aStoryboard来设置单个属性值是很奇怪的。这可行,但看起来有些难看。

但这也是您的ContextMenuService.Placement="Bottom"设置不起作用的原因:您只是没有通过 调用ContextMenuService打开上下文菜单Storyboard,因此PlacementTarget不会将 设置为您的按钮。

我建议您创建一个简单的附加属性并在您的视图中重新使用它:

static class ContextMenuTools
{
    public static readonly DependencyProperty OpenOnLeftClickProperty =
        DependencyProperty.RegisterAttached(
            "OpenOnLeftClick", 
            typeof(bool), 
            typeof(ContextMenuTools),
            new PropertyMetadata(false, OpenOnLeftClickChanged));

    public static void SetOpenOnLeftClick(UIElement element, bool value)
        => element.SetValue(OpenOnLeftClickProperty, value);

    public static bool GetOpenOnLeftClick(UIElement element)
        => (bool)element.GetValue(OpenOnLeftClickProperty);

    private static void OpenOnLeftClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is IInputElement element && (bool)e.NewValue)
        {
            element.PreviewMouseLeftButtonDown += ElementOnMouseLeftButtonDown;
        }
    }

    private static void ElementOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (sender is UIElement element
            && ContextMenuService.GetContextMenu(element) is ContextMenu contextMenu)
        {
            contextMenu.Placement = ContextMenuService.GetPlacement(element);
            contextMenu.PlacementTarget = element;
            contextMenu.IsOpen = true;
        }
    }
}

用法可能如下所示:

<Button Content="Button with ContextMenu" ContextMenuService.Placement="Bottom"
    yourNS:ContextMenuTools.OpenOnLeftClick="True">
    <Button.ContextMenu>
        <ContextMenu x:Name="MainContextMenu">
            <MenuItem Header="Do A" />
        </ContextMenu>
    </Button.ContextMenu>
</Button>

注意yourNS命名空间 - 这是一个映射到附加属性类所在的 CLR 命名空间的 XAML 命名空间ContextMenuTools,例如:

xmlns:yourNS="clr-namespace:WpfApp1"

您还可以在任何 WPF 控件实现上使用此附加属性IInputElement,而不仅仅是在按钮上。


推荐阅读