ygoe
ygoe

Reputation: 20374

WPF MenuItem style is not applied in TextBox context menu

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

Answers (1)

Yoh Deadfall
Yoh Deadfall

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 Setters of a non-theme style take precedence over Setters 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 TextBoxes 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

Related Questions