为啥我的隐式 ContextMenu 样式不会覆盖 TextBox 上下文菜单样式?

Posted

技术标签:

【中文标题】为啥我的隐式 ContextMenu 样式不会覆盖 TextBox 上下文菜单样式?【英文标题】:Why my implicit ContextMenu Style doesn't override the TextBox context menu style?为什么我的隐式 ContextMenu 样式不会覆盖 TextBox 上下文菜单样式? 【发布时间】:2016-04-08 13:47:45 【问题描述】:

我对 ContextMenu 有这种隐含的风格,我从 this site 那里得到:

<Application.Resources>
        <SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />
        <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />

        <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>
    </Application.Resources>

然后我尝试在这里使用它,以便它同时应用于TextBox 的默认ContextMenu 和我为Button 添加的ContextMenu

</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <TextBox Height="30" Width="200">Test</TextBox>
    <Button Grid.Row="1" Width="200" Height="30" Content="Test2">
        <Button.ContextMenu>
            <ContextMenu>
                <MenuItem>Test</MenuItem>
                <MenuItem>Test2</MenuItem>
            </ContextMenu>
        </Button.ContextMenu>
    </Button>
</Grid>

样式会应用于Button,但不会应用于TextBox

我觉得这应该是相当直接和简单的,为什么我的隐式风格没有应用到TextBox 的默认ContextMenu,我做错了什么?

==更新==

目前我不确定答案,但我认为这里的问题是TextBoxContextMenu 的设计存在一些缺陷,我希望有更多知识的人能够确认。 使用 Snoop 我可以看到 ContextMenu 不是您所期望的对象,而是一个 EditorContextMenu 对象,它是内部的,因此您无法设置它的样式。他们为什么使用它?我不知道。

作为一种解决方法,我创建了一个默认上下文菜单并使用它。如果您向TextBox 添加上下文菜单,它会正确地采用隐式样式。 既然你知道默认ContextMenu有的item,而item又基本使用ApplicationCommands,就很简单了:

<ContextMenu x:Key="DefaultContextMenu">
        <MenuItem Command="ApplicationCommands.Copy" />
        <MenuItem Command="ApplicationCommands.Cut" />
        <MenuItem Command="ApplicationCommands.Paste" />        
    </ContextMenu>

然后在你的 TextBoxStyle 中做:

<Style x:Key="MyTextBoxStyle" TargetType="TextBox">
    <Setter Property="ContextMenu" Value="StaticResource DefaultContextMenu" />

这样,您的TextBox 的默认ContextMenu 将采用隐式样式。

【问题讨论】:

您的资源字典在哪里?在某处有像&lt;Style TargetType="ContextMenu" BasedOn="StaticResource x:Type ContextMenu" /&gt; 这样的行,您只需更改 basedon 值即可将您的新样式引用为全局默认值。 @ChrisW。 - 对不起,我不明白。正如您在我的代码中看到的,我在应用程序资源中定义了 ContextMenu 的隐式样式:&lt;Style TargetType="ContextMenu"&gt;。我尝试将它添加到不同的级别,但在任何级别中都不起作用。 您找到解决问题的方法了吗?我有同样的问题:( @mgarant - 没有人回答为什么会这样,从使用 snoop 来看,它似乎在设计中存在一些缺陷,但我找到了解决方法,我将其作为更新发布。 【参考方案1】:

对我来说,这看起来像是TextBox 实现中的一个错误,其特点是ContextMenu 不属于VisualTree。

我会试着解释一下。

默认ContextMenu 将由TextBox 创建和显示 实现,即在方法中 OnContextMenuOpening(ContextMenuEventArgs e)。要查看它,您可以 在自定义 TextBox 中覆盖此方法并省略 base. 调用。 与显式设置的ContextMenu相比,默认ContextMenu是 处理方式不同(不是通过 ContextMenuService 处理的)。 默认ContextMenu 将由TextBox 创建,仅当 TextBox.ContextMenu 属性未明确设置(XAML 或代码隐藏)。 ContextMenu 不在 VisualTree 中,是与 VisualTree 是 PlacementTarget 的属性(例如,如果您创建 并在后面的代码中打开 ContextMenu 并且不要设置 PlacementTarget 不会应用隐式样式)。 TextBox 实现中设置了ContextMenu.PlacementTarget,但该值来自一些内部缓存/字典。我不能 调试它并肯定地说,但我想缓存的值是错误的 这里。 你可以做个小测试看看缓存的值是否正确。覆盖自定义的OnContextMenuOpening(ContextMenuEventArgs e) TextBox 在调用基本函数之前,请修改 TextBox(例如,每次设置另一个 Margin)。默认的ContextMenu 甚至都不会打开!如果您代替base 调用,请实例化并显示 ContextMenu 自己修改,然后Margin 修改 不会打扰并打开ContextMenu 将具有您在资源中设置的隐式样式。

要测试的片段

public class MyStyledTextBox: TextBox

    protected override void OnContextMenuOpening(ContextMenuEventArgs e)
    
        //this.Margin = new Thickness(0, 20, 0, 0);

        var uiScope = e.Source as TextBox;

        var ctxm = new ContextMenu();

        MenuItem menuItem;
        menuItem = new MenuItem();
        menuItem.Header = "CutCustom";
        menuItem.CommandTarget = this;
        menuItem.Command = ApplicationCommands.Cut;
        ctxm.Items.Add(menuItem);

        ctxm.PlacementTarget = uiScope;

        ctxm.IsOpen = true;

        //base.OnContextMenuOpening(e);
    


<StackPanel>
    <StackPanel.Resources>
        <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_custom" Background="Chocolate" BorderBrush="Coral" BorderThickness="1" >
                            <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasDropShadow" Value="true">
                                <Setter TargetName="Border_custom" Property="Padding" Value="0,3,0,3"/>
                                <Setter TargetName="Border_custom" Property="CornerRadius" Value="4"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </StackPanel.Resources>

    <local:MyStyledTextBox Height="30" Width="200" Text="Test">
        <!--<TextBox.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Test"/>
                <MenuItem Header="Test2"/>
            </ContextMenu>
        </TextBox.ContextMenu>-->
    </local:MyStyledTextBox>
</StackPanel>

我说的是错误,因为默认 ContextMenuScrollBar 实现确实正确应用了隐式样式

坏消息是,在 TextBox 的当前实现中,您无法达到内部创建的默认 ContextMenu 并且未应用隐式样式。

【讨论】:

以上是关于为啥我的隐式 ContextMenu 样式不会覆盖 TextBox 上下文菜单样式?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Java 和 C# 没有到布尔值的隐式转换?

为啥 Hibernate 会为 @ManyToOne 关联的隐式连接生成 CROSS JOIN?

为啥从 <T> 到 <U> 的隐式转换运算符接受 <T?>?

为啥我可以阻止基元而不是用户定义类型的隐式转换?

为啥 gcc 给出警告:函数 qsort_r 的隐式声明?

为啥允许从 int 到 byte 但不允许从 long 到 int 的隐式缩小转换?