2518 字
13 分钟
WPF 为ContextMenu使用Fluent风格的亚克力材质特效
2025-11-19

书接上回,我们的Fluent WPF的版图已经完成了:

先来看看效果图(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
Waiting for api.github.com...
00K
0K
0K
Waiting...

一、为什么需要一个新的ContextMenu和MenuItem样式#

如果你直接给ContextMenu应用WindowMaterial特效,可能会出现以下丑陋的效果:
或者:
原因在于古老的ContextMenu和MenuItem样式并不能通过简单修改Background实现我们想要的布局和交互效果。

二、ContextMenu与MenuItem的结构#

1. ContextMenu 的结构#

ContextMenu直观上看是一个Popup,但它的控件模板并不包含Popup,而是直接指定内部元素(包含一个ScrollViewerStackPanel)。因此不能从控件模板中替换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时)。

以下是完整的资源字典,包含了完整的注释。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WindowEffectTest"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">

    <!--  菜单边框内边距  -->
    <Thickness x:Key="MenuBorderPadding">0,3,0,3</Thickness>

    <!--  菜单项外边距  -->
    <Thickness x:Key="MenuItemMargin">4,1</Thickness>

    <!--  菜单项内容内边距  -->
    <Thickness x:Key="MenuItemContentPadding">10,6</Thickness>

    <!--  顶级菜单项外边距  -->
    <Thickness x:Key="TopLevelItemMargin">4</Thickness>

    <!--  顶级菜单项内容边距  -->
    <Thickness x:Key="TopLevelContentMargin">10</Thickness>

    <!--  菜单圆角半径  -->
    <CornerRadius x:Key="MenuCornerRadius">4</CornerRadius>

    <!--  顶级菜单圆角半径  -->
    <CornerRadius x:Key="TopLevelCornerRadius">6</CornerRadius>

    <!--  菜单动画持续时间  -->
    <Duration x:Key="MenuAnimationDuration">0:0:0.167</Duration>

    <!--  复选标记图标路径数据  -->
    <PathGeometry x:Key="CheckGraph">
        M392.533333 806.4L85.333333 503.466667l59.733334-59.733334 247.466666 247.466667L866.133333 213.333333l59.733334 59.733334L392.533333 806.4z
    </PathGeometry>

    <!--  前进箭头图标路径数据(用于子菜单指示器)  -->
    <PathGeometry x:Key="ForwardGraph">
        M283.648 174.081l57.225-59.008 399.479 396.929-399.476 396.924-57.228-59.004 335.872-337.92z
    </PathGeometry>

    <!--  菜单项ScrollViewer样式  -->
    <Style
        x:Key="MenuItemScrollViewerStyle"
        BasedOn="{StaticResource {x:Type ScrollViewer}}"
        TargetType="{x:Type ScrollViewer}">
        <Setter Property="HorizontalScrollBarVisibility" Value="Disabled" />
        <Setter Property="VerticalScrollBarVisibility" Value="Auto" />
    </Style>

    <!--  默认集合焦点视觉样式  得到键盘焦点时显示  -->
    <Style x:Key="DefaultCollectionFocusVisualStyle">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Rectangle
                        Margin="4,0"
                        RadiusX="4"
                        RadiusY="4"
                        SnapsToDevicePixels="True"
                        Stroke="{DynamicResource AccentColor}"
                        StrokeThickness="2" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!--  默认上下文菜单样式  -->
    <Style x:Key="DefaultContextMenuStyle" TargetType="{x:Type ContextMenu}">
        <Setter Property="MinWidth" Value="140" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="Margin" Value="0" />
        <Setter Property="HasDropShadow" Value="False" />
        <Setter Property="Grid.IsSharedSizeScope" Value="True" />
        <Setter Property="Popup.PopupAnimation" Value="None" />
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="OverridesDefaultStyle" Value="True" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ContextMenu}">
                    <Border
                        x:Name="Border"
                        Padding="{StaticResource MenuBorderPadding}"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <!--  用于动画的转换变换  -->
                        <Border.RenderTransform>
                            <TranslateTransform />
                        </Border.RenderTransform>
                        <ScrollViewer CanContentScroll="True" Style="{StaticResource MenuItemScrollViewerStyle}">
                            <!--  菜单项容器  -->
                            <StackPanel
                                ClipToBounds="True"
                                IsItemsHost="True"
                                KeyboardNavigation.DirectionalNavigation="Cycle"
                                Orientation="Vertical" />
                        </ScrollViewer>
                    </Border>
                    <ControlTemplate.Triggers>
                        <!--  菜单打开时的动画效果  -->
                        <Trigger Property="IsOpen" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <!--  Y轴平移动画:从-45向下滑入到0  -->
                                        <DoubleAnimation
                                            Storyboard.TargetName="Border"
                                            Storyboard.TargetProperty="(Border.RenderTransform).(TranslateTransform.Y)"
                                            From="-45"
                                            To="0"
                                            Duration="{StaticResource MenuAnimationDuration}">
                                            <DoubleAnimation.EasingFunction>
                                                <CircleEase EasingMode="EaseOut" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!--  顶级菜单项头部模板(带子菜单)  -->
    <ControlTemplate x:Key="{x:Static MenuItem.TopLevelHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
        <Border
            x:Name="Border"
            Margin="{StaticResource TopLevelItemMargin}"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            CornerRadius="{StaticResource TopLevelCornerRadius}">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>

                <!--  菜单项内容区域  -->
                <Grid Margin="{StaticResource TopLevelContentMargin}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <!--  菜单项图标  -->
                    <ContentPresenter
                        x:Name="Icon"
                        Grid.Column="0"
                        Margin="0,0,6,0"
                        VerticalAlignment="Center"
                        Content="{TemplateBinding Icon}"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />

                    <!--  菜单项标题  -->
                    <ContentPresenter
                        x:Name="HeaderPresenter"
                        Grid.Column="1"
                        Margin="{TemplateBinding Padding}"
                        VerticalAlignment="Center"
                        ContentSource="Header"
                        RecognizesAccessKey="True"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                        TextElement.Foreground="{TemplateBinding Foreground}" />
                </Grid>

                <!--  子菜单弹出窗口(使用自定义FluentPopup)  -->
                <local:FluentPopup
                    x:Name="PART_Popup"
                    Grid.Row="1"
                    Grid.Column="0"
                    Focusable="False"
                    HorizontalOffset="-12"
                    IsOpen="{TemplateBinding IsSubmenuOpen}"
                    Placement="Bottom"
                    PlacementTarget="{Binding ElementName=Border}"
                    PopupAnimation="None"
                    VerticalOffset="1">
                    <Grid
                        x:Name="SubmenuBorder"
                        Background="{DynamicResource PopupWindowBackground}"
                        SnapsToDevicePixels="True">
                        <!--  子菜单动画变换  -->
                        <Grid.RenderTransform>
                            <TranslateTransform />
                        </Grid.RenderTransform>
                        <ScrollViewer
                            Padding="{StaticResource MenuBorderPadding}"
                            CanContentScroll="True"
                            Style="{StaticResource MenuItemScrollViewerStyle}">
                            <Grid>
                                <!--  子菜单项呈现器  -->
                                <ItemsPresenter
                                    x:Name="ItemsPresenter"
                                    Grid.IsSharedSizeScope="True"
                                    KeyboardNavigation.DirectionalNavigation="Cycle"
                                    KeyboardNavigation.TabNavigation="Cycle"
                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                            </Grid>
                        </ScrollViewer>
                    </Grid>
                </local:FluentPopup>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <!--  无图标时隐藏图标区域  -->
            <Trigger Property="Icon" Value="{x:Null}">
                <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
            </Trigger>
            <!--  无标题时隐藏标题并移除图标边距  -->
            <Trigger Property="Header" Value="{x:Null}">
                <Setter TargetName="Icon" Property="Margin" Value="0" />
                <Setter TargetName="HeaderPresenter" Property="Visibility" Value="Collapsed" />
            </Trigger>
            <!--  鼠标悬停高亮效果  -->
            <Trigger Property="IsHighlighted" Value="True">
                <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" />
            </Trigger>
            <!--  子菜单打开时的动画  -->
            <Trigger Property="IsSubmenuOpen" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="SubmenuBorder"
                                Storyboard.TargetProperty="(Border.RenderTransform).(TranslateTransform.Y)"
                                From="-45"
                                To="0"
                                Duration="{StaticResource MenuAnimationDuration}">
                                <DoubleAnimation.EasingFunction>
                                    <CircleEase EasingMode="EaseOut" />
                                </DoubleAnimation.EasingFunction>
                            </DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <!--  顶级菜单项模板(无子菜单)  -->
    <ControlTemplate x:Key="{x:Static MenuItem.TopLevelItemTemplateKey}" TargetType="{x:Type MenuItem}">
        <Border
            x:Name="Border"
            Margin="{StaticResource TopLevelItemMargin}"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            CornerRadius="{StaticResource TopLevelCornerRadius}">
            <Grid Margin="{StaticResource TopLevelContentMargin}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <!--  图标区域  -->
                <ContentPresenter
                    x:Name="Icon"
                    Grid.Column="0"
                    Margin="0,0,6,0"
                    VerticalAlignment="Center"
                    Content="{TemplateBinding Icon}"
                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />

                <!--  标题区域  -->
                <ContentPresenter
                    x:Name="HeaderPresenter"
                    Grid.Column="1"
                    Margin="{TemplateBinding Padding}"
                    VerticalAlignment="Center"
                    ContentSource="Header"
                    RecognizesAccessKey="True"
                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                    TextElement.Foreground="{TemplateBinding Foreground}" />
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <!--  鼠标悬停效果  -->
            <Trigger Property="IsHighlighted" Value="True">
                <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" />
            </Trigger>
            <Trigger Property="Icon" Value="{x:Null}">
                <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
            </Trigger>
            <Trigger Property="Header" Value="{x:Null}">
                <Setter TargetName="Icon" Property="Margin" Value="0" />
                <Setter TargetName="HeaderPresenter" Property="Visibility" Value="Collapsed" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <!--  子菜单项模板(无子级)  -->
    <ControlTemplate x:Key="{x:Static MenuItem.SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}">
        <Border
            x:Name="Border"
            Margin="{StaticResource MenuItemMargin}"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            CornerRadius="{StaticResource MenuCornerRadius}">
            <Grid Margin="{StaticResource MenuItemContentPadding}">
                <Grid.ColumnDefinitions>
                    <!--  图标/复选框列,使用共享大小组确保对齐  -->
                    <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIconCol" />
                    <!--  标题内容列  -->
                    <ColumnDefinition Width="*" />
                    <!--  快捷键提示列,使用共享大小组确保对齐  -->
                    <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemRightPartCol" />
                </Grid.ColumnDefinitions>

                <!--  复选框图标容器  -->
                <Border
                    x:Name="CheckBoxIconBorder"
                    Grid.Column="0"
                    VerticalAlignment="Center"
                    Visibility="Collapsed">
                    <Path
                        x:Name="CheckBoxIcon"
                        Width="10"
                        Height="10"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Center"
                        Fill="{TemplateBinding Foreground}"
                        Stretch="Uniform" />
                </Border>

                <!--  自定义图标  -->
                <ContentPresenter
                    x:Name="Icon"
                    Grid.Column="0"
                    Margin="0,0,6,0"
                    VerticalAlignment="Center"
                    Content="{TemplateBinding Icon}"
                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />

                <!--  菜单项标题内容  -->
                <ContentPresenter
                    Grid.Column="1"
                    Margin="{TemplateBinding Padding}"
                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                    ContentSource="Header"
                    RecognizesAccessKey="True"
                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                    TextElement.Foreground="{TemplateBinding Foreground}" />

                <!--  快捷键提示文本  -->
                <TextBlock
                    x:Name="InputGestureText"
                    Grid.Column="2"
                    Margin="25,0,0,0"
                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                    DockPanel.Dock="Right"
                    FontSize="11"
                    Opacity="0.67"
                    Text="{TemplateBinding InputGestureText}" />
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <!--  高亮状态(鼠标悬停)  -->
            <Trigger Property="IsHighlighted" Value="True">
                <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" />
            </Trigger>
            <!--  无自定义图标时隐藏图标区域  -->
            <Trigger Property="Icon" Value="{x:Null}">
                <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
            </Trigger>
            <!--  可复选时显示复选框图标容器  -->
            <Trigger Property="IsCheckable" Value="True">
                <Setter TargetName="CheckBoxIconBorder" Property="Visibility" Value="Visible" />
            </Trigger>
            <!--  已选中时显示复选标记  -->
            <Trigger Property="IsChecked" Value="True">
                <Setter TargetName="CheckBoxIcon" Property="Data" Value="{StaticResource CheckGraph}" />
            </Trigger>
            <!--  无快捷键时隐藏快捷键提示  -->
            <Trigger Property="InputGestureText" Value="">
                <Setter TargetName="InputGestureText" Property="Visibility" Value="Collapsed" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <!--  子菜单头部模板(带下级子菜单)  -->
    <ControlTemplate x:Key="{x:Static MenuItem.SubmenuHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <!--  菜单项外观  -->
            <Border
                x:Name="Border"
                Grid.Row="1"
                Height="{TemplateBinding Height}"
                Margin="{StaticResource MenuItemMargin}"
                Background="Transparent"
                CornerRadius="{StaticResource MenuCornerRadius}">
                <Grid x:Name="MenuItemContent" Margin="{StaticResource MenuItemContentPadding}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIconCol" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemRightPartCol" />
                    </Grid.ColumnDefinitions>

                    <!--  图标  -->
                    <ContentPresenter
                        x:Name="Icon"
                        Grid.Column="0"
                        Margin="0,0,6,0"
                        VerticalAlignment="Center"
                        Content="{TemplateBinding Icon}"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />

                    <!--  标题  -->
                    <ContentPresenter
                        x:Name="HeaderHost"
                        Grid.Column="1"
                        Margin="{TemplateBinding Padding}"
                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                        ContentSource="Header"
                        RecognizesAccessKey="True"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />

                    <!--  右箭头指示器(表示有子菜单)  -->
                    <Path
                        Grid.Column="2"
                        Width="10"
                        Height="10"
                        Margin="0,0,4,0"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Center"
                        Data="{StaticResource ForwardGraph}"
                        Fill="{TemplateBinding Foreground}"
                        Opacity="0.67"
                        Stretch="Uniform" />
                </Grid>
            </Border>

            <!--  子菜单弹出窗口(向右展开)  -->
            <local:FluentPopup
                x:Name="PART_Popup"
                Grid.Row="1"
                Focusable="False"
                IsOpen="{TemplateBinding IsSubmenuOpen}"
                Placement="Right"
                PlacementTarget="{Binding ElementName=MenuItemContent}"
                PopupAnimation="None">
                <Grid x:Name="PopupRoot" Background="{DynamicResource PopupWindowBackground}">
                    <!--  子菜单动画变换  -->
                    <Grid.RenderTransform>
                        <TranslateTransform />
                    </Grid.RenderTransform>
                    <ScrollViewer
                        Padding="{StaticResource MenuBorderPadding}"
                        CanContentScroll="True"
                        Style="{StaticResource MenuItemScrollViewerStyle}">
                        <!--  子菜单项容器  -->
                        <ItemsPresenter
                            x:Name="ItemsPresenter"
                            Grid.IsSharedSizeScope="True"
                            KeyboardNavigation.DirectionalNavigation="Cycle"
                            KeyboardNavigation.TabNavigation="Cycle"
                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </ScrollViewer>
                </Grid>
            </local:FluentPopup>
        </Grid>
        <ControlTemplate.Triggers>
            <!--  无图标时优化布局  -->
            <Trigger Property="Icon" Value="{x:Null}">
                <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
                <Setter TargetName="Icon" Property="Margin" Value="0" />
            </Trigger>
            <!--  高亮效果  -->
            <Trigger Property="IsHighlighted" Value="true">
                <Setter TargetName="Border" Property="Background" Value="{DynamicResource MaskColor}" />
            </Trigger>
            <!--  子菜单打开动画  -->
            <Trigger Property="IsSubmenuOpen" Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <!--  Y轴平移动画:从-45向下滑入到0  -->
                            <DoubleAnimation
                                Storyboard.TargetName="PopupRoot"
                                Storyboard.TargetProperty="(Grid.RenderTransform).(TranslateTransform.Y)"
                                From="-45"
                                To="0"
                                Duration="{StaticResource MenuAnimationDuration}">
                                <DoubleAnimation.EasingFunction>
                                    <CircleEase EasingMode="EaseOut" />
                                </DoubleAnimation.EasingFunction>
                            </DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <!--  默认菜单项样式  -->
    <Style x:Key="DefaultMenuItemStyle" TargetType="{x:Type MenuItem}">
        <Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultCollectionFocusVisualStyle}" />
        <Setter Property="KeyboardNavigation.IsTabStop" Value="True" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="BorderBrush" Value="Transparent" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Focusable" Value="True" />
        <Setter Property="OverridesDefaultStyle" Value="True" />
        <Style.Triggers>
            <!--  根据菜单项角色应用不同模板  -->

            <!--  顶级菜单项(带子菜单)  -->
            <Trigger Property="Role" Value="TopLevelHeader">
                <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.TopLevelHeaderTemplateKey}}" />
                <Setter Property="Grid.IsSharedSizeScope" Value="True" />
                <Setter Property="Height" Value="{x:Static sys:Double.NaN}" />
            </Trigger>

            <!--  顶级菜单项(无子菜单)  -->
            <Trigger Property="Role" Value="TopLevelItem">
                <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.TopLevelItemTemplateKey}}" />
                <Setter Property="Height" Value="{x:Static sys:Double.NaN}" />
            </Trigger>

            <!--  子菜单项(带子菜单)  -->
            <Trigger Property="Role" Value="SubmenuHeader">
                <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.SubmenuHeaderTemplateKey}}" />
            </Trigger>

            <!--  子菜单项(无子菜单)  -->
            <Trigger Property="Role" Value="SubmenuItem">
                <Setter Property="Template" Value="{StaticResource {x:Static MenuItem.SubmenuItemTemplateKey}}" />
            </Trigger>
        </Style.Triggers>
    </Style>

</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特效。

参考连接#

ContextMenu Styles and Templates WPF | Microsoft Learn

Menu Styles and Templates WPF | Microsoft Learn

PresentationFramework.Fluent/Themes/Fluent.xaml | GitHub

访问量: 加载中...
WPF 为ContextMenu使用Fluent风格的亚克力材质特效
https://blog.twlmgatito.cn/posts/wpf-fluent-contextmenu-with-arcrylic/
本文同步发布于 博客园、CSDN等技术论坛 微信公众号仅与WPF Develops官方合作 转载请保留所有信息
作者
TwilightLemon
发布于
2025-11-19
许可协议
CC BY-NC-SA 4.0