Nested MenuItem in WPF ToolBar
One of the most common questions asked on various WPF Forums is “How can we have Submenu or nested menuitems in WPF Toolbar”. Toolbar in WPF doesn’t show sub menuitems on mouseover or click event. So following code for Toolbar doesn’t work.
<ToolBar Name="ToolBar1" Height="25">
<!-- Regular Items -->
<Button Content="Item A"/>
<Button Content="Item B"/>
<!-- SubItems Menu For Special Items -->
<MenuItem Header="Special Items">
<MenuItem Header="Special Item A" />
<MenuItem Header="Special Item B" />
</MenuItem>
</ToolBar>
So I decided to create my own custom control which Inherits MenuItem and can handle mouseover or click event. MenuItem has a property IsSubMenuOpen which we can use to open submenu for MenuItem. Another useful property is StayOpenOnClick, which if we set to False then whenever user clicks outside of MenuItem, submenu will automatically get closed. SubMenuPlacementMode property in below code is to decide the direction in which Sub Menu will get open when we click Left Mouse button.
Imports System.Windows.Controls.Primitives
Public Class ToolBarMenuItem
Inherits System.Windows.Controls.MenuItem
Shared Sub New()
'This OverrideMetadata call tell the system that this element wants to provide a style that is different than its base class.
'This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(ToolBarMenuItem), New FrameworkPropertyMetadata(GetType(ToolBarMenuItem)))
End Sub
Public Enum PlacementMode
Bottom = 0
Top = 1
Right = 2
Left = 3
End Enum
Public Shared ReadOnly SubMenuPlacementModeProperty As DependencyProperty = DependencyProperty.Register("SubMenuPlacementMode", GetType(PlacementMode), GetType(ToolBarMenuItem), New FrameworkPropertyMetadata(PlacementMode.Bottom))
Public Property SubMenuPlacementMode As PlacementMode
Get
Return GetValue(SubMenuPlacementModeProperty)
End Get
Set(ByVal value As PlacementMode)
SetValue(SubMenuPlacementModeProperty, value)
End Set
End Property
Private Sub ToolBarMenuItem_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Click
Me.IsSubmenuOpen = False
End Sub
Private Sub ToolBarMenuItem_MouseLeave(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.MouseLeave
Me.IsSubmenuOpen = False
End Sub
Private Sub ToolBarMenuItem_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown
If Me.IsSubmenuOpen = False Then
Me.IsSubmenuOpen = True
Me.StaysOpenOnClick = False
End If
End Sub
End Class
We also need to add a following XAML Code which contains Style for our custom control.
<Style TargetType="{x:Type local:ToolBarMenuItem}">
<Setter Property="Padding" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ToolBarMenuItem}">
<ControlTemplate.Resources>
<Storyboard x:Key="HighlightOn">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BackgroundGradientOver" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="HighlightOff">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BackgroundGradientOver" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" x:Name="BackgroundGradientOver" Stroke="#FF3399FF" StrokeThickness="1" Opacity="0" Fill="#FFC2E0FF"/>
<ContentPresenter Grid.Column="0" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" x:Name="HeaderHost" RecognizesAccessKey="True" ContentSource="Header" />
<Popup IsOpen="{Binding Path=IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" Placement="{TemplateBinding SubMenuPlacementMode}" x:Name="SubMenuPopup" Focusable="false" AllowsTransparency="true" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" VerticalOffset="-3">
<Grid x:Name="SubMenu">
<Border x:Name="SubMenuBorder" BorderBrush="{DynamicResource ControlBorderBrush}" BorderThickness="1" >
<Border.Background>
<SolidColorBrush Color="White"/>
</Border.Background>
</Border>
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
</Grid>
</Popup>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSuspendingPopupAnimation" Value="true">
<Setter Property="PopupAnimation" Value="None" TargetName="SubMenuPopup" />
</Trigger>
<Trigger Property="AllowsTransparency" SourceName="SubMenuPopup" Value="true">
<Setter Property="Margin" Value="0,0,3,3" TargetName="SubMenu" />
<Setter Property="SnapsToDevicePixels" Value="true" TargetName="SubMenu" />
<Setter Property="BitmapEffect" Value="{DynamicResource PopupDropShadow}" TargetName="SubMenuBorder" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource HighlightOff}" x:Name="HighlightOff_BeginStoryboard" />
</Trigger.ExitActions>
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource HighlightOn}" />
</Trigger.EnterActions>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see in the above code, we are using SubMenuPlacementMode property to decide direction of Popup control.
Following is demo code of using ToolBarMenuItem control to allow nested menuitem.
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ToolBarMenuItemDemo"
Title="NestedToolBarItem Demo" Height="350" Width="525" Background="Aquamarine">
<Grid VerticalAlignment="Top">
<ToolBar Name="ToolBar1" Height="25" >
<!--Regular Items-->
<Button Content="Item A"/>
<Button Content="Item B"/>
<!--SubItems Menu For Special Items-->
<my:ToolBarMenuItem Header="Special Items" VerticalContentAlignment="Center" SubMenuPlacementMode="Bottom">
<MenuItem Header="Special Item 1" />
<MenuItem Header="Special Item 2" />
</my:ToolBarMenuItem>
</ToolBar>
</Grid>
</Window>
You can also refer to the following link if you want to customize MenuItem style.
http://msdn.microsoft.com/en-us/library/ms747082(v=vs.85).aspx