左键单击显示ContextMenu仅使用XAML

时间:2009-02-17 01:39:18

标签: .net wpf xaml mvvm contextmenu

WPF ContextMenu的默认行为是在用户右键单击时显示它。我希望ContextMenu显示用户左键单击的时间。看起来这应该是ContextMenu上的一个简单属性,但事实并非如此。

我操纵它,以便在代码隐藏中处理LeftMouseButtonDown事件,然后显示上下文菜单。

我在我的项目中使用MVVM,这意味着我将DataTemplate用于具有上下文菜单的项目。摆脱代码隐藏并找到一种使用XAML中的触发器或属性显示上下文菜单的方法会更加优雅。

此问题的任何想法或解决方案?

5 个答案:

答案 0 :(得分:9)

我建议做的是创建一个带有DependencyProperty的新静态类。调用类LeftClickContextMenu和属性Enabled(只是想法)。注册DependencyProperty时添加一个更改后的回调。然后在属性更改回调中,如果Enabled设置为true,则向LeftMouseButtonDown事件添加处理程序并在那里执行您的操作。如果Enabled设置为false,则删除处理程序。这样您就可以通过在xaml中使用以下内容将其设置为任何属性。

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

此技术称为附加行为,您可以在此代码项目文章中阅读有关它的更多信息:http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx

答案 1 :(得分:7)

我刚刚根据HK1的答案编写并测试了这个(您还可以阅读Attached Properties Overview)中的附加属性:

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()方法中的注释)

答案 2 :(得分:4)

虽然Caleb的回答是正确的,但它并不包含工作代码。我使用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

答案 3 :(得分:0)

此答案与@nightcoder的答案完全相同(感谢您的启发!)。它使用Blend样式的行为,与附加属性相比,这是一种更现代的方法。

@Service
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserDao userDao;

@Transactional(readOnly=true)
@Override
public UserDetails loadUserByUsername(final String username)
        throws UsernameNotFoundException {

    com.rjproject.entities.User user = userDao.findByUserName(username);
    List<GrantedAuthority> authorities =
            buildUserAuthority(user.getAuthorities());

    return buildUserForAuthentication(user, authorities);

}

private User buildUserForAuthentication(com.rjproject.entities.User user,
                                        List<GrantedAuthority> authorities) {
    return new User(user.getUsername(), user.getPassword(),
            user.isEnabled(), true, true, true, authorities);
}

private List<GrantedAuthority> buildUserAuthority(Set<Authorities> userRoles) {

    Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();

    // Build user's authorities
    for (Authorities userRole : userRoles) {
        setAuths.add(new SimpleGrantedAuthority(userRole.getAuthority()));
    }

    List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);

    return Result;
}

然后将行为添加到按钮:

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> 事件,这意味着对于任何交互性而言,没有什么能很好地起作用。因此,将所有内容包装在一个按钮中,以获取该OnClick事件。还可以通过将鼠标光标移到鼠标悬停上的手来暗示该区域是可单击的。

OnClick

答案 4 :(得分:-1)

忘记&#34;只有xaml&#34;事情。当你将它包装成附加行为时,这可以很好地解决。

这是一种左键单击上下文菜单的方法:

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);
}

它做什么,它基本上将左键单击映射到右键单击。为了可重用,您可以将其包装为附加行为。