为什么我的隐式ContextMenu样式不能覆盖TextBox上下文菜单样式?

6

我有一个 ContextMenu 的隐式样式,是从这个网站上获取的:

<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中的项,而这些项基本上使用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将采用隐式样式。

你的资源字典在哪里?其中某个地方会有这样一行代码:<Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}" />。你只需要把basedon的值更改为你新样式的引用,就可以将其作为全局默认值了。 - Chris W.
@ChrisW. - 对不起,我不明白。正如您在我的代码中所看到的,我已经在应用程序资源中定义了一个隐式样式 ContextMenu<Style TargetType="ContextMenu">。我尝试将其添加到不同的级别,但它在任何级别都不起作用。 - Dzyann
1
你找到解决问题的方法了吗?我也有同样的问题 :( - mgarant
@mgarant - 好吧,似乎没有人回答为什么会发生这种情况,从使用 snoop 来看,似乎是设计上的一些缺陷,但我找到了一个解决方法,并作为更新发布了。 - Dzyann
1个回答

0

我觉得这看起来像是 TextBox 实现上的一个 bug,奇特之处在于 ContextMenu 并不属于 VisualTree。

我会尝试解释一下。

  • 默认的ContextMenu将由TextBox实现创建并显示,即在方法OnContextMenuOpening(ContextMenuEventArgs e)中。如果要查看它,可以在自定义的TextBox中覆盖此方法并省略base.调用。
  • 与显式设置的ContextMenu相比,默认的ContextMenu处理方式不同(不是通过ContextMenuService处理)。
  • 仅当未显式设置TextBox.ContextMenu属性(XAML或代码后台)时,TextBox才会创建默认的ContextMenu
  • ContextMenu不在VisualTree中,与VisualTree之间的唯一连接是PlacementTarget属性(例如,在代码后台创建并打开ContextMenu而没有设置PlacementTarget时,隐式样式将不会被应用)。
  • TextBox实现中,ContextMenu.PlacementTarget被设置,但该值来自某个内部缓存/字典。我无法对其进行调试并确定,但我认为缓存的值是错误的。您可以进行小型测试以查看缓存的值是否正确。覆盖自定义TextBoxOnContextMenuOpening(ContextMenuEventArgs e),在调用基本函数之前修改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>

我在谈论错误,因为默认的ContextMenu实现并没有正确地应用隐式样式。

坏消息是,在当前TextBox的实现中,您无法访问内部创建的默认ContextMenu,因此隐式样式也无法应用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接