Skip to content

WPF 已知问题 Separator 无法应用 ContextMenu 定义的默认样式

Updated: at 08:22,Created: at 12:01

本文记录一个 WPF 已知问题,在 ContextMenu 的 Resources 里定义 Separator 的默认样式,在 ContextMenu 里面的 Separator 将应用不上,或者说不会找到此默认的样式。需要明确给 Style 一个 Key 且在 Separator 写明此 Key 才能应用上

如下面的例子,在 ContextMenu 的 Resources 资源里面定义了 Separator 的默认样式

<ContextMenu>
<ContextMenu.Resources>
<Style TargetType="Separator">
<Setter Property="Margin" Value="10,10,10,10"></Setter>
</Style>
</ContextMenu.Resources>
</ContextMenu>

接着在 ContextMenu 里面存放一个 Separator 元素,可以看到此 Separator 元素没有使用或者说找到定义的样式,视觉上就是 Margin 没有生效

<ContextMenu>
<ContextMenu.Resources>
<Style TargetType="Separator">
<Setter Property="Margin" Value="10,10,10,10"></Setter>
</Style>
</ContextMenu.Resources>
<Separator></Separator>
</ContextMenu>

而如果给 Separator 的 Style 加上 Key 且在 Separator 写明了此 Key 那么将可以成功应用上,如下面代码

<ContextMenu>
<ContextMenu.Resources>
<Style x:Key="SeparatorStyle" TargetType="Separator">
<Setter Property="Margin" Value="10,10,10,10"></Setter>
</Style>
</ContextMenu.Resources>
<Separator Style="{StaticResource SeparatorStyle}"></Separator>
</ContextMenu>

此问题是在 WPF 代码里面写了特殊判断逻辑,预计是有我没有理解的坑才如此做。感谢 少珺 工具人帮我找到了在 WPF 框架里面的问题

为了方便说明问题,我将给出可以运行的测试代码,此测试代码可以在本文末尾找到项目的下载

新建一个 WPF 项目,编辑主窗口,添加以下代码

<Grid Background="White" MouseDown="Grid_OnMouseDown">
<Grid.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<Style TargetType="Separator">
<Setter Property="Margin" Value="10,10,10,10"></Setter>
</Style>
<Style x:Key="SeparatorStyle" TargetType="Separator">
<Setter Property="Margin" Value="10,10,10,10"></Setter>
</Style>
</ContextMenu.Resources>
<Menu>1</Menu>
<Menu>2</Menu>
<Menu>3</Menu>
<Separator></Separator>
<Menu>1</Menu>
<Menu>2</Menu>
<Menu>3</Menu>
<Separator Style="{StaticResource SeparatorStyle}"></Separator>
<Menu>1</Menu>
<Menu>2</Menu>
<Menu>3</Menu>
</ContextMenu>
</Grid.ContextMenu>
</Grid>

对应的后台代码如下

private void Grid_OnMouseDown(object sender, MouseButtonEventArgs e)
{
var grid = (Grid)sender;
grid.ContextMenu.IsOpen = true;
}

此时点击窗口内容,即可看到弹出了菜单

弹出的菜单的两条分割线的 Margin 是不相同的

根本原因是在 WPF 里面,对于在 Menu 里面的 Separator 采用的是如下逻辑,以下代码可以从 WPF 官方开源仓库 https://github.com/dotnet/wpf/blob/1aab9e3f42dbf550797bff97a32f2dbfb61a3198/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/MenuItem.cs#L1344-L1353 找到。我此问题报告给 WPF 官方,请看 https://github.com/dotnet/wpf/issues/7268

Separator separator = item as Separator;
if (separator != null)
{
bool hasModifiers;
BaseValueSourceInternal vs = separator.GetValueSource(StyleProperty, null, out hasModifiers);
if (vs <= BaseValueSourceInternal.ImplicitReference)
separator.SetResourceReference(StyleProperty, SeparatorStyleKey);
separator.DefaultStyleKey = SeparatorStyleKey;
}

从上面代码可以看到,判断如果样式是小于等于 ImplicitReference 优先级的,那就采用默认的 SeparatorStyleKey 作为样式属性。如果没有在代码里面明确给定资源的 Key 内容,那以上代码的 vs 就是 ImplicitReference 优先级,于是样式就被修改为默认的主题样式

这是在 WPF 里面特别给定的代码,也许是大佬们为了修复某个我理解不了的坑

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin b820847a1af20370de28a1e73e32df9561a98ecc

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin b820847a1af20370de28a1e73e32df9561a98ecc

获取代码之后,进入 HayhachujedaKikunayreefee 文件夹

更多 WPF 已知问题请参阅 博客导航


大佬 czdietrich 告诉我说,这是特意的。原因是 Separator 在很多个地方都会使用,如果在 Menu 里,也采用默认继承的关系,将会让 Separator 的样式打架。毕竟在 Menu 里面的,是期望比较特殊一些的。于是就限制了,如果是放在 Menu 里面,应该采用的是 MenuItem.SeparatorStyleKey 样式的才会作为默认的继承样式,如下面代码

<Window x:Class="TestContextMenuSeparatorStyle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<Style x:Key="{x:Static MenuItem.SeparatorStyleKey}" TargetType="Separator">
<Setter Property="Background" Value="Tomato" />
</Style>
</Window.Resources>
<Grid Background="White">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Item 1" />
<Separator />
<MenuItem Header="Item 2" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Window>

以上的代码也符合 WPF 的逻辑,详细请看 https://github.com/dotnet/wpf/issues/7268#issuecomment-1315303718


知识共享许可协议

原文链接: http://blog.lindexi.com/post/WPF-%E5%B7%B2%E7%9F%A5%E9%97%AE%E9%A2%98-Separator-%E6%97%A0%E6%B3%95%E5%BA%94%E7%94%A8-ContextMenu-%E5%AE%9A%E4%B9%89%E7%9A%84%E9%BB%98%E8%AE%A4%E6%A0%B7%E5%BC%8F

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。 欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系