WPF 为ContextMenu使用Fluent风格的亚克力材质特效
书接上回,我们的Fluent WPF的版图已经完成了:
- Fluent Window: WPF 模拟UWP原生窗口样式——亚克力|云母材质、自定义标题栏样式、原生DWM动画 (附我封装好的类)
- Fluent Popup & ToolTip: WPF中为Popup和ToolTip使用WindowMaterial特效 win10/win11
- Fluent ScrollViewer: WPF 使用CompositionTarget.Rendering实现平滑流畅滚动的ScrollViewer,支持滚轮、触控板、触摸屏和笔
先来看看效果图(win11):
有以下xaml代码:
<TextBlock.ContextMenu> <ContextMenu> <MenuItem Header="Menu Item 1" Icon="Cd" InputGestureText="aa?" /> <MenuItem Header="Menu Item 2" > <MenuItem Header="Child Item 1" Icon="Ab" /> <MenuItem Header="Child Item 2" Icon="Ad" /> <MenuItem Header="Child Item 3" IsCheckable="True" IsChecked="True"/> </MenuItem> <MenuItem Header="Menu Item 3" Icon="cd" /> </ContextMenu> </TextBlock.ContextMenu>

(由于我的win10虚拟机坏了,暂时没有测试)
前面的工作已经解决了让任意窗口支持Acrylic材质的问题,本文重点介绍ContentMenu和MenuItem的样式适配和实现。
本文的Demo:
TwilightLemon/WindowEffectTest: 测试win10/11的模糊效果 (github.com)
一、为什么需要一个新的ContextMenu和MenuItem模板
如果你直接给ContextMenu应用WindowMaterial特效,可能会出现以下丑陋的效果:
或者:
原因在于古老的ContextMenu和MenuItem模板和样式并不能通过简单修改Background实现我们想要的布局和交互效果。
二、ContextMenu与MenuItem的结构
1. ContextMenu 的结构
ContextMenu直观上看是一个Popup,但它的控件模板并不包含Popup,而是直接指定内部元素(包含一个ScrollViewer和StackPanel)。因此不能从控件模板中替换Popup为自定义的FluentPopup,需要使用其他手段让其内部Popup也支持Acrylic材质(见下文)。
2. MenuItem 的四种形态
在示例代码仓库中展示了较为完整的Menu相关的结构:

