WPF context menu whose items are defined as data templates - wpf

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.

Related

WPF TreeView IsSelected Binding Throwing Null Reference Exception

I've got a tabcontrol that is bound to a tab collection in my view model. Right now there are 2 types of tabs and one of them has a treeview in it. When the tab is first created, the treeview selection works. When I switch tabs and come back to the tab with the treeview in it, the treeview items seem to become unbound. When I try to select one I get the following error:
System.Windows.Data Error: 8 : Cannot save value from target back to source.
BindingExpression:Path=IsSelected; DataItem='NavigationItem' (HashCode=50956576);
target element is 'TreeViewItem' (Name=''); target property is 'IsSelected' (type
'Boolean') NullReferenceException:'System.NullReferenceException:
Object reference not set to an instance of an object.
at System.ComponentModel.ReflectPropertyDescriptor.SetValue(
Object component, Object value)
at MS.Internal.Data.PropertyPathWorker.SetValue(Object item, Object value)
at MS.Internal.Data.ClrBindingWorker.UpdateValue(Object value)
at System.Windows.Data.BindingExpression.UpdateSource(Object value)'
I'm not sure why this is occurring. When debugging through the application my model still has all of the correct data, and the treeview even displays the information from the model meaning it has to have been bound correctly when the tab was initially changed. It's just when I try to select an item after having left the tab and come back. Here's the source for my tab control and the tab in question with a treeview in it:
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
...
</TabControl.ItemTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type tabs:ArchitectureTabViewModel}">
<views:ArchitectureTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type tabs:TestOrderTabViewModel}">
<views:TestOrderTab/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
And the tab in question:
<Grid DataContext="{Binding GraphNavigationModel}">
...
<TreeView ItemsSource="{Binding NavigationTree}" Background="#00ffffff" Margin="5" BorderThickness="0" MinWidth="200">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Text}">
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Grid>

context menu for removing items in listview

I have a ListView which displays a list of string values. I want to add a context menu entry for each item in the list to remove the selected item. My XAML looks like this:
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The problem is that the CommandParameter value is always null. I've added an additional button to remove the selected item to check if my command works. The button has exactly the same binding and removing items via the button works. The button looks like this:
<Button Content="Remove selected item"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}"/>
The command looks like this:
private ICommand _removeItem;
public ICommand RemoveItem
{
get { return _removeItem ?? (_removeItem = new RelayCommand(p => RemoveItemCommand((string)p))); }
}
private void RemoveItemCommand(string item)
{
if(!string.IsNullOrEmpty(item))
MyItems.Remove(item);
}
Any ideas why the selected item is null when opening the context menu? Maybe a focus problem of the listview?
H.B. is right. but you can also use RelativeSource Binding
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
ContextMenus are disconnected, you cannot use ElementName bindings. One workaround would be using Binding.Source and x:Reference which requires you to extract parts that use it to be in the resources (due to cyclical dependency errors). You can just put the whole context menu there.
An example:
<ListBox Name="lb" Height="200">
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="{Binding ActualHeight, Source={x:Reference lb}}" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ContextMenu>
<StaticResource ResourceKey="cm" />
</ListBox.ContextMenu>
</ListBox>
This work for me CommandParameter="{Binding}"

ContextMenu.PlacementTarget is not getting set, no idea why

<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>

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?

Copy Context Menu for ListView Control

What is the best way to add "copy to clipboard" functionality to a ListView control in WPF?
I tried adding an ApplicationCommands.Copy to either the ListView ContextMenu or the ListViewItem ContextMenu, but the command remains disabled.
Thanks,
Peter
Here is an xaml sample of one of my attempts...
<Window.Resources>
<ContextMenu x:Key="SharedInstanceContextMenu" x:Shared="True">
<MenuItem Header="Copy" Command="ApplicationCommands.Copy"/>
</ContextMenu>
</Window.Resources>
<ListBox Margin="12,233,225,68" Name="listBox1" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=UpToSourceCategoryByCategoryId.Category}" ContextMenu="{DynamicResource ResourceKey=SharedInstanceContextMenu}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How should I set the CommandTarget in this case?
Thanks,Peter
It looks like you need a CommandBinding.
Here is how I would probably go about doing what you trying to do.
<Window.CommandBindings>
<CommandBinding
Command="ApplicationCommands.Copy"
Executed="CopyCommandHandler"
CanExecute="CanCopyExecuteHandler" />
</Window.CommandBindings>
<Window.Resources>
<ContextMenu x:Key="SharedInstanceContextMenu">
<MenuItem Header="Copy" Command="ApplicationCommands.Copy"/>
</ContextMenu>
<Style x:Key="MyItemContainerStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu" Value="{StaticResource SharedInstanceContextMenu}" />
</Style>
</Window.Resources>
<ListBox ItemContainerStyle="{StaticResource MyItemContainerStyle}">
<ListBoxItem>One</ListBoxItem>
<ListBoxItem>Two</ListBoxItem>
<ListBoxItem>Three</ListBoxItem>
<ListBoxItem>Four</ListBoxItem>
</ListBox>
It is also possible to achieve this functionality via an attached property, as I described it on my blog. The idea is to register the ApplicationCommands.Copy command with the ListView and, when the command is executed, read the values from the data bindings.
You'll find a downloadable sample on the blog entry, too.

Resources