XAML 主题资源

XAML 中的主题资源是一组可应用不同值的资源,具体取决于哪个系统主题处于活动状态。 XAML 框架支持三种主题:“浅色”、“深色”和“高对比度”。

先决条件:本主题假设你已阅读 ResourceDictionary 和 XAML 资源引用

主题资源与静态资源

有两个 XAML 标记扩展,可从现有 XAML 资源字典引用 XAML 资源:{StaticResource} 标记扩展{ThemeResource} 标记扩展

将在应用加载时和随后每次在运行时更改主题时,发生 {ThemeResource} 标记扩展评估。 这通常是用户更改其设备设置或从更改其当前主题的应用内进行编程更改所引起的。

相比之下,仅在应用首次加载 XAML 时评估 {StaticResource} 标记扩展。 它未更新。 这类似于在应用启动时使用实际运行时值在 XAML 中进行查找和替换。

资源字典结构中的主题资源

每个主题资源都是 XAML 文件 themeresources.xaml 的一部分。 出于设计目的,themeresources.xaml 在 Windows 软件开发工具包 (SDK) 安装中的 \(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<SDK 版本>\Generic 文件夹中提供。 themeresources.xaml 中的资源字典还在同一目录中的 generic.xaml 中重现。

Windows 运行时不使用这些物理文件进行运行时查找。 这就是它们专门在 DesignTime 文件夹中,并且在默认情况下不复制到应用中的原因。 这些资源字典在内存中作为 Windows 运行时本身的一部分存在,并且你的应用的对主题资源(或系统资源)的 XAML 资源引用将在运行时在该处进行解析。

自定义主题资源指南

当定义和使用你自己的自定义主题资源时,请遵循这些指南:

注意

如果不遵循这些指南,可能会出现与应用中的主题相关的意外行为。 有关详细信息,请参阅主题资源疑难解答部分。

XAML 颜色渐变和依赖于主题的画笔

由“浅色”、“深色”和“高对比度”主题组合的颜色集构成了 XAML 中的 Windows 颜色渐变。 无论是想要修改系统主题,还是将主题应用到自己的 XAML 元素,了解如何构建颜色资源都非常重要。

有关如何在 Windows 应用中应用颜色的更多信息,请参阅 Windows 应用中的颜色

浅色和深色主题颜色

XAML 框架提供了一个已命名的 Color 资源集,其中包含的值专为“浅色”和“深色”主题而定制。 对于 WinUI 2,主题资源在公共主题资源 Xaml 文件中定义。 颜色名称非常适合其预期用途,每个颜色资源都有相应的 SolidColorBrush 资源。

提示

有关这些颜色的可视化概述,请参阅 WinUI 3 图库应用: 颜色

WinUI 3 库应用包括大多数 WinUI 3 控件、特性和功能的交互式示例。 通过 Microsoft Store 获取应用,或在 GitHub 上获取源代码

Windows 系统对比度主题颜色

除了 XAML 框架提供的资源集,还存在派生自 Windows 系统调色板的颜色值集。 这些颜色并不特定于 Windows 运行时或 Windows 应用。 然而,当使用“高度对比”主题运行系统(并且应用正在运行)时,许多 XAML Brush 资源都将使用这些颜色。 XAML 框架提供这些系统范围的颜色作为键控资源。 这些键遵循以下命名格式:SystemColor[name]Color

若要详细了解如何支持对比度主题,请参阅对比度主题

系统主题色

除了系统对比度主题颜色外,通过使用键 SystemAccentColor,系统主题色还会作为特定的颜色资源提供。 在运行时,该资源获取用户已在 Windows 个性化设置中指定为主题色的颜色。

注意

虽然可能会覆盖系统颜色资源,但这是遵循用户的颜色选择(特别是对于对比度主题设置而言)的最佳做法。

依赖于主题的画笔

使用前面部分中所示的颜色资源来设置系统主题资源字典中的 SolidColorBrush 资源的 Color 属性。 你可以使用画笔资源将颜色应用到 XAML 元素中。

让我们看一下如何在运行时确定此画笔的颜色值。 在“浅色”和“深色”资源字典中,定义此画笔,如下所示:

<SolidColorBrush x:Key="TextFillColorPrimaryBrush" Color="{StaticResource TextFillColorPrimary}"/>

在“高对比度”资源字典中,定义此画笔,如下所示:

<SolidColorBrush x:Key="TextFillColorPrimaryBrush" Color="{ThemeResource SystemColorWindowTextColor}"/>

当将此画笔应用到 XAML 元素时,其颜色在运行时由当前主题决定,如此表中所示。

主题 颜色资源 运行时值
轻型 TextFillColorPrimary #E4000000
TextFillColorPrimary #FFFFFFFF
高对比度 SystemColorWindowTextColor 文本设置中指定的颜色。

XAML 类型渐变

themeresources.xaml 文件将定义若干个资源,这些资源定义可应用到 UI 中的文本容器(特别是 TextBlockRichTextBlock)的 Style。 它们不是默认的隐式样式。 通过它们,你可以更轻松地创建匹配字体指南中记录的 Windows 类型渐变的 XAML UI 定义。

这些样式用于你要应用到整个文本容器的文本属性。 如果你仅想要将样式应用到该文本部分,请在容器中的文本元素上(例如 TextBlock.Inlines 中的 Run 上或 RichTextBlock.Blocks 中的 Paragraph 上)设置属性。

当应用于 TextBlock 时,这些样式如下所示:

text block styles

样式 重量 大小
Caption 常规 12
正文 常规 14
粗正文 半粗体 14
大正文 常规 18
副标题 半粗体 20
标题 半粗体 28
大标题 半粗体 40
显示 半粗体 68
<TextBlock Text="Caption" Style="{StaticResource CaptionTextBlockStyle}"/>
<TextBlock Text="Body" Style="{StaticResource BodyTextBlockStyle}"/>
<TextBlock Text="Body Strong" Style="{StaticResource BodyStrongTextBlockStyle}"/>
<TextBlock Text="Body Large" Style="{StaticResource BodyLargeTextBlockStyle}"/>
<TextBlock Text="Subtitle" Style="{StaticResource SubtitleTextBlockStyle}"/>
<TextBlock Text="Title" Style="{StaticResource TitleTextBlockStyle}"/>
<TextBlock Text="Title Large" Style="{StaticResource TitleLargeTextBlockStyle}"/>
<TextBlock Text="Display" Style="{StaticResource DisplayTextBlockStyle}"/>

有关如何在应用中使用 Windows 类型渐变的指南,请参阅 Windows 应用中的版式

有关 XAML 样式的详细信息,请参阅 GitHub 上的 WinUI:

提示

有关这些样式的可视化概述,请参阅 WinUI 3 图库应用: 版式

BaseRichTextBlockStyle

TargetTypeRichTextBlock

为所有其他 RichTextBlock 容器样式提供常用属性。

<!-- Usage -->
<RichTextBlock Style="{StaticResource BaseRichTextBlockStyle}">
    <Paragraph>Rich text.</Paragraph>
</RichTextBlock>

<!-- Style definition -->
<Style x:Key="BaseRichTextBlockStyle" TargetType="RichTextBlock">
    <Setter Property="FontFamily" Value="Segoe UI"/>
    <Setter Property="FontWeight" Value="SemiBold"/>
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="TextTrimming" Value="None"/>
    <Setter Property="TextWrapping" Value="Wrap"/>
    <Setter Property="LineStackingStrategy" Value="MaxHeight"/>
    <Setter Property="TextLineBounds" Value="Full"/>
    <Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
</Style>

BodyRichTextBlockStyle

<!-- Usage -->
<RichTextBlock Style="{StaticResource BodyRichTextBlockStyle}">
    <Paragraph>Rich text.</Paragraph>
</RichTextBlock>

<!-- Style definition -->
<Style x:Key="BodyRichTextBlockStyle" TargetType="RichTextBlock" BasedOn="{StaticResource BaseRichTextBlockStyle}">
    <Setter Property="FontWeight" Value="Normal"/>
</Style>

注意:RichTextBlock 样式不具有 TextBlock 包含的所有文本渐变样式,主要原因是适用于 RichTextBlock 的基于块的文档对象模型使在个别文本元素上设置属性更为简单。 同样,使用 XAML 内容属性设置 TextBlock.Text 将出现以下情况:没有要设置样式的文本元素,因此你必须设置容器样式。 对于"RichTextBlock",这不是问题,因为其文本内容始终位于特定的文本元素(例如 Paragraph)中,你可能会在该元素中为页面标头、页面子标头和类似文本渐变定义应用 XAML 样式。

其他命名样式

还存在一组额外的键控 Style 定义,可供你向 Button 应用不同于其默认隐式样式的样式。

TargetTypeButton

Style 提供的适用于 Button 的完整模板可能是适用于导航应用的导航后退按钮。 默认尺寸是 40 x 40 像素。 要定制样式,可明确设置 HeightWidthFontSize 和“Button”上的其他属性,也可以使用 BasedOn 创建一个派生的样式。

下面介绍向其应用了“NavigationBackButtonNormalStyle”资源的 Button

<Button Style="{StaticResource NavigationBackButtonNormalStyle}" />

它的外观如下所示:

A button styled as a back button

TargetTypeButton

Style 提供的适用于 Button 的完整模板可能是适用于导航应用的导航后退按钮。 它与“NavigationBackButtonNormalStyle”类似,但尺寸为 30 x 30 像素。

下面是一个应用了“NavigationBackButtonSmallStyle”资源的 Button

<Button Style="{StaticResource NavigationBackButtonSmallStyle}" />

主题资源疑难解答

如果你不遵循使用主题资源指南,你可能会看到与你的应用中的主题相关的意外行为。

例如,当你打开浅色主题的浮出控件时,深色主题的应用的某些部分也会更改,就好像在浅色主题中一样。 或者如果你导航至浅色主题的页面,然后再导航回来,此时原始的深色主题页面(或部分页面)看起来仍像在浅色主题中一样。

通常,在提供“默认”主题和“高对比度”主题以支持高对比度方案,然后在你的应用的不同部分中使用“浅色”和“深色”主题时,会发生这些类型的问题。

例如,考虑此主题字典定义:

<!-- DO NOT USE. THIS XAML DEMONSTRATES AN ERROR. -->
<ResourceDictionary>
  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Default">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource SystemColorButtonFaceColor}"/>
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

从直观上来说这看起来准确无误。 你想要在处于高对比度下时更改 myBrush 所指向的颜色,但在不处于高对比度下时,你依赖于 {ThemeResource} 标记扩展,以确保 myBrush 针对你的主题指向正确的颜色。 如果你的应用从未在可视化树内的元素上设置 FrameworkElement.RequestedTheme,这通常会按预期工作。 但是,在开始设置可视化树的不同部分的主题时,你的应用会立即出现问题。

与大多数其他 XAML 类型不同,产生该问题的原因是画笔是共享的资源。 如果你在 XAML 子树(具有可引用相同画笔资源的不同主题)中具有 2 个元素,则当框架遍历每个子树以更新其 {ThemeResource} 标记扩展表达式时,将在其他子树中反映对已共享画笔资源的更改,这不是你预期的结果。

若要解决此问题,除了“高对比度”以外,还使用“浅色”和“深色”主题的单独主题字典替换“默认”字典:

<!-- DO NOT USE. THIS XAML DEMONSTRATES AN ERROR. -->
<ResourceDictionary>
  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Light">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="Dark">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource SystemColorButtonFaceColor}"/>
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

但是,如果其中任何资源在继承属性(如 Foreground)中引用,则仍会发生这些问题。 你的自定义控件模板可能使用 {ThemeResource} 标记扩展指定元素的前景色,但当框架将继承的值传播到子元素时,它可以直接引用由 {ThemeResource} 标记扩展表达式解决的资源。 这将在框架处理主题更改时导致问题,因为它遍历控件的可视化树。 它将重新计算 {ThemeResource} 标记扩展表达式以获取新的画笔资源,但尚未将该引用传播到你的控件的子元素;这种情况将在以后发生(例如在下一个测量阶段发生)。

因此,在遍历控件可视化树以响应主题更改后,该框架将遍历子项并更新对其设置的任何 {ThemeResource} 标记扩展表达式,或在其属性上设置的对象。 在此情况下会发生问题;框架将遍历画笔资源,并且因为它使用 {ThemeResource} 标记扩展指定其颜色,因此它将重新计算。

此时,框架看起来已被你的主题字典污染,因为它现在具有的资源来自从另一个字典设置其颜色的一个字典。

若要解决此问题,请使用 {StaticResource} 标记扩展而不是 {ThemeResource} 标记扩展。 通过应用指南,主题字典如下所示:

<ResourceDictionary>
  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Light">
      <SolidColorBrush x:Key="myBrush" Color="{StaticResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="Dark">
      <SolidColorBrush x:Key="myBrush" Color="{StaticResource ControlFillColorDefault}"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
      <SolidColorBrush x:Key="myBrush" Color="{ThemeResource SystemColorButtonFaceColor}"/>
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

请注意,{ThemeResource} 标记扩展仍在“HighContrast”字典而不是 {StaticResource} 标记扩展中使用。 此情况属于在指南中的前面部分所给定的异常情况。 用于“高对比度”主题的大多数画笔值使用由系统全局控制的颜色选择,但会作为特别命名的资源暴露给 XAML(名称中带有“SystemColor”前缀)。 系统使用户可以通过“轻松访问中心”设置应该用于其对比度主题设置的特定颜色。 这些颜色选择将应用于特殊命名的资源中。 当 XAML 框架检测到画笔在系统级别上发生更改时,它也将使用相同的主题更改事件更新这些画笔。 这就是在此处使用 {ThemeResource} 标记扩展的原因。