The default behavior of a WPF ContextMenu
is to display it when the user right-clicks. I want the ContextMenu
to show when the user left-clicks. It seems like this should be a simple property on ContextMenu
, but it is not.
I rigged it, so that I handle the LeftMouseButtonDown
event in the code-behind and then display the context menu.
I'm using MVVM in my project which means I'm using DataTemplate
s for the items that have the context menus. It would be much more elegant to get rid of the code-behind and find a way to display the context menu using triggers or properties in the XAML.
Any ideas or solutions to this issue?
I've just written and tested this based on HK1's answer (you can also read about attached properties in 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>
(note the comment inside the OnMouseLeftButtonUp() method)
What I would suggest doing is making a new static class with attached DependencyProperty. Call the class LeftClickContextMenu and the property Enabled (just ideas). When your registering the DependencyProperty add an on changed callback. Then in the property changed callback if Enabled is set to true then add a handler to the LeftMouseButtonDown event and do your stuff there. If Enabled is set to false remove the handler. This sould allow you to set it like a property on anything by simply using the following in your xaml.
<Border namespace:LeftClickContextMenu.Enabled="True" />
This technique is called an attached behavior and you can read more about it in this code project article: http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
While Caleb's answer is correct, it doesn't include working code. I setup an example using VB.NET (sorry) so I'm posting it here.
<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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With