Показать ContextMenu при щелчке левой кнопкой мыши, используя только XAML - PullRequest
7 голосов
/ 17 февраля 2009

Поведение WPF ContextMenu по умолчанию - отображать его, когда пользователь щелкает правой кнопкой мыши. Я хочу, чтобы ContextMenu показывал, когда пользователь щелкает левой кнопкой мыши. Кажется, это должно быть простое свойство на ContextMenu, но это не так.

Я сфальсифицировал его, чтобы обработать событие LeftMouseButtonDown в коде и затем отобразить контекстное меню.

Я использую MVVM в своем проекте, что означает, что я использую DataTemplate s для элементов, которые имеют контекстные меню. Было бы намного элегантнее избавиться от кода и найти способ отображать контекстное меню, используя триггеры или свойства в XAML.

Есть идеи или решения по этому вопросу?

Ответы [ 5 ]

9 голосов
/ 19 февраля 2009

Я бы предложил создать новый статический класс с прикрепленным DependencyProperty. Вызовите класс LeftClickContextMenu и свойство Enabled (только идеи). Когда вы регистрируете DependencyProperty, добавьте обратный вызов. Затем в свойстве изменили обратный вызов, если для Enabled установлено значение true, затем добавьте обработчик в событие LeftMouseButtonDown и выполните там свои действия. Если для параметра Enabled установлено значение false, удалить обработчик. Это может позволить вам установить его как свойство для чего-либо, просто используя следующее в xaml.

<Border namespace:LeftClickContextMenu.Enabled="True" />

Эта техника называется прикрепленным поведением, и вы можете прочитать больше об этом в этой статье проекта кода: http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx

7 голосов
/ 18 марта 2015

Я только что написал и протестировал это, основываясь на ответе HK1 (о прикрепленных свойствах можно прочитать в Обзор вложенных свойств) :

public static class ContextMenuLeftClickBehavior
{
    public static bool GetIsLeftClickEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsLeftClickEnabledProperty);
    }

    public static void SetIsLeftClickEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsLeftClickEnabledProperty, value);
    }

    public static readonly DependencyProperty IsLeftClickEnabledProperty = DependencyProperty.RegisterAttached(
        "IsLeftClickEnabled", 
        typeof(bool), 
        typeof(ContextMenuLeftClickBehavior), 
        new UIPropertyMetadata(false, OnIsLeftClickEnabledChanged));

    private static void OnIsLeftClickEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var uiElement = sender as UIElement;

        if(uiElement != null) 
        {
            bool IsEnabled = e.NewValue is bool && (bool) e.NewValue;

            if(IsEnabled)
            {
                if(uiElement is ButtonBase)
                    ((ButtonBase)uiElement).Click += OnMouseLeftButtonUp;
                else
                    uiElement.MouseLeftButtonUp += OnMouseLeftButtonUp;
            }
            else
            {
                if(uiElement is ButtonBase)
                    ((ButtonBase)uiElement).Click -= OnMouseLeftButtonUp;
                else
                    uiElement.MouseLeftButtonUp -= OnMouseLeftButtonUp;
            }
        }
    }

    private static void OnMouseLeftButtonUp(object sender, RoutedEventArgs e)
    {
        Debug.Print("OnMouseLeftButtonUp");
        var fe = sender as FrameworkElement;
        if(fe != null)
        {
            // if we use binding in our context menu, then it's DataContext won't be set when we show the menu on left click
            // (it seems setting DataContext for ContextMenu is hardcoded in WPF when user right clicks on a control, although I'm not sure)
            // so we have to set up ContextMenu.DataContext manually here
            if (fe.ContextMenu.DataContext == null)
            {
                fe.ContextMenu.SetBinding(FrameworkElement.DataContextProperty, new Binding { Source = fe.DataContext });
            }

            fe.ContextMenu.IsOpen = true;
        }
    }

}

...

<Button Content="Do All" local:ContextMenuLeftClickBehavior.IsLeftClickEnabled="True" >
    <Button.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Make everything awesome" />
            <MenuItem Header="Control the World" />
        </ContextMenu>
    </Button.ContextMenu>
</Button>

(обратите внимание на комментарий внутри метода OnMouseLeftButtonUp ())

4 голосов
/ 26 марта 2014

Хотя ответ Калеба правильный, он не включает рабочий код. Я настроил пример, используя VB.NET (извините), поэтому я публикую его здесь.

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:AttachedBehaviorTest.AttachedBehaviorTest"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <TextBlock local:ContextMenuLeftClickBehavior.IsLeftClickEnabled="True">Some Text Goes Here
                <TextBlock.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Test1" />
                    </ContextMenu>
                </TextBlock.ContextMenu>            
            </TextBlock>

        </StackPanel>
    </Grid>
