I have a WPF application..In which I have an Image control in Xaml file.
On right click of this image I have a context menu.
I would like to have same to be displayed on "Left click" also.
How do I do this in MVVM way ?
Here is a XAML only solution.
Just add this style to your button.
This will cause the context menu to open on both left and right click. Enjoy!
<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>
You can do this by using the MouseDown event of an Image like this
<Image ... MouseDown="Image_MouseDown">
<Image.ContextMenu>
<ContextMenu>
<MenuItem .../>
<MenuItem .../>
</ContextMenu>
</Image.ContextMenu>
</Image>
And then show the ContextMenu in the EventHandler in code behind
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;
}
}
You can invent your own DependencyProperty which opens a context menu when image is clicked, just like this:
<Image Source="..." local:ClickOpensContextMenuBehavior.Enabled="True">
<Image.ContextMenu>...
</Image.ContextMenu>
</Image>
And here is a C# code for that property:
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;
}
}
}
}
If you want to do this just in Xaml without using code-behind you can use Expression Blend's triggers support:
...
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>
you only need add the code into function Image_MouseDown
e.Handled = true;
Then it will not disappear.
Hey I came across the same problem looking for a solution which I didn't find here.
I don't know anything about MVVM so it's probably not MVVM conform but it worked for me.
Step 1: Give your context menu a name.
<Button.ContextMenu>
<ContextMenu Name="cmTabs"/>
</Button.ContextMenu>
Step 2: Double click the control object and insert this code. Order matters!
Private Sub Button_Click_1(sender As Object, e As Windows.RoutedEventArgs)
cmTabs.StaysOpen = True
cmTabs.IsOpen = True
End Sub
Step 3: Enjoy
This will react for left & right click. It's a button with a ImageBrush with a ControlTemplate.
you can bind the Isopen Property of the contextMenu to a property in your viewModel like "IsContextMenuOpen".
but the problem is your can't bind directly the contextmenu to your viewModel because it's not a part of your userControl hiarchy.So to resolve this you should bing the tag property to the dataontext of your view.
<Image Tag="{Binding DataContext, ElementName=YourUserControlName}">
<ContextMenu IsOpen="{Binding PlacementTarget.Tag.IsContextMenuOpen,Mode=OneWay}" >
.....
</ContextMenu>
<Image>
Good luck.
Interactivity is old and not support any more. The new approach for the implementation is:
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>
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;
}
Related
When contextmenu is opening, I want to fill a textbox with source control's information, for example its name and so on, for viewing and editing purpose.
But I however cannot access the opening contextmenu anyway.
Maybe this is because my less understanding of control's xaml style.
The following is my xaml:
the context menu part: I want to fill the TextBox when contextmenu is opening.
<ContextMenu x:Key="VacUnitContextMenu" >
<MenuItem Header="Property">
<MenuItem>
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<Label Content="Name" />
<TextBox Width="60" Name="VacName" Margin="2,0" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem Header="Get" Command="{x:Static s:DesignerCanvas.VacUnitNameGet}"/>
</MenuItem>
<MenuItem Header="X">
<MenuItem Header="TEST" Command="{x:Static s:DesignerCanvas.XTest}">
<MenuItem.Icon>
<Image Source="Images/SendToBack.png" Width="16"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</ContextMenu>
the context menu is used in this way:
<!-- VacUnit Style -->
<Style TargetType="{x:Type s:VacUnit}" >
<Setter Property="MinWidth" Value="10"/>
<Setter Property="MinHeight" Value="10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type s:VacUnit}">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{StaticResource VacUnitContextMenu}"
>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I use conextmenuopening event, and try to fill the textbox there,
How to: Handle the ContextMenuOpening Event
But I cannnot get the contextmenu, it is null:
FrameworkElement fe = e.Source as FrameworkElement;
ContextMenu cm = fe.ContextMenu;
Many thanks in advance.
Ting
Getting a reference to the ContextMenu should be easy provided that you handle the ContextMenuOpening event for the Grid in the ControlTemplate:
<ControlTemplate TargetType="{x:Type s:VacUnit}">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{StaticResource VacUnitContextMenu}"
ContextMenuOpening="Grid_ContextMenuOpening">
</Grid>
</ControlTemplate>
What's a bit trickier is to get a reference to the TextBox. You need to wait until it has been loaded:
private void Grid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
Grid grid = (Grid)sender;
ContextMenu cm = grid.ContextMenu;
if (cm != null)
cm.Opened += Cm_Opened;
}
private void Cm_Opened(object sender, RoutedEventArgs e)
{
ContextMenu cm = (ContextMenu)sender;
cm.Opened -= Cm_Opened;
MenuItem header = cm.Items[0] as MenuItem;
MenuItem child = header.Items[0] as MenuItem;
StackPanel sp = child.Header as StackPanel;
(sp.Children[1] as TextBox).Background = Brushes.Yellow;
}
Probably e.Source is not a Grid where the context menu is defined.
You can search parent elements until you will find the element where the context menu is.
private void Xxx_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
var fe = e.Source as FrameworkElement;
var contextMenu = FindContextMenu(fe);
if(contextMenu != null)
{
// your code
}
}
// helper
private ContextMenu FindContextMenu(FrameworkElement fe)
{
if(fe == null)
{
return null;
}
if(fe.ContextMenu != null)
{
return fe.ContextMenu;
}
else
{
var parent = VisualTreeHelper.GetParent(fe) as FrameworkElement;
return FindContextMenu(parent);
}
}
I would like to get buttons like in AX, where some buttons only open a menu with more menu items underneath. I have found the code below and it works fine. But when I left-click on the button, it shows the context menu where the mouse pointer is. When I right-click on the button, it shows the context menu exactly under the button nicely. I would like the left-click to work like the right-click.
The problem is that ContextMenuService.Placement="Bottom" is only working when I right-click.
<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>
Using a Storyboard for setting a single property value is pretty odd. This works, but looks somewhat ugly.
But this is also the reason why your ContextMenuService.Placement="Bottom" setting doesn't work: you just don't invoke the ContextMenuService for opening the context menu via the Storyboard, so the PlacementTarget won't be set to your button.
I'd suggest you to create a simple attached property and re-use it in your views:
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;
}
}
}
Usage might look like this:
<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>
Note the yourNS namespace - this is a XAML namespace mapped to the CLR namespace where the attached property class ContextMenuTools is located, e.g.:
xmlns:yourNS="clr-namespace:WpfApp1"
You could also use this attached property on any WPF control implementing IInputElement, not only on buttons.
This should do the trick instead of using a Button
<Menu>
<MenuItem Header="Options">
<MenuItem Header="Do A"/>
<MenuItem Header="Do B"/>
<MenuItem Header="Do C"/>
</MenuItem>
</Menu>
How can I disable tree items collapsing/expanding when I double-click on tree item? I still would like to do this by clicking on toggle button, but not when I double-click on item.
This is XAML I have:
<TreeView Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Categories}" helpers:TreeViewHelper.SelectedItem="{Binding SelectedCategory, Mode=TwoWay}" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type core:Category}" ItemsSource="{Binding SubCategories}">
<Label Content="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CreateGroupsFromCategoryCommand , Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I would like to do this only in XAML.
Thank you for your help.
You should suppress the double click event on treeviewitem:
<TreeView Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Categories}" helpers:TreeViewHelper.SelectedItem="{Binding SelectedCategory, Mode=TwoWay}" TreeViewItem.PreviewMouseDoubleClick="TreeViewItem_PreviewMouseDoubleClick" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type core:Category}" ItemsSource="{Binding SubCategories}">
<Label Content="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CreateGroupsFromCategoryCommand , Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code Behind:
private void TreeViewItem_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
//this will suppress the event that is causing the nodes to expand/contract
e.Handled = true;
}
You could implement a custom EventTrigger in your views code behind, as suggested here: https://stackoverflow.com/a/7688249/1206431
public class HandlingEventTrigger : System.Windows.Interactivity.EventTrigger
{
protected override void OnEvent(System.EventArgs eventArgs)
{
var routedEventArgs = eventArgs as RoutedEventArgs;
if (routedEventArgs != null)
routedEventArgs.Handled = true;
base.OnEvent(eventArgs);
}
}
Add the namespace to your view like this xmlns:views="clr-namespace:YourNamespace.Views"
And then replace <i:EventTrigger EventName="MouseDoubleClick"> with <local:HandlingEventTrigger EventName="PreviewMouseDoubleClick">.
The HandlingEventTrigger will stop the event from being passed further up the visual tree and therefore not expanding/collapsing your tree, and using PreviewMouseDoubleClick instead of MouseDoubleClick will allow you to still fire your own command.
XAML:
<TreeView x:Name="TreeView1" MouseDoubleClick="TreeView1_MouseDoubleClick" />
C# Code:
private void TreeView1_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
}
Works fine for me.
I know this issue is old, but I found a slightly different way that preserves the wanted double-click behavior but stops the expand/collapse in case it helps someone.
Keep your typical double click method:
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Do something
}
Create an inherited class from TreeView (such as MyTreeView). Add the following to that inherited class:
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
var tvi = GetTreeViewItemFromObject(ItemContainerGenerator, SelectedItem);
if (tvi != null) tvi.IsExpanded = true;
base.OnMouseDoubleClick(e);
}
/// <summary>
/// Use the object to get its Tree View item
/// </summary>
/// <param name="container"></param>
/// <param name="targetObject"></param>
/// <returns></returns>
private TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object targetObject)
{
if (container.ContainerFromItem(targetObject) is TreeViewItem target) return target;
for (int i = 0; i < container.Items.Count; i++)
if ((container.ContainerFromIndex(i) as TreeViewItem)?.ItemContainerGenerator is ItemContainerGenerator childContainer)
if (GetTreeViewItemFromObject(childContainer, targetObject) is TreeViewItem childTarget) return childTarget;
return null;
}
The GetTreeViewItemFromObject method was lifted from someplace else. I don't remember where.
I have here a behavior handling drag & drop, and I attach the mouse events through the view. The item's viewmodel inherit from IDraggable. I use Caliburn.Micro as MVVM framework.
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Width" Value="{Binding Path=Width}" />
<Setter Property="Height" Value="{Binding Path=Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<!-- item contents -->
<i:Interaction.Behaviors>
<behaviors:DragOnCanvasBehavior DraggableItem="{Binding}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction CommandName="StartDrag" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseLeftButtonUp">
<i:InvokeCommandAction CommandName="StopDrag" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseMove">
<i:InvokeCommandAction CommandName="Dragging" />
</i:EventTrigger>
</i:Interaction.Triggers>
</behaviors:DragOnCanvasBehavior>
</i:Interaction.Behaviors>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then the behavior attaches the mouse events to the element's mouse handlers :
public class DragOnCanvasBehavior : Behavior<DependencyObject>
{
public static readonly DependencyProperty DraggableItemProperty =
DependencyProperty.RegisterAttached(
"DraggableItem",
typeof(IDraggable),
typeof(DragOnCanvasBehavior),
new PropertyMetadata(new PropertyChangedCallback((d, e) =>
{
((DragOnCanvasBehavior)d).draggable = (IDraggable)e.NewValue;
})));
private IDraggable draggable;
public DragOnCanvasBehavior()
{
this.StartDrag = new RelayCommand((o) =>
{
((UIElement)this.AssociatedObject).MouseLeftButtonDown += this.ElementOnMouseLeftButtonDown;
});
this.StopDrag = new RelayCommand((o) =>
{
((UIElement)this.AssociatedObject).MouseLeftButtonUp += this.ElementOnMouseLeftButtonUp;
});
this.Dragging = new RelayCommand((o) =>
{
((UIElement)this.AssociatedObject).MouseMove += this.ElementOnMouseMove;
});
}
public IDraggable DraggableItem
{
get { return (IDraggable)this.GetValue(DraggableItemProperty); }
set { this.SetValue(DraggableItemProperty, value); }
}
public ICommand Dragging { get; private set; }
public ICommand StartDrag { get; private set; }
public ICommand StopDrag { get; private set; }
// these handle the drag through the IDraggable properties
// and the mouse event args
private void ElementOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {}
private void ElementOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {}
private void ElementOnMouseMove(object sender, MouseEventArgs e) {}
}
It works, but I'm pretty sure I'm doing something wrong here. The mouse events are attached in the behavior constructor, but they are "hard-linked" to the events (meaning I can't change the triggers to something else than mousedown/up/move).
I must, however, have access to the mouse position in the ElementOnMousexxx methods, so I'm not sure how to do it properly.
OK so it turns out that you don't need the MouseEventArgs to get the mouse cursor position, you can just use System.Windows.Input.Mouse. You also don't need the "sender" object, when using Behavior<T> you can just use this.AssociatedObject
public DragOnCanvasBehavior()
{
this.StartDrag = new RelayCommand((o) =>
{
this.OnStartDrag();
});
}
private void OnStartDrag()
{
// get mouse position
this.mouseStartPosition = Mouse.GetPosition(Application.Current.MainWindow);
// access control properties
((UIElement)this.AssociatedObject).CaptureMouse();
}
Here is a problem.
I am displaying context menu on a button click and the menu command is bind to ICommand in the view model. Menu is displaying on the button click as well as on the right click. The problem is menu click is not firing when I click button and then click context menu, but I can confirm that menu is working when I right click on button and then click on menu.
<Button Grid.Row="3" Width="500" Height="30" Name="cmButton" >
Button with Context Menu
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=Self}}" >
<MenuItem DataContext="{Binding}" Header="New Layout Element..." Command="{Binding Path=SubmitBtn}" />
</ContextMenu>
</Button.ContextMenu>
<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>
</Style>
</Button.Style>
</Button>
I can confirm there is nothing wrong in my view model because command is firing when I do right click on button then click on context menu.
PlacementTarget is null when you manually set ContextMenu.IsOpen property because it is set to actual value only once it's open by right clicking on target control. (PopUpService class is responsible for setting this value to actual target).
Since PlacementTarget is null in case when you open it via Storyboard, binding is not able to resolve actual command it's binded to.
So, issue is you need to pass on the DataContext of Button to the MenuItem so that binding can be resolved. (MenuItem are not is same visual tree as that of button). That can be achieved via two ways:
Using x:Reference (available in WPF 4.0 and higher) but you need to declare dummy control so that it can be referenced to get DataContext with Visibility set to Collapsed.
<FrameworkElement x:Name="dummyControl" Visibility="Collapsed"/>
<Button Width="100" Height="30" Name="cmButton">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="New Layout Element..."
Command="{Binding Path=DataContext.SubmitBtn,
Source={x:Reference dummyControl}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
Another interesting thing is Freezable objects inherit DataContext even if they don't lie in VisualTree so we can use this feature to overcome situations where we need to inherit DataContext.
First we need to create class inheriting from Freezable and exposing DP which can be bind to:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy));
}
Now we can use it in XAML like this:
<Button Width="100" Height="30" Name="cmButton">
<Button.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}"/>
</Button.Resources>
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="New Layout Element..."
Command="{Binding Path=Data.SubmitBtn,
Source={StaticResource proxy}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
It's happened because the DataContext of ContextMenu is null, you just need set him on event click from Button. Look the sample:
XAML:
<Button Content="More..." Click="ButtonMoreClick" ContextMenu="{StaticResource ContextMenu1}"/>
BEHIND CODE
private void ButtonMoreClick(object sender, RoutedEventArgs e)
{
var menu = (sender as Button).ContextMenu;
menu.DataContext = DataContext;
menu.IsOpen = true;
}
I hope help