书接上回,我们的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>
View Code
三、应用模板和样式到全局
将上面的资源字典合并到应用程序资源中,然后为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特效。
参考文档
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名TwilightLemon(https://blog.twlmgatito.cn),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
文章及其代码仓库可能不时更新,查看原文:WPF 为ContextMenu使用Fluent风格的亚克力材质特效 - Twlm's Blog