WPF XAML MenuItem Styles

by Jim Nuzzi 29. June 2008 21:22

I recently began working with WPF for the first time.  I was doing this outside of my job on my own time (which is sometimes quite limited).  I had never worked with WPF before this venture.  I decided to download a trial copy of Microsoft Expression Blend 2 to check out the designer side of WPF.  The first thing that I noticed was the menus.  The menus had a black background with white text and the submenus had white borders and white menu separators (pictured below).  I decided that I liked this look and set out to implement this using Visual Studio 2008.  This entry describes how to create this menu.

Menu Example

I implemented the menu using two custom Styles.  The first for the Separator objects and the second for the MenuItem objects.  The XAML for the Separator style is as follows:

001<Style x:Key="MLB_Separator" TargetType="{x:Type Separator}">
002    <Setter Property="Margin" Value="0,3,0,3" />
003    <Setter Property="Template">
004        <Setter.Value>
005            <ControlTemplate TargetType="{x:Type Separator}">
006                <Grid>
007                    <Rectangle Height="1" Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Menu}}}" />
008                </Grid>
009            </ControlTemplate>
010        </Setter.Value>
011    </Setter>
012</Style>

As you can see in the code, The Separator style simply sets the default margins (line 2) and draws a 1px high rectangle using the parent Menu's foreground color (line 7).

For the MenuItem style, I started with the SimpleMenuItem style that was included when I was using Expression Blend. I found that this style was not complete and simply did not work correctly in some cases. After cleaning up that code and adding some additional features, I ended up with the XAML below:

