WPF MenuItem 样式未应用于 TextBox 上下文菜单

WPF MenuItem style is not applied in TextBox context menu

我曾经创建了一种样式来修复 WPF 中菜单项的错误外观。这主要是关于未对齐的菜单文本。它在左上角太远,没有使用适当的间距。

我发现它在 window 菜单中有效,但在我现在测试的 TextBox 的上下文菜单中 无效 。所以问题是,为什么文本框的上下文菜单不考虑这种样式?

更新: 我发现 TextBox 使用它自己的菜单项 class,一个私有嵌套 class TextEditorContextMenu.EditorContextMenu 和它自己的菜单项,嵌套 class EditorMenuItem。两者都分别派生自 ContextMenuMenuItem。因此,如果它们是我设计的 classes 的子class,那么为什么我的样式没有应用到它们呢?

我唯一能做的就是复制

的定义
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}">

从PresentationFramework.Aero的资源到我的样式文件。但这很明显使我的菜单看起来像 Windows 7,这在 Windows 8 或 10(或 XP)上可能不是预期的。但重新定义该样式键至少会影响 EditorMenuItem 的外观。为什么?

如果 EditorMenuItem 没有自己的样式(我找不到它),为什么它不使用我为基础 class 提供的任何样式?它怎么知道使用我的样式,而只知道默认的样式被替换并且对于所有其他上下文菜单都无法访问?

这是 XAML 代码,它存储在 MenuStyles.xaml 中并包含在 App.xaml 的 ResourceDictionary 中。

<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>

你的问题在于没有清楚地理解 WPF 中的样式是如何工作的。有两种样式,处理方式不同。

第一种是主题风格。每个 FrameworkElementFrameworkContentElement 在初始化时使用 DefaultStyleKey 属性 解析自己的 Style。具有主题资源的资源字典的位置由 ThemeInfoAttribute.

指定

第二种是non-theme风格。可以通过指定元素的 Style 属性 来显式设置此样式。或者它也可以在初始化时隐式解析。 non-theme 样式的 Setter 优先于主题样式的 Setter

当您通过将 non-theme Style 添加到应用程序或没有键的元素的 ResourceDictionary 中时,它会隐式且仅使用目标类型的实例没有显式设置 Style 属性 将受到影响而不是派生类型。此行为在 FrameworkElement.GetRawValue 方法 (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;
        }
    }

    // ...
}

所以你的 Style 没有应用,因为它是专为 MenuItem class 设计的,因为它不是主题 Style。您有两种方法可以更改 TextBoxContextMenu 中项目的 Style,它们都有缺点。

第一种方法是为所有 TextBox 添加一个 Style 并在其中设置您的 ContextMenu。但是,如果分别使用文本服务框架和拼写检查,您将失去重新转换和拼写 MenuItems

<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>

第二种方法是在启动时使用反射为TextEditorContextMenu.EditorMenuItem class创建一个Style。但只有在使用拼写检查和文本服务框架时才应使用此方法。

// 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);
    }
}