ContextMenu.PlacementTarget is not getting set, no idea why - wpf

<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource ContentTemplateB}" Grid.Row="0" />
<ContentControl Name="uiContentPresenter" Content="{Binding ContentView}" Grid.Row="1" Height="0" />
<ContentControl DataContext="{Binding IsContentDisplayed}" DataContextChanged="IsDisplayed_Changed" Visibility="Collapsed" />
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Text"
Command="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
The above data template is applied to an ItemsControl. The issue is that for the ContextMenu that is specified for the Grid, the PlacementTarget property is never actually getting set to anything so I cannot get to the Tag property of the Grid which is necessary for passing the Command that should execute on the parent UserControl down to the context menu. I've based this approach off of similar examples such as this: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0244fbb0-fd5f-4a03-bd7b-978d7cbe1be3/
I've not been able to identify any other good way to pass this command down. This is setup this way because we are using an MVVM approach so the command we have to execute lives in the View Model of the user control this template is applied in. I've tried explicitly setting the PlacementTarget in a few different ways but it still always shows up as not set.

I realise that this is old and answered, but it doesn't seem properly answered. I came across a similar post and left a full answer. You might like to take a look as you can get it working with just a few adjustments to your code.
First, name your view UserControl... I generally name all of mine This for simplicity. Then remembering that our view model is bound to the DataContext of the UserControl, we can bind to the view model using {Binding DataContext, ElementName=This}.
So now we can bind to the view model, we have to connect that with the ContextMenu.DataContext. I use the Tag property of the object with the ContextMenu (the PlacementTarget) as that connection, in this example, a Grid:
<DataTemplate x:Key="YourTemplate" DataType="{x:Type DataTypes:YourDataType}">
<Grid ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext,
ElementName=This}">
...
</Grid>
</DataTemplate>
We can then access the view model properties and commands in the ContextMenu by binding the ContextMenu.DataContext property to the PlacementTarget.Tag property (of the Grid in our example):
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.Tag, RelativeSource=
{RelativeSource Self}}">
<MenuItem Header="Delete" Command="{Binding DeleteFile}" CommandParameter=
"{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource
AncestorType=ContextMenu}}" CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
Note the binding on the MenuItem.CommandTarget property. Setting this ensures that the target element on which the specified command is raised is the PlacementTarget, or the Grid in this case.
Also note the CommandParameter binding. This binds to the DataContext of the PlacementTarget, or the Grid in this case. The DataContext of the Grid will be inherited from the DataTemplate and so your data item is now bound to the object parameter in your Command if you're using some implementation of the ICommand interface:
public bool CanExecuteDeleteFileCommand(object parameter)
{
return ((YourDataType)parameter).IsInvalid;
}
public void ExecuteDeleteFileCommand(object parameter)
{
Delete((YourDataType)parameter);
}
Or if you are using some kind of RelayCommand delegates directly in your view model:
public ICommand Remove
{
get
{
return new ActionCommand(execute => Delete((YourDataType)execute),
canExecute => return ((YourDataType)canExecute).IsInvalid);
}
}

We have the same problem, but it works randomly. A contextmenu inside the controltemplate in a style for a listbox. We have tried to move the contextmenu to different levels inside the template but the same error occurs.
We think it might be connected to the refreshing of our ICollectionView that is the itemssource of the ListBox.
It seems that when the view refreshes the relative source binding inside the contextmenu is being evaluated before the PlacementTarget is being set.
It feels like a bug in either collectionviewsource or the ContextMenu of WPF...

Here is a working standalone XAML-only example based on your test case: a ContextMenu that retrieves a Command from the DataContext of its PlacementTarget using a Tag. You can reintroduce portions of your code until it stops working to try to find where the problem is:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point X="10" Y="20"/>
<Point X="30" Y="40"/>
</PointCollection>
<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}}">
<TextBlock Text="{Binding X}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</Grid.Resources>
<DockPanel DataContext="{x:Static ApplicationCommands.Open}">
<ListBox ItemTemplate="{StaticResource _ItemTemplateA}" ItemsSource="{StaticResource sampleData}"/>
</DockPanel>
</Grid>

In this post,
ContextMenu.PlacementTarget is filled up from ContextMenuService.PlacementTarget when you do right click with mouse on button.
It means ContextMenu.PlacementTarget is filled up when the menu is shown up. You can check that by snoop.
EDIT 1
This code works fine.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowBaseStyle}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Style="{StaticResource ContextMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}">
<MenuItem Header="Enable" Command="{Binding Path=PlacementTarget.Tag.(viewModels:PrinterListPageViewModel.EnableCommand), RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>

Related

MVVM How to bind command to ContextMenu