001<Style x:Key="MLB_MenuItem" TargetType="{x:Type MenuItem}">
002    <Setter Property="Foreground" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Menu}}}"/>
003    <Setter Property="Template">
004        <Setter.Value>
005            <ControlTemplate TargetType="{x:Type MenuItem}">
006                <Border x:Name="Border"
007                        Background="{TemplateBinding Background}"
008                        BorderBrush="{TemplateBinding BorderBrush}"
009                        BorderThickness="{TemplateBinding BorderThickness}">
010                    <Grid>
011                        <Grid.ColumnDefinitions>
012                            <ColumnDefinition x:Name="Col0" MinWidth="17" Width="Auto" SharedSizeGroup="MenuItemIconColumnGroup"/>
013                            <ColumnDefinition Width="Auto" SharedSizeGroup="MenuTextColumnGroup"/>
014                            <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGTColumnGroup"/>
015                            <ColumnDefinition x:Name="Col3" Width="14"/>
016                        </Grid.ColumnDefinitions>
017
018                        <!-- ContentPresenter to show an Icon if needed -->
019                        <ContentPresenter Grid.Column="0" Margin="4,0,6,0" x:Name="Icon" VerticalAlignment="Center" ContentSource="Icon"/>
020
021                        <!-- Glyph is a checkmark if needed for a checkable menu -->
022                        <Grid Grid.Column="0" Visibility="Hidden" Margin="4,0,6,0" x:Name="GlyphPanel" VerticalAlignment="Center">
023                            <Path x:Name="GlyphPanelpath" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" Data="M0,2 L0,4.8 L2.5,7.4 L7.1,2.8 L7.1,0 L2.5,4.6 z" FlowDirection="LeftToRight"/>
024                        </Grid>
025
026                        <!-- Content for the menu text etc -->
027                        <ContentPresenter Grid.Column="1"
028                                          Margin="{TemplateBinding Padding}"
029                                          x:Name="HeaderHost"
030                                          RecognizesAccessKey="True"
031                                          ContentSource="Header"/>
032
033                        <!-- Content for the menu IGT -->
034                        <ContentPresenter Grid.Column="2"
035                                          Margin="8,1,8,1"
036                                          x:Name="IGTHost"
037                                          ContentSource="InputGestureText"
038                                          VerticalAlignment="Center"/>
039
040                        <!-- Arrow drawn path which points to the next level of the menu -->
041                        <Grid Grid.Column="3" Margin="4,0,6,0" x:Name="ArrowPanel" VerticalAlignment="Center">
042                            <Path x:Name="ArrowPanelPath" HorizontalAlignment="Right" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" Data="M0,0 L0,8 L4,4 z"/>
043                        </Grid>
044
045                        <!-- The Popup is the body of the menu which expands down or across depending on the level of the item -->
046                        <Popup IsOpen="{Binding Path=IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" x:Name="SubMenuPopup" Focusable="false" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
047                            <Border x:Name="SubMenuBorder" BorderBrush="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Menu}}}" BorderThickness="1" Padding="2,2,2,2">
048                                <Grid x:Name="SubMenu" Grid.IsSharedSizeScope="True">
049                                    <!-- StackPanel holds children of the menu. This is set by IsItemsHost=True -->
050                                    <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
051                                </Grid>
052                            </Border>
053                        </Popup>
054                    </Grid>
055                </Border>
056
057                <!-- These triggers re-configure the four arrangements of MenuItem to show different levels of menu via Role -->
058                <ControlTemplate.Triggers>
059                    <!-- Role = TopLevelHeader : this is the root menu item in a menu; the Popup expands down -->
060                    <Trigger Property="Role" Value="TopLevelHeader">
061                        <Setter Property="Padding" Value="6,1,6,1"/>
062                        <Setter Property="Placement" Value="Bottom" TargetName="SubMenuPopup"/>
063                        <Setter Property="MinWidth" Value="0" TargetName="Col0"/>
064                        <Setter Property="Width" Value="Auto" TargetName="Col3"/>
065                        <Setter Property="Visibility" Value="Collapsed" TargetName="Icon"/>
066                        <Setter Property="Visibility" Value="Collapsed" TargetName="GlyphPanel"/>
067                        <Setter Property="Visibility" Value="Collapsed" TargetName="IGTHost"/>
068                        <Setter Property="Visibility" Value="Collapsed" TargetName="ArrowPanel"/>
069                    </Trigger>
070
071                    <!-- Role = TopLevelItem :  this is a child menu item from the top level without any child items-->
072                    <Trigger Property="Role" Value="TopLevelItem">
073                        <Setter Property="Padding" Value="6,1,6,1"/>
074                        <Setter Property="Visibility" Value="Collapsed" TargetName="ArrowPanel"/>
075                    </Trigger>
076
077                    <!-- Role = SubMenuHeader : this is a child menu item which does not have children -->
078                    <Trigger Property="Role" Value="SubmenuHeader">
079                        <Setter Property="DockPanel.Dock" Value="Top"/>
080                        <Setter Property="Padding" Value="0,2,0,2"/>
081                    </Trigger>
082
083                    <!-- Role = SubMenuItem : this is a child menu item which has children-->
084                    <Trigger Property="Role" Value="SubmenuItem">
085                        <Setter Property="DockPanel.Dock" Value="Top"/>
086                        <Setter Property="Padding" Value="0,2,0,2"/>
087                        <Setter Property="Visibility" Value="Collapsed" TargetName="ArrowPanel"/>
088                    </Trigger>
089                    <Trigger Property="IsSuspendingPopupAnimation" Value="true">
090                        <Setter Property="PopupAnimation" Value="None" TargetName="SubMenuPopup"/>
091                    </Trigger>
092
093                    <!-- If no Icon is present the we collapse the Icon Content -->
094                    <Trigger Property="Icon" Value="{x:Null}">
095                        <Setter Property="Visibility" Value="Collapsed" TargetName="Icon"/>
096                    </Trigger>
097
098                    <!-- The GlyphPanel contains the CheckMark -->
099                    <Trigger Property="IsChecked" Value="true">
100                        <Setter Property="Visibility" Value="Visible" TargetName="GlyphPanel"/>
101                        <Setter Property="Visibility" Value="Collapsed" TargetName="Icon"/>
102                    </Trigger>
103
104                    <!-- Using the system colors for the Menu Highlight and IsEnabled-->
105                    <Trigger Property="IsHighlighted" Value="true">
106                        <Setter Property="Background" Value="LightGray" TargetName="Border"/>
107                        <Setter Property="Foreground" Value="Black"/>
108                    </Trigger>
109                    <Trigger Property="IsEnabled" Value="false">
110                        <Setter Property="Foreground" Value="LightGray"/>
111                    </Trigger>
112                </ControlTemplate.Triggers>
113            </ControlTemplate>
114        </Setter.Value>
115    </Setter>
116</Style>

The MenuItem style first sets the foreground color to the parent Menu's foreground color (line 2). Then comes the Template, which is where it gets interesting. The Template starts with a Border around a Grid. The Grid has 4 ColumnDefinition items. The first is for the icon or the checkbox (depending on the type of MenuItem). The second is for the text of the MenuItem. The third is for the input gesture text (e.g. Ctrl-S). The last is for the arrow, if the MenuItem contains a sub-menu.

The contents of the columns are then defined. First is a ContentPresenter that will display the value of the "Icon" property. Next is a Grid that will hold the checkbox, if needed. These are both displayed in the first column of the main Grid (but not at the same time). After this is another ContentPresenter that will display the value of the "Header" property. Next is another ContentPresenter that will display the value of the "InputGestureText" property. Then there is a Grid that will display the arrow if there is a sub-menu. Finally, there is a Popup that will display the sub-menu (if one is defined).

The remainder of the Style contains the Trigger items that will manipulate what is displayed based on the type of MenuItem (top-level menu item, sub-menu meu item, etc.) that is being rendered. I will leave these items for a future entry.

Tags: , , , , , , ,

.NET | WPF

Comments

7/11/2008 1:07:56 PM #

trackback

Trackback from DotNetKicks.com

