The other day we had a requirement to style a ContextMenu for our large WPF app. Now this sounds easy enough to do, and there are even some pointer on a Microsoft site which show you the alleged Styles applied to the standard controls, for the ContextMenu though, the example on the Microsoft site which is as follows:
<Style TargetType="ContextMenu">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Grid.IsSharedSizeScope" Value="true"/>
<Setter Property="HasDropShadow" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContextMenu">
<Border
Name="Border"
Background="{StaticResource WindowBackgroundBrush}"
BorderBrush="{StaticResource SolidBorderBrush}"
BorderThickness="1" >
<StackPanel IsItemsHost="True"
KeyboardNavigation.DirectionalNavigation="Cycle"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" Value="true">
<Setter TargetName="Border" Property="Padding" Value="0,3,0,3"/>
<Setter TargetName="Border" Property="CornerRadius" Value="4"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Turned out to be way wrong, you can clearly see that the screen shot below which uses the standard ContextMenu with no additional styling has a ScrollViewer to present items that do not fit in a certain amount of space. There clearly is no ScrollViewer in the Microsoft example shown above.
So I thought ok, so I need to Style a ContextMenu myself, fine. How I would normally tackle this in Expression Blend is to place some control on the work surface, right click, and just choose to edit the current Template. This is shown below, where I will edit a Button Template.
Thing is this approach does not work for ContextMenu at all. mmmm So what can you do. Well luckily Expression Blend is awesome enough to cope with this, so what you do is as follows assuming you have a ContextMenu (or even have a dummy one created just so you can test the Styling)
I have this dummy one created:
<Grid x:Name="LayoutRoot" Background="CornflowerBlue">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="MenuItem"/>
<MenuItem Header="MenuItem"/>
<MenuItem Header="MenuItem"/>
<MenuItem Header="MenuItem"/>
....
....
....
....
<MenuItem Header="MenuItem"/>
<MenuItem Header="MenuItem"/>
<MenuItem Header="MenuItem"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
So lets assume you have a similar setup. So how can we get into what the ContextMenu style looks like. Well in Blend find you control that has the ContextMenu (real or dummy one) applied, in the VisualTree in Blend. For me that is the Grid called “LayoutRootâ€. So here is me doing that
Next I look at the selected Grid (“LayoutRootâ€) properties, and I find its ContextMenu property, which is under the Miscellaneous group.
Now you can expand the ContextMenu as I have above, and scrolldown until you find the ContextMenu Style property.
Then what you can do is click on the small rectangle on the right, and select the “Convert to New Resource†option as shown below
Ok so now what we get if we go to our XAML is a new ContextMenu style that looks something like this, which Expression Blend just generated for us
<!-- This is what Blend produced when you let it create
a local resource for you for the ContextMenu -->
<Style x:Key="ContextMenuStyle1"
TargetType="{x:Type ContextMenu}">
<Setter Property="Background"
Value="{DynamicResource MenuBackgroundBrush}"/>
<Setter Property="BorderThickness"
Value="1"/>
<Setter Property="BorderBrush"
Value="{DynamicResource WindowBorderBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContextMenu}">
<Border Uid="Border_93">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Tag"
Value="{DynamicResource
{x:Static SystemParameters.DropShadowKey}}"/>
<Style.Triggers>
<DataTrigger
Binding="{Binding Tag,
RelativeSource={RelativeSource Self}}"
Value="True">
<Setter Property="Background"
Value="Transparent"/>
<Setter Property="Padding"
Value="0,0,5,5"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect
BlurRadius="4"
Opacity="0.8"
ShadowDepth="1"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Uid="Border_50">
<ScrollViewer CanContentScroll="True"
Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer,
TypeInTargetAssembly={x:Type FrameworkElement}}}"
Uid="ScrollViewer_9">
<ItemsPresenter
KeyboardNavigation.DirectionalNavigation="Cycle"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Uid="ItemsPresenter_5"/>
</ScrollViewer>
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Ok so that is what the ContextMenu looks like by default. But what I wanted to do was swap out the standard look and feel of the ContextMenu (not the MenuItem controls they are styled separately), and the look of the Up/Down arrows it obviously has in its Template. So how about that then.
Well looking into it, I could see a ScrollViewer that is declared like this, which seemed to be the key:
<ScrollViewer CanContentScroll="True"
Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer,
TypeInTargetAssembly={x:Type FrameworkElement}}}"
Uid="ScrollViewer_9">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
So all that I really need to do then was create a new Style for the ScrollViewer using that ComponentResourceKey, which I did and here is all the relevant XAML for that.
<!-- These are the extras that you have to create to get your own ScrollViewer in for a custom ContextMenu
ScrollViewer -->
<MenuScrollingVisibilityConverter x:Key="MenuScrollingVisibilityConverter" />
<Style x:Key="MenuScrollButton"
BasedOn="{x:Null}"
TargetType="{x:Type RepeatButton}">
<Setter Property="ClickMode"
Value="Hover" />
<Setter Property="MinWidth"
Value="0" />
<Setter Property="MinHeight"
Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<DockPanel SnapsToDevicePixels="true" Width="Auto"
Height="30"
Background="CornflowerBlue">
<Rectangle x:Name="R1"
Width="2"
Fill="DarkBlue"
DockPanel.Dock="Right" />
<Rectangle x:Name="B1"
Height="2"
Fill="DarkBlue"
DockPanel.Dock="Bottom" />
<Rectangle x:Name="L1"
Width="2"
Fill="DarkBlue"
DockPanel.Dock="Left" />
<Rectangle x:Name="T1"
Height="2"
Fill="DarkBlue"
DockPanel.Dock="Top" />
<ContentPresenter HorizontalAlignment="Center"
Margin="2,2,2,2"
x:Name="ContentContainer"
VerticalAlignment="Center" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Geometry x:Key="UpArrow">M 0,4 L 3.5,0 L 7,4 Z</Geometry>
<Geometry x:Key="DownArrow">M 0,0 L 3.5,4 L 7,0 Z</Geometry>
<Style x:Key="{ComponentResourceKey ResourceId=MenuScrollViewer,
TypeInTargetAssembly={x:Type FrameworkElement}}"
BasedOn="{x:Null}"
TargetType="{x:Type ScrollViewer}">
<Setter Property="HorizontalScrollBarVisibility"
Value="Hidden" />
<Setter Property="VerticalScrollBarVisibility"
Value="Auto" />
<Setter Property="MaxHeight"
Value="200" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid SnapsToDevicePixels="true">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Grid.Row="1">
<ScrollContentPresenter Margin="{TemplateBinding Padding}" />
</Border>
<RepeatButton Focusable="false"
Style="{StaticResource MenuScrollButton}"
Grid.Column="0"
Grid.Row="0"
Command="{x:Static ScrollBar.LineUpCommand}"
CommandTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<RepeatButton.Visibility>
<MultiBinding FallbackValue="Visibility.Collapsed"
Converter="{StaticResource MenuScrollingVisibilityConverter}"
ConverterParameter="0">
<Binding Path="ComputedVerticalScrollBarVisibility"
RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="VerticalOffset"
RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="ExtentHeight"
RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="ViewportHeight"
RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</RepeatButton.Visibility>
<Path Fill="{DynamicResource {x:Static SystemColors.MenuTextBrushKey}}"
Data="{StaticResource UpArrow}" />
</RepeatButton>
<RepeatButton Focusable="false"
Style="{StaticResource MenuScrollButton}"
Grid.Column="0"
Grid.Row="2"
Command="{x:Static ScrollBar.LineDownCommand}"
CommandTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<RepeatButton.Visibility>
<MultiBinding FallbackValue="Visibility.Collapsed"
Converter="{StaticResource MenuScrollingVisibilityConverter}"
ConverterParameter="100">
<Binding Path="ComputedVerticalScrollBarVisibility"
RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="VerticalOffset"
RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="ExtentHeight"
RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="ViewportHeight"
RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</RepeatButton.Visibility>
<Path Fill="{DynamicResource {x:Static SystemColors.MenuTextBrushKey}}"
Data="{StaticResource DownArrow}" />
</RepeatButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
And having done that, I ended up with this. Ok I have over styled it for this blog post, but that is easily changed by the 2 RepeatButtons with the key “MenuScrollButton†that the ScrollViewer is using
Additional Data That May Be Of Interest
You may actually find these additional links of use, as they do show you how to style Menu/MenuItem controls quite well. I trust these 2 links
Demo App:
As usual here is a small demo app : Demo App