I am completely confused using relativeSource and ancestorLevel.
Relative source is used for to get source from another elements. But to do that successfully you have to count at what level is that element. (How to debug?) It is most confusing part in WPF.
In my example i have context menu that i want to bind datasource and then command. How would binding must be to get command in my vm? Thank you
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--Page Header info content-->
<Grid Grid.Row="0">
<TextBlock Text="{Binding SelectedUser.Name}"/>
<TextBlock Text="{Binding ElementName=myGrd, Path=CurrentColumn.DisplayIndex}"/>
</Grid>
<!--Datagrid content-->
<DataGrid x:Name="myGrd"
SelectionMode="Single"
SelectionUnit="Cell"
CurrentItem="{Binding SelectedUser, Mode=TwoWay}"
CurrentColumn="{Binding CurrentColumn, Mode=TwoWay}"
IsReadOnly="True"
Grid.Row="1"
ItemsSource="{Binding FilteredUserList}"
AutoGenerateColumns="True"
CanUserAddRows="False"
>
<DataGrid.Resources>
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Items>
<MenuItem Header="{Binding
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type Page},
AncestorLevel=4}, Path=vm}" />
</ContextMenu.Items>
</ContextMenu>
</DataGrid.Resources>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
You can't use RelativeSource in ContextMenu, because the menu is not a part of the visual tree. However this can be avoided by using Binding Source and x:Reference.
I assume your ViewModel looks like this
public class UserViewModel
{
public string Header { get; set; }
public ICommand MyCommand { get; }
... more code
}
Now let's bind Header and MyCommand properties of the VM
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Items>
<MenuItem Header="{Binding Header, Source={x:Reference vm}}"
Command="{Binding MyCommand, Source={x:Reference vm}}"/>
</ContextMenu.Items>
</ContextMenu>
The important part is to have the ViewModel somewhere in the visual tree and set its x:Name, just like you've done in your example
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
If you still want to know more about RelativeSource, this question seems to have the same problem as you. Basically Path of the binding has to be DataContext.MyViewModelProperty and the RelativeSource of the binding must be and element with DataContext set to the ViewModel.

WPF context menu whose items are defined as data templates

I have a list view that displays a collection of items, each item has as its underlying data a view model (MVVM).
What I would like to do is display different menu items within the context menu when the user right clicks one of these list view items. The menu items displayed is dependent on the type of the item selected (i.e. the type of the underlying view model).
I would expect the below to work, but it does not (no items are displayed in the context menu).
<ListView.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}">
<ContextMenu.Resources>
<DataTemplate DataType="{x:Type ViewModels:ViewModel1}">
<MenuItem Header="DoStuffForVM1" Command="{Binding DoStuffForVM1Command}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:ViewModel2}">
<MenuItem Header="DoStuffForVM2" Command="{Binding DoStuffForVM2Command}"/>
</DataTemplate>
</ContextMenu.Resources>
<ContentPresenter ContentSource="{Binding}" />
</ContextMenu>
</ListView.ContextMenu>
Any ideas?
Thanks.
this works for me:
<ListView.ContextMenu>
<ContextMenu>
<ContentPresenter Content="{Binding Path=PlacementTarget.SelectedItem,
RelativeSource={RelativeSource AncestorType=ContextMenu}}" >
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type ViewModels:ViewModel1}">
<MenuItem Header="DoStuffForVM1" Command="{Binding DoStuffForVM1Command}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:ViewModel2}">
<MenuItem Header="DoStuffForVM2" Command="{Binding DoStuffForVM2Command}"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</ContextMenu>
</ListView.ContextMenu>
That is because you are setting the ContextMenu of the ListView, so your DataContext is the outer context of the ListView, rather than the ViewModel of an individual list item.
You need to set the ContextMenu of each item instead. For instance, using a style:
<ListView.Resources>
<ContextMenu x:Key="ItemContextMenu">
<MenuItem Header="DoStuffForVM1" Command="{Binding DataContext.DoStuffForVM1Command, RelativeSource={RelativeSource AncestorType={x:Type ListView}}"/>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}"/>
</Style>
</ListView.ItemContainerStyle>
However, the above does not account for the different types of menu.
If you are defining data templates for your listview for each type, the easiest way would be to define the ContextMenu explicitly in each DataTemplate.
If not, you'll have to do it with triggers in the style. You'll probably have to write a converter to be able to trigger on object type.

WPF + Binding Command and Header of a Context Menu Item using MVVM

I created a WPF application and am following MVVM Pattern. I have a context menu in my xaml and I need to bind the command and Header text. Using the following code I can bind the Header of the context menu with the "MenuItemName" which is a property in BOList which is an observable collection. My issue is that command is not getting fired? I changes the Item source of the Context Menu to datacontext
(DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}")
Command is working fine but my header is getting blank. Is there a way to bind my header and command of a menu item? Here the command MenuClick is a Icommand property in the VM and MenuItemName is a property inside BOList which is an observable collection binded to my ListBox.
<Grid>
<StackPanel Orientation="Vertical">
<Button x:Name="btnClickMe" Command="{Binding ButtonCommand}" Content="Click Me" />
<ListBox ItemsSource="{Binding BOList}" x:Name="lstDemo" SelectedItem="{Binding BussinessObj,Mode=OneWayToSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="stkStyleRender" Orientation="Horizontal" Background="Cyan" Width="525" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" >
<TextBlock x:Name="txtId" Text="{Binding FirstName}"></TextBlock>
<TextBlock x:Name="txtName" Text="{Binding LastName}"></TextBlock>
<StackPanel.ContextMenu>
<ContextMenu x:Name="cntMnuTest" ItemsSource ="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" >
<MenuItem Header="{Binding MenuItemName}" Command="{Binding MenuClick}" CommandParameter="Icon"></MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Hi Identified the issue.
If we set Item Source of the context menu to BOList (observable collection ) command wont get fired because the ICommand definition is in Window data context (vm).
We need to handle the code like wise.
Since debugging is not possible for binding , I was beating around the bush :-)
This link helped me a lot WPF Tutorial - Debug Databinding Issues in WPF
In context menu use DataContext instead of using Items source
then bind your menu item
try this:
<MenuItem Header="{Binding Path=PlacementTarget.MenuItemName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}" Command="{Binding Path=PlacementTarget.MenuClick, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}" />