</Window>
Namespace AttachedBehaviorTest

    Public NotInheritable Class ContextMenuLeftClickBehavior

        Private Sub New()
        End Sub

        Public Shared Function GetIsLeftClickEnabled(obj As DependencyObject) As Boolean
            Return CBool(obj.GetValue(IsLeftClickEnabled))
        End Function

        Public Shared Sub SetIsLeftClickEnabled(obj As DependencyObject, value As Boolean)
            obj.SetValue(IsLeftClickEnabled, value)
        End Sub

        Public Shared ReadOnly IsLeftClickEnabled As DependencyProperty = _
            DependencyProperty.RegisterAttached("IsLeftClickEnabled", GetType(Boolean), GetType(ContextMenuLeftClickBehavior), New UIPropertyMetadata(False, AddressOf OnIsLeftClickEnabled))

        Private Shared Sub OnIsLeftClickEnabled(sender As Object, e As DependencyPropertyChangedEventArgs)
            Dim fe As FrameworkElement = TryCast(sender, FrameworkElement)
            If fe IsNot Nothing Then
                Dim IsEnabled As Boolean = CBool(e.NewValue)
                If IsEnabled = True Then
                    AddHandler fe.MouseLeftButtonUp, AddressOf OnMouseLeftButtonUp
                    Debug.Print("Added Handlers")
                Else
                    RemoveHandler fe.MouseLeftButtonUp, AddressOf OnMouseLeftButtonUp
                    Debug.Print("RemovedHandlers")
                End If 
            End If
        End Sub

        Private Shared Sub OnMouseLeftButtonUp(sender As Object, e As RoutedEventArgs)
            Debug.Print("OnMouseLeftButtonUp")
            Dim fe As FrameworkElement = TryCast(sender, FrameworkElement)
            If fe IsNot Nothing Then
                'Next Line is Needed if Context Menu items are Data Bound
                'fe.ContextMenu.DataContext = fe.DataContext
                fe.ContextMenu.IsOpen = True
            End If
        End Sub

    End Class

End Namespace
0 голосов
/ 04 марта 2019

Этот ответ выполняет ту же работу, что и ответ @nightcoder (спасибо за вдохновение!). Он использует Blend-стиль поведения, который является более современным подходом по сравнению с присоединенным свойством.

using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Interactivity;

/// <summary>
/// Add this to any button menu allow a left click to open the context menu as well as the right.
/// </summary>
public class ContextMenuLeftClickBehavior : Behavior<ButtonBase>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        this.AssociatedObject.Loaded += this.OnWindowLoaded;
        this.AssociatedObject.Unloaded += this.OnWindowUnloaded;
    }

    private void OnWindowLoaded(object sender, RoutedEventArgs e)
    {
        this.AssociatedObject.Click += OnMouseLeftButtonUp;
    }

    private void OnWindowUnloaded(object sender, RoutedEventArgs e)
    {
        this.AssociatedObject.Click -= OnMouseLeftButtonUp; // Cannot override OnDetached(), as this is not called on Dispose. Known issue in WPF.
    }

    private static void OnMouseLeftButtonUp(object sender, RoutedEventArgs e)
    {
        if (sender is ButtonBase fe && fe.ContextMenu != null)
        {
            if (fe.ContextMenu != null)
            {
                // If we use binding in our context menu, then it's DataContext won't be set when we show the menu on left click. It
                // seems setting DataContext for ContextMenu is hardcoded in WPF when user right clicks on a control? So we have to set
                // up ContextMenu.DataContext manually here.
                if (fe.ContextMenu?.DataContext == null)
                {
                    fe.ContextMenu?.SetBinding(FrameworkElement.DataContextProperty, new Binding { Source = fe.DataContext });
                }

                fe.ContextMenu.IsOpen = true;
            }
        }
    }
}

Затем добавьте поведение к кнопке:

<Button>
    <i:Interaction.Behaviors>
        <attachedProperties:ContextMenuLeftClickBehavior/>
    </i:Interaction.Behaviors>                                                
<Button>

Такие элементы, как Ellipse или Rectangle, не имеют события OnClick, что означает, что на самом деле ничего не работает очень хорошо для чего-либо интерактивного. Так что заверните все в кнопку, чтобы получить это OnClick событие. Можно также намекнуть, что область доступна для нажатия, если навести курсор мыши на курсор мыши.

<Button.Style>
    <Style TargetType="{x:Type Button}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Ellipse Fill="{TemplateBinding Background}" Width="16" Height="16"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <!--  Bind to custom color in ViewModel -->
                <Setter Property="Background" Value="{Binding CustomBrush}"/>
                <Setter Property="Cursor" Value="Hand"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Button.Style>
0 голосов
/ 29 декабря 2014

Забудьте о «единственном xaml». Эту проблему можно решить, если вы включите ее в привязанное поведение.

Вот способ показать контекстное меню при щелчке левой кнопкой мыши:

Создайте новый обработчик левой кнопки для элемента Border:

<Border x:Name="Win"
        Width="40"
        Height="40"
        Background="Purple"
        MouseLeftButtonUp="UIElement_OnMouseLeftButtonUp">

и затем добавьте это:

private void UIElement_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    e.Handled = true;

    var mouseDownEvent =
        new MouseButtonEventArgs(Mouse.PrimaryDevice,
            Environment.TickCount,
            MouseButton.Right)
        {
            RoutedEvent = Mouse.MouseUpEvent,
            Source = Win,
        };


    InputManager.Current.ProcessInput(mouseDownEvent);
}

Что он делает, он в основном отображает щелчок левой кнопкой мыши в щелчок правой кнопкой мыши. Для повторного использования вы можете заключить это в прикрепленное поведение.

...