XAML 样式

可以使用 XAML 框架通过多种方式自定义应用的外观。 通过样式可以设置控件属性,并重复使用这些设置,以便保持多个控件具有一致的外观。

WinUI 和样式

从 WinUI 2.2 开始,我们使用 Windows UI 库 (WinUI) 在 UI 组件中提供新的视觉样式更新。 如果你发现 UI 未更新到最新样式,请务必更新到最新的 WinUI NuGet 包。

从 WinUI 2.6 开始,我们为大多数控件提供新样式,并提供一个新的版本控制系统,让你可以根据需要恢复到以前的控件样式。 建议你使用新样式,因为它们更符合 Windows 的设计方向。 但是,如果你的方案不支持新样式,以前的版本仍然可用。

使用 WinUI 版本 2 时,可以通过设置包含在 Application.Resources 中的 XamlControlsResources 上的 ControlsResourcesVersion 属性来更改样式版本。 ControlsResourcesVersion 默认为枚举值 Version2

将此值设置为 Version1 会导致 XamlControlsResources 加载以前的样式版本,而不是最新 WinUI 版本使用的新样式。 不支持在运行时更改此属性,并且 Visual Studio 的热重载功能将不起作用;但是,重新生成应用程序后,你会看到控件样式发生了变化。

<Application.Resources>
    <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" 
                           ControlsResourcesVersion="Version1"/>
</Application.Resources>

样式基础知识

使用样式可将属性设置提取到可重复使用的资源中。 下面的示例显示了具有设置 BorderBrushBorderThicknessForeground 属性样式的 3 个按钮。 通过应用样式可以使控件具有相同外观,而无需单独为每个控件设置这些属性。

三个并排对齐的样式设置按钮的屏幕截图。

你可以定义在 XAML 中为控件嵌入的样式,或者作为可重复使用的资源。 在单个页面的 XAML 文件中、App.xaml 文件中,或者单独的资源词典 XAML 文件中定义资源。 可以在应用之间共享资源词典 XAML 文件,并且可以将多个资源词典合并到单个应用中。 定义资源的位置决定了该资源可以使用的范围。 页面级资源只在定义了它们的页面中可用。 如果在 App.xaml 和页面中同时定义了具有相同关键字的资源,则页面中的资源将覆盖 App.xaml 中的资源。 如果资源在单独的资源词典文件中定义,则它的范围由引用资源词典的位置确定。

Style 定义中,你需要一个 TargetType 属性和由一个或多个 Setter 元素组成的集合。 TargetType 属性是一个指定要应用样式的 FrameworkElement 类型的字符串。 TargetType 值必须指定由 Windows 运行时定义的派生的 FrameworkElement 类型或引用的程序集中提供的自定义类型。 如果你试图将某个样式应用到某控件,而此控件的类型与你试图应用的样式的 TargetType 属性不匹配,则会发生异常。

每个 Setter 元素都需要一个 Property 和一个 Value。 这些属性设置用于指示该设置将应用于哪个控件属性,以及为该属性设置的值。 你可以使用特性或属性元素语法设置 Setter.Value。 下面的 XAML 显示了应用于前面显示的按钮的样式。 在此 XAML 中,前两个 Setter 元素使用特性语法,但是最后一个 Setter(用于设置 BorderBrush 属性)使用属性元素语法。 该示例不使用 x:Key 特性这一特性,因此该样式已隐式应用到按钮。 隐式或显式应用样式在下一节进行介绍。

<Page.Resources>
    <Style TargetType="Button">
        <Setter Property="BorderThickness" Value="5" />
        <Setter Property="Foreground" Value="Black" />
        <Setter Property="BorderBrush" >
            <Setter.Value>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <GradientStop Color="Yellow" Offset="0.0" />
                    <GradientStop Color="Red" Offset="0.25" />
                    <GradientStop Color="Blue" Offset="0.75" />
                    <GradientStop Color="LimeGreen" Offset="1.0" />
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>

<StackPanel Orientation="Horizontal">
    <Button Content="Button"/>
    <Button Content="Button"/>
    <Button Content="Button"/>
</StackPanel>

应用隐式或显式样式

如果你将样式定义为资源,有两种方法可将其应用到控件:

如果样式包含 x:Key 特性,则只能通过将控件的 Style 属性设置为键控样式,从而将其应用到控件。 相反,没有 x:Key 特性的样式会自动应用到其目标类型的每个控件,这些控件没有显示样式设置。

下面两个按钮演示了隐式和显示样式。

隐式和显示样式按钮。

在本示例中,第一个样式具有 x:Key 特性,其目标类型为 Button。 第一个按钮的 Style 属性设置为此关键字,所以显示应用该样式。 第二个样式会隐式应用到第二个按钮,因为该按钮的目标类型为 Button,并且该样式没有 x:Key 特性。

<Page.Resources>
    <Style x:Key="PurpleStyle" TargetType="Button">
        <Setter Property="FontFamily" Value="Segoe UI"/>
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="Foreground" Value="Purple"/>
    </Style>

    <Style TargetType="Button">
        <Setter Property="FontFamily" Value="Segoe UI"/>
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="RenderTransform">
            <Setter.Value>
                <RotateTransform Angle="25"/>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush" Value="Green"/>
        <Setter Property="BorderThickness" Value="2"/>
        <Setter Property="Foreground" Value="Green"/>
    </Style>
</Page.Resources>

<Grid x:Name="LayoutRoot">
    <Button Content="Button" Style="{StaticResource PurpleStyle}"/>
    <Button Content="Button"/>
</Grid>

基于样式使用

为了使样式便于维护以及优化样式的重复使用,你可以创建从其他样式继承的样式。 使用 BasedOn 属性可创建继承的样式。 从其他样式继承的样式必须应用到同一类型的控件,或者从基本样式的目标类型派生出来的控件。 例如,如果基本样式的目标类型为 ContentControl,则基于此样式的样式可应用到 ContentControl 或从 ContentControl 派生的类型(如 ButtonScrollViewer)。 如果基于样式的值没有设置,则从基本样式继承。 要从基本样式更改值,基于样式会覆盖该值。 下一个示例演示了具有从同一基本样式继承的样式的 ButtonCheckBox

使用基于样式的样式设计按钮。

基本样式应用到 ContentControl,并设置 HeightWidth 属性。 基于此样式的样式应用到 CheckBoxButton,这些类型从 ContentControl 派生而来。 基于样式为 BorderBrushForeground 属性设置不同的颜色。 (通常不会在 CheckBox 周围设置边框。我们在此处执行此操作以显示 style 的效果。)

<Page.Resources>
    <Style x:Key="BasicStyle" TargetType="ContentControl">
        <Setter Property="Width" Value="130" />
        <Setter Property="Height" Value="30" />
    </Style>

    <Style x:Key="ButtonStyle" TargetType="Button"
           BasedOn="{StaticResource BasicStyle}">
        <Setter Property="BorderBrush" Value="Orange" />
        <Setter Property="BorderThickness" Value="2" />
        <Setter Property="Foreground" Value="Red" />
    </Style>

    <Style x:Key="CheckBoxStyle" TargetType="CheckBox"
           BasedOn="{StaticResource BasicStyle}">
        <Setter Property="BorderBrush" Value="Blue" />
        <Setter Property="BorderThickness" Value="2" />
        <Setter Property="Foreground" Value="Green" />
    </Style>
</Page.Resources>

<StackPanel>
    <Button Content="Button" Style="{StaticResource ButtonStyle}" Margin="0,10"/>
    <CheckBox Content="CheckBox" Style="{StaticResource CheckBoxStyle}"/>
</StackPanel>

使用工具轻松处理样式

将样式应用到控件的一种快捷方式是,在 Microsoft Visual Studio XAML 设计界面上,右键单击控件并选择“编辑样式” 或“编辑模板” (取决于右键单击的控件)。 然后,通过选择“应用资源” 来应用现有样式,或通过选择“创建空项” 来定义一个新样式。 如果创建空白样式,则可以使用相应的选项在该页面、App.xaml 文件或一个单独的资源字典中进行定义。

轻型样式设置

通常在应用级别或页面级别上替代系统画笔,在任意情况下,颜色替代都将影响引用该画笔的所有控件;在 XAML 中,许多控件可以引用相同的系统画笔。

两个按钮的屏幕截图:一个处于静止状态,另一个已应用轻型样式设置。

<Page.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <ResourceDictionary x:Key="Light">
                 <SolidColorBrush x:Key="ButtonBackground" Color="Transparent"/>
                 <SolidColorBrush x:Key="ButtonForeground" Color="MediumSlateBlue"/>
                 <SolidColorBrush x:Key="ButtonBorderBrush" Color="MediumSlateBlue"/>
            </ResourceDictionary>
        </ResourceDictionary.ThemeDictionaries>
    </ResourceDictionary>
</Page.Resources>

对于诸如 PointerOver(鼠标悬停在按钮上)、PointerPressed(按钮已调用)或 Disabled(按钮不可交互)的状态。 这些结尾追加到原始轻量级样式名称上: ButtonBackgroundPointerOverButtonForegroundPressedButtonBorderBrushDisabled 等。修改这些画笔可确保控件的颜色与应用的主题一致。

App.Resources 级别上放置这些画笔替代将更改整个应用(而不是单个页面)内的所有按钮。

每个控件的样式设置

在其他情况下,需要仅以特定方式更改一个页面上的单个控件,而不更改该控件的任何其他版本:

三个堆叠排列的样式设置按钮的屏幕截图。

<CheckBox Content="Normal CheckBox" Margin="5"/>
<CheckBox Content="Special CheckBox" Margin="5">
    <CheckBox.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Light">
                    <SolidColorBrush x:Key="CheckBoxForegroundUnchecked"
                        Color="Purple"/>
                    <SolidColorBrush x:Key="CheckBoxForegroundChecked"
                        Color="Purple"/>
                    <SolidColorBrush x:Key="CheckBoxCheckGlyphForegroundChecked"
                        Color="White"/>
                    <SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeChecked"  
                        Color="Purple"/>
                    <SolidColorBrush x:Key="CheckBoxCheckBackgroundFillChecked"
                        Color="Purple"/>
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </CheckBox.Resources>
</CheckBox>
<CheckBox Content="Normal CheckBox" Margin="5"/>

这仅影响存在该控件的页面上的一个“特殊复选框”。

自定义控件

当你生成自己的自定义控件,并且这些控件的视觉效果和/或功能可能与我们的内置控件一致时,请考虑使用隐式样式和轻型样式资源来定义自定义内容。 你可以直接使用资源,也可以为资源创建新别名。

直接使用控件资源

例如,如果你正在编写一个看起来像 Button 的控件,可以让该控件直接引用按钮资源,如下所示:

<Style TargetType="local:MyCustomControl">
  <Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
  <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
</Style>

为控件资源创建新别名

或者,如果你想创建自己的资源,则应将默认轻型样式资源的别名设置为这些自定义名称。

例如,自定义控件的样式可能具有特殊的资源定义:

<Style TargetType="local:MyCustomControl">
  <Setter Property="Background" Value="{ThemeResource MyCustomControlBackground}" />
  <Setter Property="BorderBrush" Value="{ThemeResource MyCustomControlBorderBrush}"/>
</Style>

在资源字典或主定义中,可以将轻型样式资源挂钩到自定义资源:

<ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Default">
        <StaticResource x:Key="MyCustomControlBackground" ResourceKey="ButtonBackground" />
        <StaticResource x:Key="MyCustomControlBorderBrush" ResourceKey="ButtonBorderBrush" />
    </ResourceDictionary>        
    <ResourceDictionary x:Key="Light">
        <StaticResource x:Key="MyCustomControlBackground" ResourceKey="ButtonBackground" />
        <StaticResource x:Key="MyCustomControlBorderBrush" ResourceKey="ButtonBorderBrush" />
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
        <StaticResource x:Key="MyCustomControlBackground" ResourceKey="ButtonBackground" />
        <StaticResource x:Key="MyCustomControlBorderBrush" ResourceKey="ButtonBorderBrush" />
    </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>

此代码要求重复使用 ThemeDictionary 三次,以正确处理三个不同的主题更改(DefaultLightHighContrast)。

注意

如果为轻型样式资源分配新别名,并重新定义轻型样式资源,则在资源查找顺序不正确的情况下,可能不会应用你的自定义资源。 例如,如果在找到 MyCustomControlBackground 之前搜索的位置中替代 ButtonBackground,替代将丢失。

避免重新设计控件样式

Windows UI 库 2.2 或更高版本包括用于 WinUI 和系统控件的新样式和模板。

及时获取最新视觉样式的最佳方法是使用最新的 WinUI 2 包并避免使用自定义样式和模板(也称为重新创建模板)。 如需跨应用中的控件一致地应用某组值,样式仍不失为一种便捷的方式。 执行此操作时,务必使用我们的最新样式。

对于使用 WinUI 样式的系统控件(Windows.UI.Xaml.Controls 命名空间),请设置 BasedOn="{StaticResource Default<ControlName>Style}",其中 <ControlName> 是控件的名称。 例如:

<Style TargetType="TextBox" BasedOn="{StaticResource DefaultTextBoxStyle}">
    <Setter Property="Foreground" Value="Blue"/>
</Style>

对于 WinUI 2 控件(Microsoft.UI.Xaml.Controls 命名空间),默认样式在元数据中定义,因此省略 BasedOn

派生控件

如果从现有 XAML 控件派生自定义控件,默认情况下它不会获得 WinUI 2 样式。 若要应用 WinUI 2 样式,请执行以下操作:

  • 创建新的 Style,并将其 TargetType 设置为自定义控件。
  • 使 Style 基于从其派生的控件的默认样式。

一种常见的情况是从 ContentDialog 派生新控件。 本示例演示如何创建将 DefaultContentDialogStyle 应用于自定义对话框的新 Style。

<ContentDialog
    x:Class="ExampleApp.SignInContentDialog"
    ... >

    <ContentDialog.Resources>
        <Style TargetType="local:SignInContentDialog" BasedOn="{StaticResource DefaultContentDialogStyle}"/>
        ...
    </ContentDialog.Resources> 
    <!-- CONTENT -->
</ContentDialog>        

template 属性

样式 setter 可用于 ControlTemplate 属性,实际上,此样式构成了多数典型的 XAML 样式和应用的 XAML 资源。 此内容将在主题控件模板中更详细地讨论。