本章导读:一个优秀的应用,不仅要有强大的功能,还要有一致、精美的外观。想象一下,如果你的应用有 50 个按钮,每个按钮的颜色、圆角、字体都需要单独设置——那将是维护者的噩梦。更糟糕的是,当设计团队决定把品牌色从蓝色改为紫色时,你需要找到并修改所有 50 处代码。本章将揭示 XAML 资源系统的奥秘——它是如何让你的代码"一次定义,处处使用"的,是如何让你的应用轻松支持深色模式的,是如何让你彻底重塑控件外观的。
在开始之前,让我们先理解一个核心问题:为什么需要资源系统?
假设你正在开发一个企业应用,设计规范规定了以下品牌色:
<Button Background="#0078D4" Foreground="White" Content="保存" />
<Button Background="#0078D4" Foreground="White" Content="取消" />
<TextBlock Foreground="#666666" Text="提示信息" />
<Border Background="#FFFFFF" BorderBrush="#0078D4" />
如果设计师决定把主色从蓝色改为绿色,你需要找到所有写死 #0078D4 的地方——可能有几十处,分布在十几个文件中。漏掉一个?应用就会出现颜色不一致的 bug。
资源系统将这个问题优雅地解决了。你在资源字典中定义一次,然后在需要的地方引用:
<Page.Resources>
<!-- 定义颜色资源 -->
<Color x:Key="BrandPrimaryColor">#0078D4</Color>
<Color x:Key="BrandSecondaryColor">#666666</Color>
<Color x:Key="AccentColor">#FF8C00</Color>
<!-- 定义画刷资源(更常用) -->
<SolidColorBrush x:Key="BrandPrimaryBrush" Color="{StaticResource BrandPrimaryColor}" />
<SolidColorBrush x:Key="BrandSecondaryBrush" Color="{StaticResource BrandSecondaryColor}" />
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />
<!-- 定义数值资源 -->
<x:Double x:Key="DefaultFontSize">14</x:Double>
<x:Double x:Key="HeaderFontSize">24</x:Double>
<!-- 定义厚度资源 -->
<Thickness x:Key="DefaultPadding">16</Thickness>
<Thickness x:Key="SmallMargin">8</Thickness>
</Page.Resources>
<!-- 使用资源 -->
<Button Background="{StaticResource BrandPrimaryBrush}"
Foreground="White"
Content="保存" />
<TextBlock Foreground="{StaticResource BrandSecondaryBrush}"
FontSize="{StaticResource DefaultFontSize}"
Text="提示信息" />
第一性原理:资源查找是如何工作的? XAML 有一个资源查找链机制。当你在某个元素上使用{StaticResource Key}时,系统会按照以下顺序查找:这意味着你可以在局部覆盖全局资源——子元素的同名资源会"遮蔽"父元素的资源。
- 当前元素的 Resources
- 父元素的 Resources
- 父父元素的 Resources...(向上递归)
- 应用级别的 Resources(App.xaml)
- 系统内置资源
XAML 提供了两种资源引用方式,它们的行为有重要区别。
StaticResource 在 XAML 解析时一次性查找并应用资源。之后,即使资源字典中的值发生变化,引用它的元素也不会更新。
ThemeResource 是动态的——它会响应主题变化。当用户从浅色模式切换到深色模式时,所有使用 ThemeResource 的属性都会自动重新查找并更新。
<Page.Resources>
<!-- 定义主题感知的颜色 -->
<SolidColorBrush x:Key="PageBackgroundBrush" Color="White" />
</Page.Resources>
<!-- 使用 StaticResource:主题切换时不会更新 -->
<Grid Background="{StaticResource PageBackgroundBrush}">
<!-- 使用 ThemeResource:主题切换时自动更新 -->
<Grid Background="{ThemeResource PageBackgroundBrush}">
最佳实践:何时使用哪种? 强烈建议:在 Uno Platform 开发中,所有涉及颜色的资源都使用ThemeResource。这是因为现代应用几乎都需要支持深色模式,使用ThemeResource可以让你"免费"获得主题切换支持。只有那些确定不会随主题变化的资源(如固定的数值、字符串)才使用StaticResource。
资源可以定义在不同的层级,每个层级有不同的作用范围。
元素级资源:定义在单个控件的 Resources 属性中,只在该控件及其子控件中可用。
<Button>
<Button.Resources>
<SolidColorBrush x:Key="LocalBrush" Color="Red" />
</Button.Resources>
</Button>
页面级资源:定义在 Page.Resources 中,在整个页面中可用。
<Page>
<Page.Resources>
<SolidColorBrush x:Key="PageBrush" Color="Blue" />
</Page.Resources>
</Page>
应用级资源:定义在 App.xaml 中,在整个应用中可用。
<Application>
<Application.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="AppBrush" Color="Green" />
</ResourceDictionary>
</Application.Resources>
</Application>
独立的资源字典文件:对于大型应用,可以将资源组织在独立的 XAML 文件中,然后在 App.xaml 中合并引用。
<!-- Themes/Colors.xaml -->
<ResourceDictionary>
<SolidColorBrush x:Key="PrimaryBrush" Color="#0078D4" />
</ResourceDictionary>
<!-- Themes/Styles.xaml -->
<ResourceDictionary>
<Style x:Key="HeaderStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="24" />
</Style>
</ResourceDictionary>
<!-- App.xaml -->
<Application>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Colors.xaml" />
<ResourceDictionary Source="Themes/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
资源解决了"一处定义,处处使用"的问题,但想象这样的场景:你的应用中有 100 个 TextBlock,每个都需要设置 FontSize、FontWeight、Foreground、Margin 四个属性。即使用了资源,你仍然需要写 400 个属性设置。
样式(Style)将一组相关的属性设置封装成一个单元,一键应用。
<Page.Resources>
<!-- 定义命名样式 -->
<Style x:Key="HeaderTextBlock" TargetType="TextBlock">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="{ThemeResource TextPrimaryBrush}" />
<Setter Property="Margin" Value="0,0,0,16" />
</Style>
<Style x:Key="BodyTextBlock" TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Foreground" Value="{ThemeResource TextSecondaryBrush}" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="LineHeight" Value="20" />
</Style>
</Page.Resources>
<!-- 使用样式 -->
<TextBlock Style="{StaticResource HeaderTextBlock}" Text="章节标题" />
<TextBlock Style="{StaticResource BodyTextBlock}" Text="正文内容..." />
每个 Style 必须指定 TargetType,表示该样式可以应用到哪种类型的控件。每个 Setter 指定一个属性名和值。
样式支持继承,你可以基于一个现有样式创建更具体的样式:
<Page.Resources>
<!-- 基础文本样式 -->
<Style x:Key="BaseTextBlock" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
<!-- 继承基础样式的标题样式 -->
<Style x:Key="HeaderTextBlock" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlock}">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<!-- 继承基础样式的副标题样式 -->
<Style x:Key="SubHeaderTextBlock" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlock}">
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
</Page.Resources>
子样式会继承父样式的所有设置,并可以覆盖或添加新的设置。这种模式非常适合建立一套有层次感的样式体系。
如果样式没有设置 x:Key,它会自动应用到作用范围内所有匹配 TargetType 的控件——这就是隐式样式(Implicit Style)。
<Page.Resources>
<!-- 隐式样式:没有 x:Key -->
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily" Value="Segoe UI" />
</Style>
<Style TargetType="Button">
<Setter Property="Background" Value="{ThemeResource AccentButtonBackground}" />
<Setter Property="CornerRadius" Value="4" />
</Style>
</Page.Resources>
<!-- 这些控件会自动应用上面的隐式样式 -->
<TextBlock Text="自动应用隐式样式" />
<Button Content="我也是" />
费曼技巧提问:隐式样式和命名样式如何选择? 想象你在装修房子。隐式样式像是"默认装修"——所有房间都自动获得地板和白色墙壁。命名样式像是"特色主题"——你可以选择"现代风格"或"古典风格"应用到特定房间。通常的做法是:用隐式样式设置全局默认值,用命名样式为特殊情况提供变体。
样式只能修改控件的现有属性,但如果你想彻底改变控件的结构呢?比如让按钮变成圆形,让文本框有发光边框,让进度条变成环形?这就需要控件模板(ControlTemplate)。
每个控件都由两部分组成:逻辑(Logic)和视觉树(Visual Tree)。逻辑定义控件的行为(如按钮的 Click 事件),视觉树定义控件的外观。控件模板就是视觉树的定义。
<Style x:Key="RoundedButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<!-- 按钮的背景 -->
<Ellipse x:Name="BackgroundEllipse"
Fill="{TemplateBinding Background}" />
<!-- 按钮的内容 -->
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
Foreground="{TemplateBinding Foreground}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 使用圆形按钮 -->
<Button Style="{StaticResource RoundedButtonStyle}"
Background="Blue" Foreground="White"
Width="60" Height="60"
Content="+" />
TemplateBinding 是一个特殊的绑定,它将模板内部的元素属性连接到外部控件的属性。
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8">
<ContentPresenter Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
Foreground="{TemplateBinding Foreground}" />
</Border>
</ControlTemplate>
技术细节:TemplateBinding vs BindingTemplateBinding是{Binding RelativeSource={RelativeSource TemplatedParent}}的简写,它在模板内部使用更简洁。但TemplateBinding不支持转换器和复杂数据源,这时需要使用完整的Binding语法。
控件在不同状态下需要不同的视觉表现。按钮有 Normal、PointerOver、Pressed、Disabled 等状态;TextBox 有 Focused、Unfocused 状态。控件模板通过 VisualStateManager 定义这些状态的视觉表现。
<Style x:Key="CustomButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<!-- 正常状态 -->
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Target="BackgroundBorder.Background"
Value="{ThemeResource ButtonBackground}" />
</VisualState.Setters>
</VisualState>
<!-- 鼠标悬停状态 -->
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="BackgroundBorder.Background"
Value="{ThemeResource ButtonBackgroundPointerOver}" />
</VisualState.Setters>
</VisualState>
<!-- 按下状态 -->
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="BackgroundBorder.Background"
Value="{ThemeResource ButtonBackgroundPressed}" />
<Setter Target="ContentPresenter.Opacity" Value="0.8" />
</VisualState.Setters>
</VisualState>
<!-- 禁用状态 -->
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="BackgroundBorder.Background"
Value="{ThemeResource ButtonBackgroundDisabled}" />
<Setter Target="ContentPresenter.Foreground"
Value="{ThemeResource ButtonForegroundDisabled}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="BackgroundBorder"
Background="{ThemeResource ButtonBackground}"
CornerRadius="4">
<ContentPresenter x:Name="ContentPresenter"
Content="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
设计提示:从哪里开始自定义模板? 不要从零开始编写控件模板——这很容易遗漏重要的视觉状态。正确的方法是:先找到控件的默认模板(使用 Visual Studio 的"编辑模板"功能),然后在此基础上修改。这样你不会遗漏任何视觉状态,也能保持与系统设计的一致性。
现代应用几乎都需要支持深色模式(Dark Mode)。XAML 的主题字典机制让这变得出奇简单。
资源字典可以包含一个特殊的 ThemeDictionaries 节,其中为不同主题定义不同的资源:
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<!-- 浅色主题 -->
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="ApplicationPageBackgroundBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#1A1A1A" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#666666" />
<SolidColorBrush x:Key="CardBackgroundBrush" Color="#F5F5F5" />
<SolidColorBrush x:Key="DividerBrush" Color="#E0E0E0" />
</ResourceDictionary>
<!-- 深色主题 -->
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="ApplicationPageBackgroundBrush" Color="#1A1A1A" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#B0B0B0" />
<SolidColorBrush x:Key="CardBackgroundBrush" Color="#2D2D2D" />
<SolidColorBrush x:Key="DividerBrush" Color="#404040" />
</ResourceDictionary>
<!-- 高对比度主题 -->
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="ApplicationPageBackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
<!-- ... -->
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
然后使用 ThemeResource 引用这些资源:
<Page Background="{ThemeResource ApplicationPageBackgroundBrush}">
<StackPanel>
<TextBlock Text="主标题"
Foreground="{ThemeResource TextPrimaryBrush}" />
<TextBlock Text="副标题"
Foreground="{ThemeResource TextSecondaryBrush}" />
<Border Background="{ThemeResource CardBackgroundBrush}"
Padding="16">
<TextBlock Text="卡片内容" />
</Border>
</StackPanel>
</Page>
当用户切换系统主题时,所有使用 ThemeResource 的属性会自动更新为对应主题的颜色。
除了跟随系统主题,你还可以在应用内手动切换主题:
// 切换到深色主题
if (App.Current.Resources.TryGetValue("Dark", out var darkTheme))
{
App.Current.Resources.MergedDictionaries.Clear();
App.Current.Resources.MergedDictionaries.Add((ResourceDictionary)darkTheme);
}
// 或者使用 WinUI 的方式
App.Current.RequestedTheme = ApplicationTheme.Dark;
最佳实践:设计主题颜色时的注意事项 深色模式不是简单的"颜色反转"。考虑这些因素:
- 深色背景上的文字应该减少对比度(纯白 #FFFFFF 可能太刺眼)
- 阴影效果在深色背景上不可见,考虑使用边框或高光代替
- 图片和图标可能需要不同的处理
- 品牌色在深色背景上可能需要调整饱和度
数据模板(DataTemplate)定义了数据对象如何在 UI 中呈现。它与控件模板不同:控件模板定义控件的外观,数据模板定义数据的外观。
<Page.Resources>
<DataTemplate x:Key="UserTemplate" x:DataType="model:User">
<Grid Padding="8" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<PersonPicture Grid.Column="0"
DisplayName="{x:Bind Name}"
Width="48" Height="48" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{x:Bind Name}"
FontWeight="Bold" />
<TextBlock Text="{x:Bind Email}"
Foreground="Gray"
FontSize="12" />
</StackPanel>
</Grid>
</DataTemplate>
</Page.Resources>
<!-- 在 ListView 中使用数据模板 -->
<ListView ItemsSource="{x:Bind ViewModel.Users}"
ItemTemplate="{StaticResource UserTemplate}" />
<!-- 或内联定义 -->
<ListView ItemsSource="{x:Bind ViewModel.Users}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:User">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
有时不同的数据需要不同的模板。数据模板选择器(DataTemplateSelector)根据数据对象返回合适的模板。
public class MessageTemplateSelector : DataTemplateSelector
{
public DataTemplate SentMessageTemplate { get; set; }
public DataTemplate ReceivedMessageTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is Message message)
{
return message.IsSent ? SentMessageTemplate : ReceivedMessageTemplate;
}
return base.SelectTemplateCore(item);
}
}
<Page.Resources>
<DataTemplate x:Key="SentMessageTemplate" x:DataType="model:Message">
<Border Background="Blue" CornerRadius="8" Padding="12" Margin="40,4,4,4">
<TextBlock Text="{x:Bind Content}" Foreground="White" />
</Border>
</DataTemplate>
<DataTemplate x:Key="ReceivedMessageTemplate" x:DataType="model:Message">
<Border Background="LightGray" CornerRadius="8" Padding="12" Margin="4,4,40,4">
<TextBlock Text="{x:Bind Content}" />
</Border>
</DataTemplate>
<local:MessageTemplateSelector x:Key="MessageSelector"
SentMessageTemplate="{StaticResource SentMessageTemplate}"
ReceivedMessageTemplate="{StaticResource ReceivedMessageTemplate}" />
</Page.Resources>
<ListView ItemsSource="{x:Bind ViewModel.Messages}"
ItemTemplateSelector="{StaticResource MessageSelector}" />
让我们将本章学到的知识整合起来,构建一个小型但完整的设计系统。
MyApp/
├── Themes/
│ ├── Colors.xaml # 颜色定义
│ ├── Typography.xaml # 字体样式
│ ├── Buttons.xaml # 按钮样式
│ ├── TextBlocks.xaml # 文本块样式
│ └── Generic.xaml # 控件模板
├── Converters/ # 值转换器
└── App.xaml
Colors.xaml:
<ResourceDictionary>
<!-- 品牌色 -->
<Color x:Key="BrandPrimary">#0078D4</Color>
<Color x:Key="BrandSecondary">#106EBE</Color>
<Color x:Key="Accent">#FF8C00</Color>
<!-- 功能色 -->
<Color x:Key="Success">#107C10</Color>
<Color x:Key="Warning">#FFB900</Color>
<Color x:Key="Error">#D13438</Color>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="BackgroundBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="SurfaceBrush" Color="#F5F5F5" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#1A1A1A" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#666666" />
<SolidColorBrush x:Key="DividerBrush" Color="#E0E0E0" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="BackgroundBrush" Color="#1A1A1A" />
<SolidColorBrush x:Key="SurfaceBrush" Color="#2D2D2D" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#B0B0B0" />
<SolidColorBrush x:Key="DividerBrush" Color="#404040" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
Typography.xaml:
<ResourceDictionary>
<!-- 字体大小 -->
<x:Double x:Key="FontSizeSmall">12</x:Double>
<x:Double x:Key="FontSizeNormal">14</x:Double>
<x:Double x:Key="FontSizeMedium">16</x:Double>
<x:Double x:Key="FontSizeLarge">20</x:Double>
<x:Double x:Key="FontSizeXLarge">24</x:Double>
<x:Double x:Key="FontSizeHero">36</x:Double>
<!-- 文本样式 -->
<Style x:Key="HeroText" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource FontSizeHero}" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="{ThemeResource TextPrimaryBrush}" />
</Style>
<Style x:Key="TitleText" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource FontSizeXLarge}" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{ThemeResource TextPrimaryBrush}" />
</Style>
<Style x:Key="SubtitleText" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource FontSizeLarge}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Foreground" Value="{ThemeResource TextSecondaryBrush}" />
</Style>
<Style x:Key="BodyText" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource FontSizeNormal}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Foreground" Value="{ThemeResource TextPrimaryBrush}" />
</Style>
</ResourceDictionary>
<Application x:Class="MyApp.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="Themes/Colors.xaml" />
<ResourceDictionary Source="Themes/Typography.xaml" />
<ResourceDictionary Source="Themes/Buttons.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
资源、样式和模板是构建高质量、可维护 UI 的基石。通过资源系统,我们实现了视觉属性的集中管理和复用;通过样式,我们封装了属性组并建立了层次化的样式体系;通过控件模板,我们获得了彻底重塑控件外观的能力。
主题字典让深色模式支持变得简单——只需为浅色和深色主题定义不同的颜色值,系统会自动处理切换。数据模板则让数据呈现变得灵活,不同的数据类型可以有不同的视觉表现。
这些概念结合起来,就是设计系统(Design System)的基础。一个良好的设计系统不仅能提高开发效率,还能确保应用视觉的一致性,让用户获得更好的体验。
在下一章中,我们将从静态走向动态——学习 Uno 导航系统,掌握如何在多个页面之间流转,如何构建流畅的用户旅程。
动手实验:
- 创建一个资源字典,定义你的个人品牌色系(至少包含 5 种颜色),然后在页面中应用这些颜色。
- 为 TextBlock 创建一套完整的样式体系:Hero、Title、Subtitle、Body、Caption,并使用 BasedOn 实现继承。
- 自定义一个按钮模板,使其在鼠标悬停时放大 1.05 倍,按下时缩小到 0.95 倍(使用 VisualState 和 RenderTransform)。
- 实现完整的深色模式支持:创建主题字典,定义浅色和深色两套颜色,使用 ThemeResource 引用,然后测试主题切换。
还没有人回复