Loading...
正在加载...
请稍候

第七章:资源字典、样式与模板

✨步子哥 (steper) 2026年02月17日 05:28
# 第七章:资源字典、样式与模板 > **本章导读**:一个优秀的应用,不仅要有强大的功能,还要有一致、精美的外观。想象一下,如果你的应用有 50 个按钮,每个按钮的颜色、圆角、字体都需要单独设置——那将是维护者的噩梦。更糟糕的是,当设计团队决定把品牌色从蓝色改为紫色时,你需要找到并修改所有 50 处代码。本章将揭示 XAML 资源系统的奥秘——它是如何让你的代码"一次定义,处处使用"的,是如何让你的应用轻松支持深色模式的,是如何让你彻底重塑控件外观的。 --- ## 💎 7.1 XAML 资源系统:视觉属性的统一管理 在开始之前,让我们先理解一个核心问题:为什么需要资源系统? 假设你正在开发一个企业应用,设计规范规定了以下品牌色: - 主色:蓝色 (#0078D4) - 次要色:灰色 (#666666) - 强调色:橙色 (#FF8C00) - 背景色:白色 (#FFFFFF) 没有资源系统,你的代码可能是这样的: ```xml <Button Background="#0078D4" Foreground="White" Content="保存" /> <Button Background="#0078D4" Foreground="White" Content="取消" /> <TextBlock Foreground="#666666" Text="提示信息" /> <Border Background="#FFFFFF" BorderBrush="#0078D4" /> ``` 如果设计师决定把主色从蓝色改为绿色,你需要找到所有写死 `#0078D4` 的地方——可能有几十处,分布在十几个文件中。漏掉一个?应用就会出现颜色不一致的 bug。 ### 📦 7.1.1 资源的定义与引用 资源系统将这个问题优雅地解决了。你在资源字典中定义一次,然后在需要的地方引用: ```xml <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}` 时,系统会按照以下顺序查找: > 1. 当前元素的 Resources > 2. 父元素的 Resources > 3. 父父元素的 Resources...(向上递归) > 4. 应用级别的 Resources(App.xaml) > 5. 系统内置资源 > > 这意味着你可以在局部覆盖全局资源——子元素的同名资源会"遮蔽"父元素的资源。 ### 🔄 7.1.2 StaticResource 与 ThemeResource XAML 提供了两种资源引用方式,它们的行为有重要区别。 **StaticResource** 在 XAML 解析时一次性查找并应用资源。之后,即使资源字典中的值发生变化,引用它的元素也不会更新。 **ThemeResource** 是动态的——它会响应主题变化。当用户从浅色模式切换到深色模式时,所有使用 `ThemeResource` 的属性都会自动重新查找并更新。 ```xml <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`。 ### 📚 7.1.3 资源字典的层级与范围 资源可以定义在不同的层级,每个层级有不同的作用范围。 **元素级资源**:定义在单个控件的 `Resources` 属性中,只在该控件及其子控件中可用。 ```xml <Button> <Button.Resources> <SolidColorBrush x:Key="LocalBrush" Color="Red" /> </Button.Resources> </Button> ``` **页面级资源**:定义在 `Page.Resources` 中,在整个页面中可用。 ```xml <Page> <Page.Resources> <SolidColorBrush x:Key="PageBrush" Color="Blue" /> </Page.Resources> </Page> ``` **应用级资源**:定义在 `App.xaml` 中,在整个应用中可用。 ```xml <Application> <Application.Resources> <ResourceDictionary> <SolidColorBrush x:Key="AppBrush" Color="Green" /> </ResourceDictionary> </Application.Resources> </Application> ``` **独立的资源字典文件**:对于大型应用,可以将资源组织在独立的 XAML 文件中,然后在 App.xaml 中合并引用。 ```xml <!-- 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> ``` --- ## 🎨 7.2 样式:封装视觉属性组 资源解决了"一处定义,处处使用"的问题,但想象这样的场景:你的应用中有 100 个 TextBlock,每个都需要设置 `FontSize`、`FontWeight`、`Foreground`、`Margin` 四个属性。即使用了资源,你仍然需要写 400 个属性设置。 **样式**(Style)将一组相关的属性设置封装成一个单元,一键应用。 ### 📝 7.2.1 样式的定义与使用 ```xml <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` 指定一个属性名和值。 ### 🌳 7.2.2 样式继承:BasedOn 样式支持继承,你可以基于一个现有样式创建更具体的样式: ```xml <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> ``` 子样式会继承父样式的所有设置,并可以覆盖或添加新的设置。这种模式非常适合建立一套有层次感的样式体系。 ### 🔘 7.2.3 隐式样式:自动应用 如果样式没有设置 `x:Key`,它会自动应用到作用范围内所有匹配 `TargetType` 的控件——这就是**隐式样式**(Implicit Style)。 ```xml <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="我也是" /> ``` > **费曼技巧提问**:隐式样式和命名样式如何选择? > > 想象你在装修房子。隐式样式像是"默认装修"——所有房间都自动获得地板和白色墙壁。命名样式像是"特色主题"——你可以选择"现代风格"或"古典风格"应用到特定房间。通常的做法是:用隐式样式设置全局默认值,用命名样式为特殊情况提供变体。 --- ## 🎭 7.3 控件模板:重塑控件的本质 样式只能修改控件的现有属性,但如果你想彻底改变控件的结构呢?比如让按钮变成圆形,让文本框有发光边框,让进度条变成环形?这就需要**控件模板**(ControlTemplate)。 ### 🔧 7.3.1 理解控件模板 每个控件都由两部分组成:**逻辑**(Logic)和**视觉树**(Visual Tree)。逻辑定义控件的行为(如按钮的 Click 事件),视觉树定义控件的外观。控件模板就是视觉树的定义。 ```xml <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="+" /> ``` ### 🔗 7.3.2 TemplateBinding:连接模板与控件 `TemplateBinding` 是一个特殊的绑定,它将模板内部的元素属性连接到外部控件的属性。 ```xml <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 Binding > > `TemplateBinding` 是 `{Binding RelativeSource={RelativeSource TemplatedParent}}` 的简写,它在模板内部使用更简洁。但 `TemplateBinding` 不支持转换器和复杂数据源,这时需要使用完整的 `Binding` 语法。 ### 🎬 7.3.3 VisualState:模板中的状态管理 控件在不同状态下需要不同的视觉表现。按钮有 Normal、PointerOver、Pressed、Disabled 等状态;TextBox 有 Focused、Unfocused 状态。控件模板通过 `VisualStateManager` 定义这些状态的视觉表现。 ```xml <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 的"编辑模板"功能),然后在此基础上修改。这样你不会遗漏任何视觉状态,也能保持与系统设计的一致性。 --- ## 🌗 7.4 主题字典:深色模式支持 现代应用几乎都需要支持深色模式(Dark Mode)。XAML 的主题字典机制让这变得出奇简单。 ### 📖 7.4.1 ThemeDictionaries 机制 资源字典可以包含一个特殊的 `ThemeDictionaries` 节,其中为不同主题定义不同的资源: ```xml <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` 引用这些资源: ```xml <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` 的属性会自动更新为对应主题的颜色。 ### 🎚️ 7.4.2 在应用内切换主题 除了跟随系统主题,你还可以在应用内手动切换主题: ```csharp // 切换到深色主题 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 可能太刺眼) > - 阴影效果在深色背景上不可见,考虑使用边框或高光代替 > - 图片和图标可能需要不同的处理 > - 品牌色在深色背景上可能需要调整饱和度 --- ## 🏗️ 7.5 数据模板:数据的视觉呈现 **数据模板**(DataTemplate)定义了数据对象如何在 UI 中呈现。它与控件模板不同:控件模板定义控件的外观,数据模板定义数据的外观。 ### 📋 7.5.1 基础数据模板 ```xml <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> ``` ### 🔄 7.5.2 数据模板选择器 有时不同的数据需要不同的模板。**数据模板选择器**(DataTemplateSelector)根据数据对象返回合适的模板。 ```csharp 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); } } ``` ```xml <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}" /> ``` --- ## 🎯 7.6 实战:构建一致的设计系统 让我们将本章学到的知识整合起来,构建一个小型但完整的设计系统。 ### 📁 7.6.1 组织资源文件结构 ``` MyApp/ ├── Themes/ │ ├── Colors.xaml # 颜色定义 │ ├── Typography.xaml # 字体样式 │ ├── Buttons.xaml # 按钮样式 │ ├── TextBlocks.xaml # 文本块样式 │ └── Generic.xaml # 控件模板 ├── Converters/ # 值转换器 └── App.xaml ``` **Colors.xaml**: ```xml <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**: ```xml <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> ``` ### 📦 7.6.2 合并到 App.xaml ```xml <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 导航系统**,掌握如何在多个页面之间流转,如何构建流畅的用户旅程。 --- > **动手实验**: > 1. 创建一个资源字典,定义你的个人品牌色系(至少包含 5 种颜色),然后在页面中应用这些颜色。 > 2. 为 TextBlock 创建一套完整的样式体系:Hero、Title、Subtitle、Body、Caption,并使用 BasedOn 实现继承。 > 3. 自定义一个按钮模板,使其在鼠标悬停时放大 1.05 倍,按下时缩小到 0.95 倍(使用 VisualState 和 RenderTransform)。 > 4. 实现完整的深色模式支持:创建主题字典,定义浅色和深色两套颜色,使用 ThemeResource 引用,然后测试主题切换。

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!