Visual Studio如何实现这种MenuItem绘图?

时间:2017-04-17 05:31:57

标签: wpf themes visual-studio-2017

我正在为我的应用程序创建一个黑暗的ui,并在使用Visual Studio作为参考点时遇到了一些有趣的东西。我注意到他们渲染的MenuItem几乎就像是Tabcontrol中的Tabs一样。这是一张图片:

enter image description here

这就是我的样子:

enter image description here

我知道这可能很难看,因为所有东西都是相同的颜色,所以我继续前进并制作了另一张经过修改的图片以更好地突出该区域。

enter image description here

正如您可能希望看到的那样,Visual studio在MenuItem周围绘制边框,然后不会在其下方绘制边框,用于下拉子项。但Visual Studio是如何做到的呢?我怎么能实现它?这是我的模板:

<Style x:Key="{x:Type Menu}" TargetType="Menu">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="Foreground" Value="#f1f1f1" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Menu">
                        <Border x:Name="MainMenu" Background="#2d2d30">
                            <StackPanel
                                ClipToBounds="True"
                                IsItemsHost="True"
                                Orientation="Horizontal" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <ControlTemplate x:Key="MenuItemControlTemplate1" TargetType="{x:Type MenuItem}">
            <Border
                x:Name="templateRoot"
                Height="16"
                Background="{TemplateBinding Background}"
                BorderBrush="#535353"
                SnapsToDevicePixels="True">
                <Grid VerticalAlignment="Center">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>


                    <ContentPresenter
                        Grid.Column="1"
                        Margin="{TemplateBinding Padding}"
                        Content="{TemplateBinding Header}"
                        ContentSource="Header"
                        ContentStringFormat="{TemplateBinding HeaderStringFormat}"
                        ContentTemplate="{TemplateBinding HeaderTemplate}"
                        RecognizesAccessKey="True"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    <Popup
                        x:Name="PART_Popup"
                        AllowsTransparency="True"
                        Focusable="False"
                        IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
                        Placement="Bottom"
                        PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
                        <Border
                            x:Name="SubMenuBorder"
                            Padding="2"
                            Background="#1b1b1c"
                            BorderBrush="#595959"
                            BorderThickness="1">
                            <ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
                                <Grid RenderOptions.ClearTypeHint="Enabled">
                                    <Canvas
                                        Width="0"
                                        Height="0"
                                        HorizontalAlignment="Left"
                                        VerticalAlignment="Top">
                                        <Rectangle
                                            x:Name="OpaqueRect"
                                            Width="{Binding ActualWidth, ElementName=SubMenuBorder}"
                                            Height="{Binding ActualHeight, ElementName=SubMenuBorder}"
                                            Fill="{Binding Background, ElementName=SubMenuBorder}" />
                                    </Canvas>
                                    <ItemsPresenter
                                        x:Name="ItemsPresenter"
                                        Grid.IsSharedSizeScope="True"
                                        KeyboardNavigation.DirectionalNavigation="Cycle"
                                        KeyboardNavigation.TabNavigation="Cycle"
                                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                </Grid>
                            </ScrollViewer>
                        </Border>
                    </Popup>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsSuspendingPopupAnimation" Value="True">
                    <Setter TargetName="PART_Popup" Property="PopupAnimation" Value="None" />
                </Trigger>
                <Trigger Property="IsHighlighted" Value="True">
                    <Setter TargetName="templateRoot" Property="Background" Value="#3e3e40" />
                    <Setter TargetName="templateRoot" Property="BorderBrush" Value="#2C2C2C" />
                </Trigger>
                <Trigger SourceName="SubMenuScrollViewer" Property="CanContentScroll" Value="False">
                    <Setter TargetName="OpaqueRect" Property="Canvas.Top" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}" />
                    <Setter TargetName="OpaqueRect" Property="Canvas.Left" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}" />
                </Trigger>
                <Trigger Property="IsKeyboardFocusWithin" Value="True">
                    <Setter TargetName="templateRoot" Property="Background" Value="#1b1b1c" />
                    <Setter Property="Header" Value="Test" />
                    <Setter Property="BorderBrush" Value="#2C2C2C" />
                    <Setter Property="BorderThickness" Value="1" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <ControlTemplate x:Key="MenuItemControlTemplate2" TargetType="{x:Type MenuItem}">
            <Border
                x:Name="templateRoot"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                SnapsToDevicePixels="True">
                <Grid Margin="-1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition
                            Width="Auto"
                            MinWidth="22"
                            SharedSizeGroup="MenuItemIconColumnGroup" />
                        <ColumnDefinition Width="13" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="30" />
                        <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGTColumnGroup" />
                        <ColumnDefinition Width="20" />
                    </Grid.ColumnDefinitions>
                    <ContentPresenter
                        x:Name="Icon"
                        Width="16"
                        Height="16"
                        Margin="3"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Content="{TemplateBinding Icon}"
                        ContentSource="Icon"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    <Border
                        x:Name="GlyphPanel"
                        Width="22"
                        Height="22"
                        Margin="-1,0,0,0"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Background="#3D26A0DA"
                        BorderBrush="#FF26A0DA"
                        BorderThickness="1"
                        ClipToBounds="False"
                        Visibility="Hidden">
                        <Path
                            x:Name="Glyph"
                            Width="10"
                            Height="11"
                            Data="F1M10,1.2L4.7,9.1 4.5,9.1 0,5.2 1.3,3.5 4.3,6.1 8.3,0 10,1.2z"
                            Fill="#FF212121"
                            FlowDirection="LeftToRight" />
                    </Border>
                    <ContentPresenter
                        x:Name="menuHeaderContainer"
                        Grid.Column="2"
                        Margin="{TemplateBinding Padding}"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Center"
                        Content="{TemplateBinding Header}"
                        ContentSource="Header"
                        ContentStringFormat="{TemplateBinding HeaderStringFormat}"
                        ContentTemplate="{TemplateBinding HeaderTemplate}"
                        RecognizesAccessKey="True"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    <TextBlock
                        x:Name="menuGestureText"
                        Grid.Column="4"
                        Margin="{TemplateBinding Padding}"
                        VerticalAlignment="Center"
                        Opacity="0.7"
                        Text="{TemplateBinding InputGestureText}" />
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="Icon" Value="{x:Null}">
                    <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
                </Trigger>
                <Trigger Property="IsChecked" Value="True">
                    <Setter TargetName="GlyphPanel" Property="Visibility" Value="Visible" />
                    <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
                </Trigger>
                <Trigger Property="IsHighlighted" Value="True">
                    <Setter TargetName="templateRoot" Property="BorderBrush" Value="Orange" />
                    <Setter TargetName="templateRoot" Property="Background" Value="Yellow" />
                    <Setter TargetName="menuHeaderContainer" Property="TextBlock.Foreground" Value="Black" />
                </Trigger>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter TargetName="templateRoot" Property="TextElement.Foreground" Value="#FF707070" />
                    <Setter TargetName="Glyph" Property="Fill" Value="#FF707070" />
                </Trigger>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsHighlighted" Value="True" />
                        <Condition Property="IsEnabled" Value="False" />
                    </MultiTrigger.Conditions>
                    <Setter TargetName="templateRoot" Property="Background" Value="#0A000000" />
                    <Setter TargetName="templateRoot" Property="BorderBrush" Value="#21000000" />
                </MultiTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

2 个答案:

答案 0 :(得分:2)

为了弄清楚Visual Studio里面发生了什么,我启动了Visual Studio 2017的两个实例并将一个实例附加到另一个实例上,这允许我使用实时视觉树工具来检查控件(可能你可以使用{ {3}}也是如此)。

事实证明,Visual Studio中的菜单弹出窗口似乎是偏移的,因此它覆盖了菜单栏,并绘制了一些小盒子以实现连续的标签外观。如果使用“属性”窗口调整弹出窗口的VerticalOffset属性,使其与主菜单分离,则尤其明显。

在Visual Tree中查找Popup

Snoop

VerticalOffset从原来的-2更改为正数:

Tools menu popup in the visual tree

结果弹出:

Change the vertical offset from -2 to something some positive number

如果您查看现在分隔的菜单弹出窗口的左上角,您应该能够看到弹出窗口的一个小扩展,当VerticalOffset最初为-2时,与父MenuItem重叠边框创造了一个类似标签的控件的幻觉。

了解这一点,创建Visual Studio解决方案的基本版本非常简单:

<Window x:Class="MenuItemTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:MenuItemTest"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        mc:Ignorable="d">
    <Window.Resources>
        <local:SubtractingConverter x:Key="SubtractingConverter" />
        <Style x:Key="{x:Type Menu}" TargetType="Menu">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="Foreground" Value="#f1f1f1" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Menu">
                        <Border x:Name="MainMenu" Background="#2d2d30">
                            <StackPanel ClipToBounds="True"
                                        IsItemsHost="True"
                                        Orientation="Horizontal" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <ControlTemplate x:Key="MenuItemControlTemplate1" TargetType="{x:Type MenuItem}">
            <Border x:Name="templateRoot"
                    Height="16"
                    Background="{TemplateBinding Background}"
                    BorderBrush="#535353"
                    SnapsToDevicePixels="True">
                <Grid VerticalAlignment="Center">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <ContentPresenter Grid.Column="1"
                                      Margin="{TemplateBinding Padding}"
                                      Content="{TemplateBinding Header}"
                                      ContentSource="Header"
                                      ContentStringFormat="{TemplateBinding HeaderStringFormat}"
                                      ContentTemplate="{TemplateBinding HeaderTemplate}"
                                      RecognizesAccessKey="True"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    <Popup x:Name="PART_Popup"
                           AllowsTransparency="True"
                           Focusable="False"
                           IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
                           Placement="Bottom"
                           PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
                        <Grid>
                            <Border x:Name="SubMenuBorder"
                                    Padding="2"
                                    Background="#1b1b1c"
                                    BorderBrush="#595959"
                                    BorderThickness="1">
                                <ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer,                      TypeInTargetAssembly={x:Type FrameworkElement}}}">
                                    <Grid RenderOptions.ClearTypeHint="Enabled">
                                        <Canvas Width="0"
                                                Height="0"
                                                HorizontalAlignment="Left"
                                                VerticalAlignment="Top">
                                            <Rectangle x:Name="OpaqueRect"
                                                       Width="{Binding ActualWidth, ElementName=SubMenuBorder}"
                                                       Height="{Binding ActualHeight, ElementName=SubMenuBorder}"
                                                       Fill="{Binding Background, ElementName=SubMenuBorder}" />
                                        </Canvas>
                                        <ItemsPresenter x:Name="ItemsPresenter"
                                                        Grid.IsSharedSizeScope="True"
                                                        KeyboardNavigation.DirectionalNavigation="Cycle"
                                                        KeyboardNavigation.TabNavigation="Cycle"
                                                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                    </Grid>
                                </ScrollViewer>
                            </Border>
                            <Rectangle Width="{TemplateBinding ActualWidth,
                                                               Converter={StaticResource SubtractingConverter},
                                                               ConverterParameter=1}"
                                       Height="2"
                                       Margin="1,0,0,0"
                                       HorizontalAlignment="Left"
                                       VerticalAlignment="Top"
                                       Fill="{Binding Background, ElementName=SubMenuBorder}" />
                        </Grid>
                    </Popup>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsSuspendingPopupAnimation" Value="True">
                    <Setter TargetName="PART_Popup" Property="PopupAnimation" Value="None" />
                </Trigger>
                <Trigger Property="IsHighlighted" Value="True">
                    <Setter TargetName="templateRoot" Property="Background" Value="#3e3e40" />
                    <Setter TargetName="templateRoot" Property="BorderBrush" Value="#2C2C2C" />
                </Trigger>
                <Trigger SourceName="SubMenuScrollViewer" Property="CanContentScroll" Value="False">
                    <Setter TargetName="OpaqueRect" Property="Canvas.Top" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}" />
                    <Setter TargetName="OpaqueRect" Property="Canvas.Left" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}" />
                </Trigger>
                <Trigger Property="IsKeyboardFocusWithin" Value="True">
                    <Setter TargetName="templateRoot" Property="Background" Value="#1b1b1c" />
                    <Setter Property="Header" Value="Test" />
                    <Setter Property="BorderBrush" Value="#2C2C2C" />
                    <Setter Property="BorderThickness" Value="1" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
        <ControlTemplate x:Key="MenuItemControlTemplate2" TargetType="{x:Type MenuItem}">
            <Border x:Name="templateRoot"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    SnapsToDevicePixels="True">
                <Grid Margin="-1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"
                                          MinWidth="22"
                                          SharedSizeGroup="MenuItemIconColumnGroup" />
                        <ColumnDefinition Width="13" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="30" />
                        <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGTColumnGroup" />
                        <ColumnDefinition Width="20" />
                    </Grid.ColumnDefinitions>
                    <ContentPresenter x:Name="Icon"
                                      Width="16"
                                      Height="16"
                                      Margin="3"
                                      HorizontalAlignment="Center"
                                      VerticalAlignment="Center"
                                      Content="{TemplateBinding Icon}"
                                      ContentSource="Icon"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    <Border x:Name="GlyphPanel"
                            Width="22"
                            Height="22"
                            Margin="-1,0,0,0"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Background="#3D26A0DA"
                            BorderBrush="#FF26A0DA"
                            BorderThickness="1"
                            ClipToBounds="False"
                            Visibility="Hidden">
                        <Path x:Name="Glyph"
                              Width="10"
                              Height="11"
                              Data="F1M10,1.2L4.7,9.1 4.5,9.1 0,5.2 1.3,3.5 4.3,6.1 8.3,0 10,1.2z"
                              Fill="#FF212121"
                              FlowDirection="LeftToRight" />
                    </Border>
                    <ContentPresenter x:Name="menuHeaderContainer"
                                      Grid.Column="2"
                                      Margin="{TemplateBinding Padding}"
                                      HorizontalAlignment="Left"
                                      VerticalAlignment="Center"
                                      Content="{TemplateBinding Header}"
                                      ContentSource="Header"
                                      ContentStringFormat="{TemplateBinding HeaderStringFormat}"
                                      ContentTemplate="{TemplateBinding HeaderTemplate}"
                                      RecognizesAccessKey="True"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    <TextBlock x:Name="menuGestureText"
                               Grid.Column="4"
                               Margin="{TemplateBinding Padding}"
                               VerticalAlignment="Center"
                               Opacity="0.7"
                               Text="{TemplateBinding InputGestureText}" />
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="Icon" Value="{x:Null}">
                    <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
                </Trigger>
                <Trigger Property="IsChecked" Value="True">
                    <Setter TargetName="GlyphPanel" Property="Visibility" Value="Visible" />
                    <Setter TargetName="Icon" Property="Visibility" Value="Collapsed" />
                </Trigger>
                <Trigger Property="IsHighlighted" Value="True">
                    <Setter TargetName="templateRoot" Property="BorderBrush" Value="Orange" />
                    <Setter TargetName="templateRoot" Property="Background" Value="Yellow" />
                    <Setter TargetName="menuHeaderContainer" Property="TextBlock.Foreground" Value="Black" />
                </Trigger>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter TargetName="templateRoot" Property="TextElement.Foreground" Value="#FF707070" />
                    <Setter TargetName="Glyph" Property="Fill" Value="#FF707070" />
                </Trigger>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsHighlighted" Value="True" />
                        <Condition Property="IsEnabled" Value="False" />
                    </MultiTrigger.Conditions>
                    <Setter TargetName="templateRoot" Property="Background" Value="#0A000000" />
                    <Setter TargetName="templateRoot" Property="BorderBrush" Value="#21000000" />
                </MultiTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Menu Background="#2d2d30">
            <MenuItem Header="Tools" Template="{StaticResource MenuItemControlTemplate1}">
                <MenuItem Padding="0"
                          Background="#2d2d30"
                          Header="Test"
                          Template="{StaticResource MenuItemControlTemplate2}" />
            </MenuItem>
            <MenuItem Header="Whatever" Template="{StaticResource MenuItemControlTemplate1}">
                <MenuItem Padding="0"
                          Background="#2d2d30"
                          Header="Test"
                          Template="{StaticResource MenuItemControlTemplate2}" />
            </MenuItem>
        </Menu>
    </Grid>
</Window>

这与上面给出的样式和模板基本相同,在Rectangle MenuItemControlTemplate1ControlTemplate中添加了Grid,以便它和现有Border可以包含在弹出窗口中。 SubtractingConverter只是一个简单的IValueConverter,它从价值中减去ConverterParameter ......没什么特别的。我也继续把它扔进一个窗口进行测试。当我运行这个测试程序时,我现在得到了这个:

Resulting Tool menu popup

由于我没有你所有的风格,显然不是所有的颜色都是正确的,但你会注意到你现在关注的菜单似乎是一个连续的标签,就像在Visual Studio中一样。 / p>

现在这是一个完整的解决方案。有一些明显的细节,例如父项“工具”和“随意”菜单周围缺少边框,但更重要的是,您仍需要考虑Popup因放置显示器边缘时与显示器边缘重叠而改变其位置Rectangle

如果您将应用程序窗口移动到屏幕底部,Popup类将打开菜单实例上方“工具”菜单而不是下方,这显然会导致Rectangle被放错了地方。类似地,当窗口再次位于屏幕的右边缘时打开菜单将再次导致Rectangle由于弹出位置的改变而被错放。即使Visual Studio 2017也没有正确解释这种情况,如下所示:

Test Program with modifications

现在,在这种情况下,处理基本用例就足够了,太棒了!如果你想进一步处理重新定位,调整大小和/或隐藏/显示矩形以使其看起来完美无论用户打开菜单的奇怪位置,那么我真的看不到没有实际的干净利落C#代码。我怀疑这至少是以前显示的Visual Studio的Live Visual Tree中的VSMenuItem类在沼泽标准MenuItem类之上做的事情之一。实现该功能实际上超出了原始问题的范围,但希望至少可以清楚地说明他们是如何实现这一功能的。

答案 1 :(得分:-1)

将该菜单项的背景颜色设置为所需的颜色,将前景颜色设置为白色。