Reputation: 20374
I once created a style to fix the bugged appearance of menu items in WPF. It's mainly about the menu text that is misaligned. It's too far in the top left corner and doesn't use proper spacing.
I discovered that it does work in window menus, but not in the context menu of TextBox
which I tested now. So the question is, why isn't this style regarded for context menus from a text box?
Update: I found out that the TextBox
uses its own menu item class, a private nested class TextEditorContextMenu.EditorContextMenu
and its own menu items, the nested class EditorMenuItem
. Both are derived from ContextMenu
and MenuItem
, resp. So if they are a subclass of the classes I have styled, then why isn't my style applied to them as well?
The only thing I could do was copying the definition of
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}">
from PresentationFramework.Aero's resources into my style file. But that very clearly makes my menus look like Windows 7 which may not be expected on Windows 8 or 10 (or XP). But redefining that style key at lest could influence the EditorMenuItem
's appearance. Why?
And if EditorMenuItem
doesn't have its own style (I couldn't find it), why doesn't it use whatever style I provide for the base class as well? How does it know not to use my style but only the default one which is replaced and inaccessible for every other context menu?
Here's the XAML code, which is stored in MenuStyles.xaml and included in the ResourceDictionary
from App.xaml.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Expression Blend 4 created this (and a lot more) from some system theme on Windows 7 -->
<Style TargetType="{x:Type MenuItem}">
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ScrollViewer.PanningMode" Value="Both"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=SubmenuItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
<Style.Triggers>
<Trigger Property="Role" Value="TopLevelHeader">
<Setter Property="Padding" Value="7,2,8,3"/>
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelHeaderTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
</Trigger>
<Trigger Property="Role" Value="TopLevelItem">
<Setter Property="Padding" Value="7,2,8,3"/>
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
</Trigger>
<Trigger Property="Role" Value="SubmenuHeader">
<Setter Property="Padding" Value="5,4,2,3"/>
<!-- Changed from 2,3,2,3 -->
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=SubmenuHeaderTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
</Trigger>
<Trigger Property="Role" Value="SubmenuItem">
<Setter Property="Padding" Value="5,4,2,3"/>
<!-- Changed from 2,3,2,3 -->
</Trigger>
</Style.Triggers>
</Style>
<!-- Expression Blend 4 created this from some system theme on Windows 7 -->
<!-- Edited like in: http://devlicio.us/blogs/christopher_bennage/archive/2008/06/19/styling-separators-in-wpf.aspx -->
<!-- Decreased in height to be more platform standard -->
<Style x:Key="{x:Static MenuItem.SeparatorStyleKey}" TargetType="{x:Type Separator}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Separator}">
<Grid Margin="0,3,0,2" SnapsToDevicePixels="true">
<!-- Changed from 0,6,0,4 -->
<Rectangle Fill="#E0E0E0" Height="1" Margin="30,0,1,1"/>
<Rectangle Fill="White" Height="1" Margin="30,1,1,0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Upvotes: 1
Views: 2411
Reputation: 2781
Your problem lies in not clearly understanding how styles in WPF work. There are two type of styles which handled differently.
The first type is theme style. Every FrameworkElement
and FrameworkContentElement
resolves its own Style
using the DefaultStyleKey
property when it's initialized. The location of resource dictionaries with theme resources is specified by ThemeInfoAttribute
.
The second type is non-theme style. This style could be set explicitly by specifying the Style
property of an element. Or it could also be resolved implicitly at initialization time. The Setter
s of a non-theme style take precedence over Setter
s of a theme style.
When you make an non-theme Style
by adding it into the ResourceDictionary
of an application or an element without a key, then it used implicitly and only instances of the target type without the explicitly set Style
property will be affected and not derived types. This behavior is defined in the FrameworkElement.GetRawValue
method (line 1887):
internal void GetRawValue(DependencyProperty dp, PropertyMetadata metadata, ref EffectiveValueEntry entry)
{
// ...
if (dp != StyleProperty)
{
if (StyleHelper.GetValueFromStyleOrTemplate(new FrameworkObject(this, null), dp, ref entry))
{
return;
}
}
else
{
object source;
object implicitValue = FrameworkElement.FindImplicitStyleResource(this, this.GetType(), out source);
if (implicitValue != DependencyProperty.UnsetValue)
{
// This style has been fetched from resources
HasImplicitStyleFromResources = true;
entry.BaseValueSourceInternal = BaseValueSourceInternal.ImplicitReference;
entry.Value = implicitValue;
return;
}
}
// ...
}
So your Style
isn't applied because it's designed only for the MenuItem
class because it's not a theme Style
. And you have two ways to change the Style
of items in the TextBox
's ContextMenu
and they both have cons.
The first way is to add a Style
for all TextBox
es and set in it your ContextMenu
. But you will loose reconversion and speller MenuItems
if Text Services Framework and spell check are used, respectively.
<Style TargetType="{x:Type TextBox}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy" />
<MenuItem Command="ApplicationCommands.Cut" />
<MenuItem Command="ApplicationCommands.Paste" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
And the second way is to use reflection at startup to create a Style
for the TextEditorContextMenu.EditorMenuItem
class. But this method should be used only if you are using spell checking and Text Services Framework.
// Inside the Application.OnStartup method
Style menuItemStyle = TryFindResource(typeof(MenuItem)) as Style;
if (menuItemStyle != null)
{
Assembly menuItemAssembly = typeof(MenuItem).Assembly;
Type editorMenuType = menuItemAssembly.GetType("System.Windows.Documents.TextEditorContextMenu+EditorMenuItem", false);
if (editorMenuType != null)
{
Resources.Add(editorMenuType, menuItemStyle);
}
Type reconversionMenuType = menuItemAssembly.GetType("System.Windows.Documents.TextEditorContextMenu+ReconversionMenuItem", false);
if (reconversionMenuType != null)
{
Resources.Add(reconversionMenuType, menuItemStyle);
}
}
Upvotes: 3