MenuItem根据其在菜单树中的位置,通过Role属性分为四种形态。我们在Style.Triggers中分别为它们指定了不同的模板:
- TopLevelHeader: 顶级菜单项,且包含子菜单
Popup(例如菜单栏上的”File”)。 - TopLevelItem: 顶级菜单项,不含子菜单(例如菜单栏上的”Help”)。
- SubmenuHeader: 子菜单项,且包含下一级子菜单
Popup。 - SubmenuItem: 子菜单项,不含子菜单(叶子节点)。其模板主要处理图标、文字、快捷键的布局。
三、重写Menu相关控件模板和样式
1. ContextMenu 样式
ContextMenu的样式主要参考了.NET 9自带的Fluent样式,并作了一些调整以适配Acrylic材质。主要目的是覆盖原始模板的Icon部分白框和分割线:
因为我们的WindowMaterial已经为窗口自动附加上圆角、阴影和亚克力材质的DWM效果,所以我们只需要将ContextMenu的背景设置为透明,并移除不必要的边框和分割线即可。
此外,还添加了弹出动画(是在Popup内部做的,并非对window,效果可能不会很理想)。
2. MenuItem 样式
MenuItem的样式主要处理了图标、文字、快捷键的布局,并根据不同的角色(TopLevelHeader、TopLevelItem、SubmenuHeader、SubmenuItem)应用不同的模板。
- TopLevelHeader: 只包含Icon和Header,以及弹出的Popup,只需要替换为自定义的FluentPopup即可。
- TopLevelItem: 只包含Icon和Header,无Popup。
- SubmenuHeader: 包含Icon、Header和Chevron图标(这里就是一个展开的箭头图标,但是官方叫做雪佛龙..?),以及弹出的Popup,同样替换为FluentPopup。
- SubmenuItem: 叶子节点,包含Icon、Header和InputGestureText。 其模板主要处理图标、文字、快捷键的布局。
菜单项主要分为三个部分:Icon图标、Header文字和最右侧的提示文字或展开箭头图标。只需要保持三个部分的布局对其即可。如果IsCheckable为True,则Icon部分被自定义图标占据(√, 当IsChecked为True时)。
以下是完整的资源字典,包含了完整的注释。
1 <ResourceDictionary 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:WindowEffectTest" 5 xmlns:sys="clr-namespace:System;assembly=mscorlib"> 6 7 <!-- 菜单边框内边距 --> 8 <Thickness x:Key="MenuBorderPadding">0,3,0,3</Thickness> 9 10 <!-- 菜单项外边距 --> 11 <Thickness x:Key="MenuItemMargin">4,1</Thickness> 12 13 <!-- 菜单项内容内边距 --> 14 <Thickness x:Key="MenuItemContentPadding">10,6</Thickness> 15 16 <!-- 顶级菜单项外边距 --> 17 <Thickness x:Key="TopLevelItemMargin">4</Thickness> 18 19 <!-- 顶级菜单项内容边距 --> 20 <Thickness x:Key="TopLevelContentMargin">10</Thickness> 21 22 <!-- 菜单圆角半径 --> 23 <CornerRadius x:Key="MenuCornerRadius">4</CornerRadius> 24 25 <!-- 顶级菜单圆角半径 --> 26 <CornerRadius x:Key="TopLevelCornerRadius">6</CornerRadius> 27 28 <!-- 菜单动画持续时间 --> 29 <Duration x:Key="MenuAnimationDuration">0:0:0.167</Duration> 30 31 <!-- 复选标记图标路径数据 --> 32 <PathGeometry x:Key="CheckGraph"> 33 M392.533333 806.4L85.333333 503.466667l59.733334-59.733334 247.466666 247.466667L866.133333 213.333333l59.733334 59.733334L392.533333 806.4z 34 </PathGeometry> 35 36 <!-- 前进箭头图标路径数据(用于子菜单指示器) --> 37 <PathGeometry x:Key="ForwardGraph"> 38 M283.648 174.081l57.225-59.008 399.479 396.929-399.476 396.924-57.228-59.004 335.872-337.92z 39 </PathGeometry> 40 41 <!-- 菜单项ScrollViewer样式 --> 42 <Style 43 x:Key="MenuItemScrollViewerStyle" 44 BasedOn="{StaticResource {x:Type ScrollViewer}}" 45 TargetType="{x:Type ScrollViewer}"> 46 <Setter Property="HorizontalScrollBarVisibility" Value="Disabled" /> 47 <Setter Property="VerticalScrollBarVisibility" Value="Auto" /> 48 </Style> 49 50 <!-- 默认集合焦点视觉样式 得到键盘焦点时显示 --> 51 <Style x:Key="DefaultCollectionFocusVisualStyle"> 52 <Setter Property="Control.Template"> 53 <Setter.Value> 54 <ControlTemplate> 55 <Rectangle 56 Margin="4,0" 57 RadiusX="4" 58 RadiusY="4" 59 SnapsToDevicePixels="True" 60 Stroke="{DynamicResource AccentColor}" 61 StrokeThickness="2" /> 62 </ControlTemplate> 63 </Setter.Value> 64 </Setter> 65 </Style> 66 67 <!-- 默认上下文菜单样式 --> 68 <Style x:Key="DefaultContextMenuStyle" TargetType="{x:Type ContextMenu}"> 69 <Setter Property="MinWidth" Value="140" /> 70 <Setter Property="Padding" Value="0" /> 71 <Setter Property="Margin" Value="0" /> 72 <Setter Property="HasDropShadow" Value="False" /> 73 <Setter Property="Grid.IsSharedSizeScope" Value="True" /> 74 <Setter Property="Popup.PopupAnimation" Value="None" /> 75 <Setter Property="SnapsToDevicePixels" Value="True" /> 76 <Setter Property="OverridesDefaultStyle" Value="True" /> 77 <Setter Property="Template"> 78 <Setter.Value> 79 <ControlTemplate TargetType="{x:Type ContextMenu}"> 80 <Border 81 x:Name="Border" 82 Padding="{StaticResource MenuBorderPadding}" 83 Background="{TemplateBinding Background}" 84 BorderBrush="{TemplateBinding BorderBrush}" 85 BorderThickness="{TemplateBinding BorderThickness}"> 86 <!-- 用于动画的转换变换 --> 87 <Border.RenderTransform> 88 <TranslateTransform /> 89 </Border.RenderTransform> 90 <ScrollViewer CanContentScroll="True" Style="{StaticResource MenuItemScrollViewerStyle}"> 91 <!-- 菜单项容器 --> 92 <StackPanel 93 ClipToBounds="True" 94 IsItemsHost="True" 95 KeyboardNavigation.DirectionalNavigation="Cycle" 96 Orientation="Vertical" /> 97 </ScrollViewer> 98 </Border> 99 <ControlTemplate.Triggers> 100 <!-- 菜单打开时的动画效果 --> 101 <Trigger Property="IsOpen" Value="True"> 102 <Trigger.EnterActions> 103 <BeginStoryboard> 104 <Storyboard> 105 <!-- Y轴平移动画:从-45向下滑入到0 --> 106 <DoubleAnimation 107 Storyboard.TargetName="Border" 108 Storyboard.TargetProperty="(Border.RenderTransform).(TranslateTransform.Y)" 109 From="-45" 110 To="0" 111 Duration="{StaticResource MenuAnimationDuration}"> 112 <DoubleAnimation.EasingFunction> 113 <CircleEase EasingMode="EaseOut" /> 114 </DoubleAnimation.EasingFunction> 115 </DoubleAnimation> 116 </Storyboard> 117 </BeginStoryboard> 118 </Trigger.EnterActions> 119 </Trigger> 120 </ControlTemplate.Triggers> 121 </ControlTemplate> 122 </Setter.Value> 123 </Setter> 124 </Style> 125 126 <!-- 顶级菜单项头部模板(带子菜单) --> 127 <ControlTemplate x:Key="{x:Static MenuItem.TopLevelHeaderTemplateKey}" TargetType="{x:Type MenuItem}"> 128 <Border 129 x:Name="Border" 130 Margin="{StaticResource TopLevelItemMargin}" 131 Background="{TemplateBinding Background}" 132 BorderBrush="{TemplateBinding BorderBrush}" 133 BorderThickness="{TemplateBinding BorderThickness}" 134 CornerRadius="{StaticResource TopLevelCornerRadius}"> 135 <Grid> 136 <Grid.RowDefinitions> 137 <RowDefinition Height="*" /> 138 <RowDefinition Height="Auto" /> 139 </Grid.RowDefinitions> 140 141 <!-- 菜单项内容区域 --> 142 <Grid Margin="{StaticResource TopLevelContentMargin}"> 143 <Grid.ColumnDefinitions> 144 <ColumnDefinition Width="Auto" /> 145 <ColumnDefinition Width="*" /> 146 </Grid.ColumnDefinitions> 147 148 <!-- 菜单项图标 --> 149 <ContentPresenter 150 x:Name="Icon" 151 Grid.Column="0" 152 Margin="0,0,6,0" 153 VerticalAlignment="Center" 154 Content="{TemplateBinding Icon}" 155 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 156 157 <!-- 菜单项标题 --> 158 <ContentPresenter 159 x:Name="HeaderPresenter" 160 Grid.Column="1" 161 Margin="{TemplateBinding Padding}" 162 VerticalAlignment="Center" 163 ContentSource="Header" 164 RecognizesAccessKey="True" 165 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 166 TextElement.Foreground="{TemplateBinding Foreground}" /> 167 </Grid> 168 169 <!-- 子菜单弹出窗口(使用自定义FluentPopup) --> 170 <local:FluentPopup 171 x:Name="PART_Popup" 172 Grid.Row="1" 173 Grid.Column="0" 174 Focusable="False" 175 HorizontalOffset="-12" 176 IsOpen="{TemplateBinding IsSubmenuOpen}" 177 Placement="Bottom" 178 PlacementTarget="{Binding ElementName=Border}" 179 PopupAnimation="None" 180 VerticalOffset="1"> 181 <Grid 182 x:Name="SubmenuBorder" 183 Background="{DynamicResource PopupWindowBackground}" 184 SnapsToDevicePixels="True"> 185 <!-- 子菜单动画变换 --> 186 <Grid.RenderTransform> 187 <TranslateTransform /> 188 </Grid.RenderTransform> 189 <ScrollViewer 190 Padding="{StaticResource MenuBorderPadding}" 191 CanContentScroll="True" 192 Style="{StaticResource MenuItemScrollViewerStyle}"> 193 <Grid> 194 <!-- 子菜单项呈现器 --> 195 <ItemsPresenter 196 x:Name="ItemsPresenter" 197 Grid.IsSharedSizeScope="True" 198 KeyboardNavigation.DirectionalNavigation="Cycle" 199 KeyboardNavigation.TabNavigation="Cycle" 200 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 201 </Grid> 202 </ScrollViewer> 203 </Grid> 204 </local:FluentPopup> 205 </Grid> 206 </Border> 207 <ControlTemplate.Triggers> 208 <!-- 无图标时隐藏图标区域 --> 209 <Trigger Property="Icon" Value="{x:Null}"> 210 <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" /> 211 </Trigger> 212 <!-- 无标题时隐藏标题并移除图标边距 --> 213 <Trigger Property="Header" Value="{x:Null}"> 214 <Setter TargetName="Icon" Property="Margin" Value="0" /> 215 <Setter TargetName="HeaderPresenter" Property="Visibility" Value="Collapsed" /> 216 </Trigger> 217 <!-- 鼠标悬停高亮效果 --> 218 <Trigger Property="IsHighlighted" Value="True"> 219 <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" /> 220 </Trigger> 221 <!-- 子菜单打开时的动画 --> 222 <Trigger Property="IsSubmenuOpen" Value="True"> 223 <Trigger.EnterActions> 224 <BeginStoryboard> 225 <Storyboard> 226 <DoubleAnimation 227 Storyboard.TargetName="SubmenuBorder" 228 Storyboard.TargetProperty="(Border.RenderTransform).(TranslateTransform.Y)" 229 From="-45" 230 To="0" 231 Duration="{StaticResource MenuAnimationDuration}"> 232 <DoubleAnimation.EasingFunction> 233 <CircleEase EasingMode="EaseOut" /> 234 </DoubleAnimation.EasingFunction> 235 </DoubleAnimation> 236 </Storyboard> 237 </BeginStoryboard> 238 </Trigger.EnterActions> 239 </Trigger> 240 </ControlTemplate.Triggers> 241 </ControlTemplate> 242 243 <!-- 顶级菜单项模板(无子菜单) --> 244 <ControlTemplate x:Key="{x:Static MenuItem.TopLevelItemTemplateKey}" TargetType="{x:Type MenuItem}"> 245 <Border 246 x:Name="Border" 247 Margin="{StaticResource TopLevelItemMargin}" 248 Background="{TemplateBinding Background}" 249 BorderBrush="{TemplateBinding BorderBrush}" 250 BorderThickness="{TemplateBinding BorderThickness}" 251 CornerRadius="{StaticResource TopLevelCornerRadius}"> 252 <Grid Margin="{StaticResource TopLevelContentMargin}"> 253 <Grid.ColumnDefinitions> 254 <ColumnDefinition Width="Auto" /> 255 <ColumnDefinition Width="*" /> 256 </Grid.ColumnDefinitions> 257 258 <!-- 图标区域 --> 259 <ContentPresenter 260 x:Name="Icon" 261 Grid.Column="0" 262 Margin="0,0,6,0" 263 VerticalAlignment="Center" 264 Content="{TemplateBinding Icon}" 265 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 266 267 <!-- 标题区域 --> 268 <ContentPresenter 269 x:Name="HeaderPresenter" 270 Grid.Column="1" 271 Margin="{TemplateBinding Padding}" 272 VerticalAlignment="Center" 273 ContentSource="Header" 274 RecognizesAccessKey="True" 275 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 276 TextElement.Foreground="{TemplateBinding Foreground}" /> 277 </Grid> 278 </Border> 279 <ControlTemplate.Triggers> 280 <!-- 鼠标悬停效果 --> 281 <Trigger Property="IsHighlighted" Value="True"> 282 <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" /> 283 </Trigger> 284 <Trigger Property="Icon" Value="{x:Null}"> 285 <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" /> 286 </Trigger> 287 <Trigger Property="Header" Value="{x:Null}"> 288 <Setter TargetName="Icon" Property="Margin" Value="0" /> 289 <Setter TargetName="HeaderPresenter" Property="Visibility" Value="Collapsed" /> 290 </Trigger> 291 </ControlTemplate.Triggers> 292 </ControlTemplate> 293 294 <!-- 子菜单项模板(无子级) --> 295 <ControlTemplate x:Key="{x:Static MenuItem.SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}"> 296 <Border 297 x:Name="Border" 298 Margin="{StaticResource MenuItemMargin}" 299 Background="{TemplateBinding Background}" 300 BorderBrush="{TemplateBinding BorderBrush}" 301 BorderThickness="{TemplateBinding BorderThickness}" 302 CornerRadius="{StaticResource MenuCornerRadius}"> 303 <Grid Margin="{StaticResource MenuItemContentPadding}"> 304 <Grid.ColumnDefinitions> 305 <!-- 图标/复选框列,使用共享大小组确保对齐 --> 306 <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIconCol" /> 307 <!-- 标题内容列 --> 308 <ColumnDefinition Width="*" /> 309 <!-- 快捷键提示列,使用共享大小组确保对齐 --> 310 <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemRightPartCol" /> 311 </Grid.ColumnDefinitions> 312 313 <!-- 复选框图标容器 --> 314 <Border 315 x:Name="CheckBoxIconBorder" 316 Grid.Column="0" 317 VerticalAlignment="Center" 318 Visibility="Collapsed"> 319 <Path 320 x:Name="CheckBoxIcon" 321 Width="10" 322 Height="10" 323 HorizontalAlignment="Left" 324 VerticalAlignment="Center" 325 Fill="{TemplateBinding Foreground}" 326 Stretch="Uniform" /> 327 </Border> 328 329 <!-- 自定义图标 --> 330 <ContentPresenter 331 x:Name="Icon" 332 Grid.Column="0" 333 Margin="0,0,6,0" 334 VerticalAlignment="Center" 335 Content="{TemplateBinding Icon}" 336 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 337 338 <!-- 菜单项标题内容 --> 339 <ContentPresenter 340 Grid.Column="1" 341 Margin="{TemplateBinding Padding}" 342 VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 343 ContentSource="Header" 344 RecognizesAccessKey="True" 345 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 346 TextElement.Foreground="{TemplateBinding Foreground}" /> 347 348 <!-- 快捷键提示文本 --> 349 <TextBlock 350 x:Name="InputGestureText" 351 Grid.Column="2" 352 Margin="25,0,0,0" 353 VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 354 DockPanel.Dock="Right" 355 FontSize="11" 356 Opacity="0.67" 357 Text="{TemplateBinding InputGestureText}" /> 358 </Grid> 359 </Border> 360 <ControlTemplate.Triggers> 361 <!-- 高亮状态(鼠标悬停) --> 362 <Trigger Property="IsHighlighted" Value="True"> 363 <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" /> 364 </Trigger> 365 <!-- 无自定义图标时隐藏图标区域 --> 366 <Trigger Property="Icon" Value="{x:Null}"> 367 <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" /> 368 </Trigger> 369 <!-- 可复选时显示复选框图标容器 --> 370 <Trigger Property="IsCheckable" Value="True"> 371 <Setter TargetName="CheckBoxIconBorder" Property="Visibility" Value="Visible" /> 372 </Trigger> 373 <!-- 已选中时显示复选标记 --> 374 <Trigger Property="IsChecked" Value="True"> 375 <Setter TargetName="CheckBoxIcon" Property="Data" Value="{StaticResource CheckGraph}" /> 376 </Trigger> 377 <!-- 无快捷键时隐藏快捷键提示 --> 378 <Trigger Property="InputGestureText" Value=""> 379 <Setter TargetName="InputGestureText" Property="Visibility" Value="Collapsed" /> 380 </Trigger> 381 </ControlTemplate.Triggers> 382 </ControlTemplate> 383 384 <!-- 子菜单头部模板(带下级子菜单) --> 385 <ControlTemplate x:Key="{x:Static MenuItem.SubmenuHeaderTemplateKey}" TargetType="{x:Type MenuItem}"> 386 <Grid> 387 <Grid.RowDefinitions> 388 <RowDefinition Height="*" /> 389 <RowDefinition Height="Auto" /> 390 </Grid.RowDefinitions> 391 392 <!-- 菜单项外观 --> 393 <Border 394 x:Name="Border" 395 Grid.Row="1" 396 Height="{TemplateBinding Height}" 397 Margin="{StaticResource MenuItemMargin}" 398 Background="Transparent" 399 CornerRadius="{StaticResource MenuCornerRadius}"> 400 <Grid x:Name="MenuItemContent" Margin="{StaticResource MenuItemContentPadding}"> 401 <Grid.ColumnDefinitions> 402 <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIconCol" /> 403 <ColumnDefinition Width="*" /> 404 <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemRightPartCol" /> 405 </Grid.ColumnDefinitions> 406 407 <!-- 图标 --> 408 <ContentPresenter 409 x:Name="Icon" 410 Grid.Column="0" 411 Margin="0,0,6,0" 412 VerticalAlignment="Center" 413 Content="{TemplateBinding Icon}" 414 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 415 416 <!-- 标题 --> 417 <ContentPresenter 418 x:Name="HeaderHost" 419 Grid.Column="1" 420 Margin="{TemplateBinding Padding}" 421 VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 422 ContentSource="Header" 423 RecognizesAccessKey="True" 424 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 425 426 <!-- 右箭头指示器(表示有子菜单) --> 427 <Path 428 Grid.Column="2" 429 Width="10" 430 Height="10" 431 Margin="0,0,4,0" 432 HorizontalAlignment="Right" 433 VerticalAlignment="Center" 434 Data="{StaticResource ForwardGraph}" 435 Fill="{TemplateBinding Foreground}" 436 Opacity="0.67" 437 Stretch="Uniform" /> 438 </Grid> 439 </Border> 440 441 <!-- 子菜单弹出窗口(向右展开) --> 442 <local:FluentPopup 443 x:Name="PART_Popup" 444 Grid.Row="1" 445 Focusable="False" 446 IsOpen="{TemplateBinding IsSubmenuOpen}" 447 Placement="Right" 448 PlacementTarget="{Binding ElementName=MenuItemContent}" 449 PopupAnimation="None"> 450 <Grid x:Name="PopupRoot" Background="{DynamicResource PopupWindowBackground}"> 451 <!-- 子菜单动画变换 --> 452 <Grid.RenderTransform> 453 <TranslateTransform /> 454 </Grid.RenderTransform> 455 <ScrollViewer 456 Padding="{StaticResource MenuBorderPadding}" 457 CanContentScroll="True" 458 Style="{StaticResource MenuItemScrollViewerStyle}"> 459 <!-- 子菜单项容器 --> 460 <ItemsPresenter 461 x:Name="ItemsPresenter" 462 Grid.IsSharedSizeScope="True" 463 KeyboardNavigation.DirectionalNavigation="Cycle" 464 KeyboardNavigation.TabNavigation="Cycle" 465 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> 466 </ScrollViewer> 467 </Grid> 468 </local:FluentPopup> 469 </Grid> 470 <ControlTemplate.Triggers> 471 <!-- 无图标时优化布局 --> 472 <Trigger Property="Icon" Value="{x:Null}"> 473 <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" /> 474 <Setter TargetName="Icon" Property="Margin" Value="0" /> 475 </Trigger> 476 <!-- 高亮效果 --> 477 <Trigger Property="IsHighlighted" Value="true"> 478 <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" /> 479 </Trigger> 480 <!-- 子菜单打开动画 --> 481 <Trigger Property="IsSubmenuOpen" Value="True"> 482 <Trigger.EnterActions> 483 <BeginStoryboard> 484 <Storyboard> 485 <!-- Y轴平移动画:从-45向下滑入到0 --> 486 <DoubleAnimation 487 Storyboard.TargetName="PopupRoot" 488 Storyboard.TargetProperty="(Grid.RenderTransform).(TranslateTransform.Y)" 489 From="-45" 490 To="0" 491 Duration="{StaticResource MenuAnimationDuration}"> 492 <DoubleAnimation.EasingFunction> 493 <CircleEase EasingMode="EaseOut" /> 494 </DoubleAnimation.EasingFunction> 495 </DoubleAnimation> 496 </Storyboard> 497 </BeginStoryboard> 498 </Trigger.EnterActions> 499 </Trigger> 500 </ControlTemplate.Triggers> 501 </ControlTemplate> 502 503 <!-- 默认菜单项样式 --> 504 <Style x:Key="DefaultMenuItemStyle" TargetType="{x:Type MenuItem}"> 505 <Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultCollectionFocusVisualStyle}" /> 506 <Setter Property="KeyboardNavigation.IsTabStop" Value="True" /> 507 <Setter Property="Background" Value="Transparent" /> 508 <Setter Property="BorderBrush" Value="Transparent" /> 509 <Setter Property="BorderThickness" Value="1" /> 510 <Setter Property="Focusable" Value="True" /> 511 <Setter Property="OverridesDefaultStyle" Value="True" /> 512 <Style.Triggers> 513 <!-- 根据菜单项角色应用不同模板 --> 514 515 <!-- 顶级菜单项(带子菜单) --> 516 <Trigger Property="Role" Value="TopLevelHeader"> 517 <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.TopLevelHeaderTemplateKey}}" /> 518 <Setter Property="Grid.IsSharedSizeScope" Value="True" /> 519 <Setter Property="Height" Value="{x:Static sys:Double.NaN}" /> 520 </Trigger> 521 522 <!-- 顶级菜单项(无子菜单) --> 523 <Trigger Property="Role" Value="TopLevelItem"> 524 <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.TopLevelItemTemplateKey}}" /> 525 <Setter Property="Height" Value="{x:Static sys:Double.NaN}" /> 526 </Trigger> 527 528 <!-- 子菜单项(带子菜单) --> 529 <Trigger Property="Role" Value="SubmenuHeader"> 530 <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.SubmenuHeaderTemplateKey}}" /> 531 </Trigger> 532 533 <!-- 子菜单项(无子菜单) --> 534 <Trigger Property="Role" Value="SubmenuItem"> 535 <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.SubmenuItemTemplateKey}}" /> 536 </Trigger> 537 </Style.Triggers> 538 </Style> 539 540 </ResourceDictionary>
三、应用模板和样式到全局
将上面的资源字典合并到应用程序资源中,然后为ContextMenu和MenuItem指定默认样式:
<Style BasedOn="{StaticResource DefaultContextMenuStyle}" TargetType="{x:Type ContextMenu}"> <Style.Setters> <Setter Property="local:FluentTooltip.UseFluentStyle" Value="True" /> <Setter Property="Background" Value="{DynamicResource PopupWindowBackground}" /> <Setter Property="Foreground" Value="{DynamicResource ForeColor}" /> </Style.Setters> </Style> <Style BasedOn="{StaticResource DefaultMenuItemStyle}" TargetType="MenuItem"> <Setter Property="Height" Value="36" /> <Setter Property="Foreground" Value="{DynamicResource ForeColor}" /> <Setter Property="VerticalContentAlignment" Value="Center" /> </Style>
注意,ContextMenu需要使用之前文章中的FluentTooltip.UseFluentStyle来实现亚克力材质特效。其内部原理都是反射获取popup的hwnd句柄,然后附加WindowMaterial特效。
参考文档
1. ContextMenu Styles and Templates WPF | Microsoft Learn
2. Menu Styles and Templates WPF | Microsoft Learn
3. PresentationFramework.Fluent/Themes/Fluent.xaml | GitHub

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名TwilightLemon(https://blog.twlmgatito.cn),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
文章及其代码仓库可能不时更新,查看原文:WPF 为ContextMenu使用Fluent风格的亚克力材质特效 - Twlm's Blog

浙公网安备 33010602011771号