WPF XAML MenuItem Styles

DotNetKicks.com | Reply

7/12/2008 4:33:38 AM #

XAML Tempaltes

Hi at www.xamltemplates.net you can download a template for free which also has the menu item styled.

XAML Tempaltes United States | Reply

7/14/2008 8:40:28 AM #

Jim Nuzzi

XAML Tempaltes,

Your site looks nice, as do your templates.

Anyway, thanks for reading and for the link to your site.

Jim Nuzzi United States | Reply

7/31/2008 8:24:45 PM #

Tulga Ariuntuya

thanks, templates sounds good.

Tulga Ariuntuya Mongolia | Reply

2/21/2009 3:34:43 PM #

Mike Hankey

Nice job...Tweaked it a bit and got exactly what I wanted!

Mike

Mike Hankey United States | Reply

7/27/2009 6:30:50 PM #

Kastor

YEAH , just what I was looking for!

Kastor Germany | Reply

8/15/2009 6:26:13 PM #

halloween customes blog

Hey that was cool, Can I take part of your post to my blog, i will post the original source of course.

halloween customes blog United States | Reply

8/20/2009 2:10:32 AM #

Boliglån

Good post, I will mention it on my blog.. Cheers

Boliglån | Reply

10/30/2009 6:57:57 AM #

web design

I hope you will be the first I placed a comment to as I value your blog highly.  

web design United Kingdom | Reply

11/6/2009 6:43:30 AM #

Tina

Thanks a lot for the post, it really clears up what Blend does.
Just one thing --
I've been trying all day long but I can't seem to figure out how to implement the submenu backgrounds that you see in Microsoft Windows XP Professional SP2 (ex: On Outlook, Excel, Visual Studio etc).
I want to recreate the way the submenus have a dark gray gradient behind the icon (seems to exist only in that row) while the title part of the menu has the light gray bg color. If you have any ideas on how to create this effect please drop me a line!

Tina Singapore | Reply

11/19/2009 12:11:24 PM #

Atlanticom

This style saved me hours. Thank you so much for sharing your effort.

Atlanticom United States | Reply

3/8/2010 10:25:47 PM #

Connie

How do I subscribe to your blog?

Connie Syria | Reply

3/9/2010 10:29:10 AM #

Jim Nuzzi

There is a Subscribe link in the menu bar at the top of the page.

Jim Nuzzi United States | Reply

3/17/2010 9:14:54 AM #

Felix

Cool, you know its really hard to find code source for such styles

Felix People's Republic of China | Reply

5/18/2010 10:11:05 AM #

StableHost Hosting Review

You raise many questions in my mind; you wrote an excellent post, but it is also thought provoking, and I will have to think about it a bit more; I will be back soon.

StableHost Hosting Review United States | Reply

5/26/2010 3:33:24 AM #

Online Headshop

Stunning! At last someone that has learned what exactly they are talking regarding. Your information is great.

Online Headshop United States | Reply

6/13/2010 11:05:33 AM #

pbs kids games

I talked about it with my friends while ago

pbs kids games United States | Reply

6/22/2010 3:21:38 AM #

Web Hosting Resellerin

Accelerator v2 is implemented in native code, but ships with a managed wrapper that allows us to use it elegantly from C# and other managed languages as well. The library exposes various types that can be used for creating data-parallel computations that process 1D or 2D arrays of data. In this article, we'll work with FloatParallelArray, which represents a computation that returns a 1D array of floats (float[] in C#).

Web Hosting Resellerin United States | Reply

6/25/2010 1:58:18 PM #

Robert Dampson

Always enjoy reading spot on articles by an author who is obviously knowledgeable on their chosen subject….Now is the perfect time (summer now)to spend some money to grab those fancy dress and accessories! LOL….. Keep up the great work, see you next time

Robert Dampson Ecuador | Reply

7/9/2010 2:46:48 PM #

Naz

Hi Jim. I'm too slow but I really want to know, what does WPF stand for?

Naz Malaysia | Reply

7/13/2010 11:13:11 AM #

Jim Nuzzi

Windows Presentation Foundation

Jim Nuzzi United States | Reply

8/4/2010 2:03:57 AM #

Masquerade Masks

Love visual studio! Excellent post man Smile

Masquerade Masks United States | Reply

8/4/2010 7:21:14 PM #

http://poststream.org

The menu item styles are always interchangeable, I'm currently working on a big project for my boss which involves creating multiple menus

http://poststream.org United States | Reply

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading



About the author

Jim Nuzzi Jim Nuzzi
Software Engineer/Architect

All-Around Good Guy

View James Nuzzi's LinkedIn profileJim's LinkedIn profile
Send mailContact Jim

Recent posts

Recent comments

Comment RSS

Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's, or anyone else's, view in any way.

© Copyright 2008

Valid XHTML 1.0 Transitional

Valid CSS!