TreeView ContextMenu MVVM Binding

I currently have a UserControl that uses the MVVM model.
In that control there is a TreeView, which displays some items. I have added a HierarchicalDataTemplate for this TreeView and in that template is a ContextMenu for the Items.
In the ViewModel, which is DataContext of the control (named RestoresTreeViewControl) is a command I want to bind one of the menu items to. However what I have done doesn't seem to be working. I am getting the usual can't find source for binding reference.
Here is the bit of code for the datatemplate that tried to bind the EditDatabaseCommand to one of the menu items.
<HierarchicalDataTemplate DataType="{x:Type model:Database}" >
<StackPanel>
<TextBlock Text="{Binding Name}" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" Command="{Binding ElementName=RestoresTreeViewControl, Path=DataContext.EditDatabaseCommand}" />
<MenuItem Header="Delete"/>
<Separator/>
<MenuItem Header="Test Connection"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
Here is a section of the ViewModel where the command is.
public ICommand EditDatabaseCommand { get; private set; }
Unfortunately the ContextMenu is not in the VisualTree, so it's not going to see your DataContext. What you can do is something like this (copied from here: MVVM binding command to contextmenu item)
<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}"
Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}}}" Command = "{Binding
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}},
Path=DataContext.ConnectCommand}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
CommandParameter="{Binding Name}"
Command="{Binding Path=PlacementTarget.Tag.DataContext.RemoveCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
So simply use PlacementTarget.Tag to find your ViewModel.
You can try tracing the binding:
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
...
{binding ... diag:PresentationTraceSources.TraceLevel="High"}
However requiring the users (even if it is just yourself) of your control to name each instance of "RestoresTreeViewControl" rather burdensome.
Try:
{Binding Path=... RelativeSource={ FindAncestor, AncestorType={x:TheRestoresTreeViewControlType}} }
That probably has to do with the inheritance context.
See: Binding WPF ContextMenu MenuItem to UserControl Property vs ViewModel Property

Binding a context menu to a ListBox's Items collection

I'm trying to create a context menu for a list box which displays elements in the context menu from the list box. I am able to accomplish this by using the following XAML:
<Window.Resources>
<ContextMenu x:Key="contextMenu"
ItemsSource="{Binding Items,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" >
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ContextMenu" Value="{StaticResource contextMenu}"/>
</Style>
</Window.Resources>
This works great for one list box. However, when I have a second list box, the context menu keeps showing the elements from the first list box. In other words, the ItemsSource of the context menu does not change. Only the first time that the context menu is opened is the ItemsSource property set. For example:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox x:Name="first" >
<ListBoxItem>First 1</ListBoxItem>
<ListBoxItem>First 2</ListBoxItem>
<ListBoxItem>First 3</ListBoxItem>
<ListBoxItem>First 4</ListBoxItem>
<ListBoxItem>First 5</ListBoxItem>
</ListBox>
<ListBox x:Name="second" Grid.Column="2" >
<ListBoxItem>Second 1</ListBoxItem>
<ListBoxItem>Second 2</ListBoxItem>
<ListBoxItem>Second 3</ListBoxItem>
<ListBoxItem>Second 4</ListBoxItem>
<ListBoxItem>Second 5</ListBoxItem>
</ListBox>
</Grid>
I would like to set the context menu in a Style because I have many instances of a listbox and do not want to define a separate context menu for each listbox.
UPDATE:
I finally figured out how to fix it. I just need to bind to the PlacementTarget.Items and using a self relative source instead of using a find ancestor relative source.
<ContextMenu x:Key="contextMenu"
ItemsSource="{Binding PlacementTarget.Items,
RelativeSource={RelativeSource Self}}" >
Found the answer, I just need to bind to the PlacementTarget.Items and using a self relative source instead of using a find ancestor relative source.
<ContextMenu x:Key="contextMenu"
ItemsSource="{Binding PlacementTarget.Items,
RelativeSource={RelativeSource Self}}" >
I think the issue you're having here is due to the fact that the context menu is part of a different visual tree. That is, you cannot find the ListBox ancestor because it is not actually an ancestor of the context menu.
If you look at the debug panel of Visual Studio, you should see some warnings about the failing binding expression. Do you?

Resources