Reputation: 337
In the following WPF ListView
, I created two ContextMenus
: One for the ListView itself and one to be used for each specific list item. It looks kind of like this:
<ListView ItemsSource="{Binding Path=MyListData}">
<ListView.ContextMenu>
<ContextMenu> <!-- menu for the entire list -->
<MenuItem Header="New Item"/>
<MenuItem Header="Sort by">
<MenuItem Header="Name"/>
<MenuItem Header="Author"/>
<MenuItem Header="Date"/>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu> <!-- menu for a specific item -->
<MenuItem Header="Edit"/>
<MenuItem Header="Remove"/>
<Separator/> <!-- note how the following is basically the same as the other menu -->
<MenuItem Header="New Item"/>
<MenuItem Header="Sort by">
<MenuItem Header="Name"/>
<MenuItem Header="Author"/>
<MenuItem Header="Date"/>
</MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<!-- ListView content -->
</ListView.View>
</ListView>
This works, but as you can see, the first Contextmenu's content is reused as part of the second one. This is because I want the general options (New Item / Sort by) to also be available when the user right-clicks an item rather than empty space. Since I'm planning to use this general structure more than once, I can imagine this getting rather messy, especially when all the necessary command bindings are taken into account.
I tried defining a DataTemplate
in <ListView.Resources>
as explained in answers to similar questions, but that won't work because apperently standalone MenuItem
s cannot be wrapped in a template. And if I include the <ContextMenu>
tags in the template, there's a runtime exception because a ContextMenu
can't live inside another ContextMenu
.
Is there any way to create reusable code snippets that would normally require specific parent elements? Some kind of template that's evaluated at compile time? All I want is more maintainable code.
Upvotes: 3
Views: 218
Reputation: 101563
You can do it like this:
<Window.Resources>
<MenuItem x:Key="newItem" Header="New Item" Command="{x:Static ApplicationCommands.New}" x:Shared="False" />
<MenuItem x:Key="sortBy" Header="Sort by" x:Shared="False">
<MenuItem Header="Name" Click="SortByNameClicked"/>
<MenuItem Header="Author"/>
<MenuItem Header="Date"/>
</MenuItem>
</Window.Resources>
<ListView ItemsSource="{Binding Path=MyListData}">
<ListView.ContextMenu>
<ContextMenu>
<!-- menu for the entire list -->
<StaticResource ResourceKey="newItem" />
<StaticResource ResourceKey="sortBy" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<!-- menu for a specific item -->
<MenuItem Header="Edit"/>
<MenuItem Header="Remove"/>
<Separator/>
<StaticResource ResourceKey="newItem" />
<StaticResource ResourceKey="sortBy" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Note x:Shared="false"
attribute. What it does is makes every reference to this resource to create new copy. By default the same instance of resource is reused, but that does not fit our scenario, because we need different instances for different menus (otherwise it will complain that same item cannot be children of multiple parents).
You can define command bindings and click events (as you see) as usual on such items.
Upvotes: 2