Menus are the most well known of all user interface elements. Although their popularity has declined in recent years, in lieu of toolbar and the famous office style ribbon, they are still useful.
In WPF, a menu is constructed by a Menu class that mostly hosts MenuItem objects, that can nest to any needed level. Customizing the look of WPF menus (and context menus) can be surprisingly difficult.
The first, simplest, and most obvious way of customizing a WPF menu is to tweak its properties, such as Background, Foreground and FontSize. This, however, leaves much to be desired.
Here’s a simple try with an implicit style to be applied on all MenuItem objects:
<Style TargetType="MenuItem">
<Setter Property="Background" Value="Yellow" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="1" />
</Style>
We can change the Menu background brush as well. Here’s how this looks (with a classic Windows theme):
A few issues: The highlighted item’s background is always blue (or whatever is configured via the control panel), and the foreground white. And there’s the separator, looking completely out of place.
One way to address the selection is add a style trigger:
<Style.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter Property="Background" Value="Green" />
</Trigger>
</Style.Triggers>
This should work, but doesn’t work as expected. The trigger works only on the top level menu items, but not others:
The reason for the failed trigger is quite subtle, and relates to the way the MenuItem’s template is built.
So, what can we do about that? Do we have to write a completely new MenuItem control template? Fortunately, no.
Browsing through the properties of the MenuItem class, we come across a few static properties called SubmenuItemTemplateKey, TopLevelHeaderTemplateKey, TopLevelItemTemplateKey and SubmenuHeaderTemplateKey. These static properties are keys (pun intended) to a deeper customization of menu item.
The documentation states that these are keys of styles that are applied to menu items in various roles. This idea is pretty common in WPF, e.g. The ItemContainerStyle of ItemsControl and the FocusVisualStyle property of FrameworkElement. However, in this case the documentation is wrong. These keys are actually keys to a control template – not a style.
Armed with these insights, customization can be performed on a much more granular level. Unfortunately, this also means a control template must be supplied – a simple property change is not allowed. Here’s an example:
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuItemTemplateKey}" TargetType="MenuItem">
<Border Background="Yellow" x:Name="_grid" Padding="2" BorderThickness="1" BorderBrush="Yellow" >
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="24" SharedSizeGroup="IconGroup"/>
<ColumnDefinition Width="Auto" MinWidth="30" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<ContentPresenter ContentSource="Icon" Margin="{TemplateBinding Padding}" />
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" ContentSource="Header" RecognizesAccessKey="True" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter Property="Background" Value="Green" TargetName="_grid"/>
<Setter Property="BorderBrush" Value="Black" TargetName="_grid"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
This looks like this:
Definitely better. The “Recent Files” item is still in its default state because it’s defined by a different template key (SubmenuHeaderTemplateKey). It’s a bit more difficult, as we must add a popup element (or a StackPanel set as items host) to host the subitem of this header menu item:
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuHeaderTemplateKey}" TargetType="MenuItem">
<Border Background="Yellow" Padding="2" x:Name="_grid" BorderThickness="1" BorderBrush="Yellow" >
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="24" SharedSizeGroup="IconGroup"/>
<ColumnDefinition Width="Auto" MinWidth="30"/>
<ColumnDefinition Width="30" MinWidth="0" />
</Grid.ColumnDefinitions>
<Path Data="M 0,2 L 10,10 L 0,18 Z" Fill="Red" Grid.Column="2" Margin="20,0,0,0" />
<ContentPresenter ContentSource="Icon" Margin="{TemplateBinding Padding}" />
<ContentPresenter Grid.Column="1" ContentSource="Header" RecognizesAccessKey="True" Margin="{TemplateBinding Padding}" />
<Popup x:Name="popup" PlacementTarget="{Binding ElementName=_grid}" Placement="Right" HorizontalOffset="0">
<ItemsPresenter x:Name="_items" />
</Popup>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True" >
<Setter Property="Visibility" Value="Visible" TargetName="_items" />
<Setter Property="IsOpen" Value="True" TargetName="popup" />
<Setter Property="Background" Value="Green" TargetName="_grid"/>
<Setter Property="BorderBrush" Value="Black" TargetName="_grid"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Note the popup control with an ItemsPresenter. Using this approach we can customize all four possible menu item roles.
What about the dear separator, which seems completely out of place? It turns out the separator has a static property for a resource key named SeparatorStyleKey. In this case, it actually is a style, but ironically, we’ll replace its control template:
<Style TargetType="Separator" x:Key="{x:Static MenuItem.SeparatorStyleKey}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Separator">
<Border Background="Blue" Height="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here’s the final look: