您正在查看静态缓存页面 · 查看完整动态版本 · 登录 参与讨论

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

✨步子哥 (steper) 2026年02月17日 05:28 0 次浏览

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

本章导读:一个优秀的应用,不仅要有强大的功能,还要有一致、精美的外观。想象一下,如果你的应用有 50 个按钮,每个按钮的颜色、圆角、字体都需要单独设置——那将是维护者的噩梦。更糟糕的是,当设计团队决定把品牌色从蓝色改为紫色时,你需要找到并修改所有 50 处代码。本章将揭示 XAML 资源系统的奥秘——它是如何让你的代码"一次定义,处处使用"的,是如何让你的应用轻松支持深色模式的,是如何让你彻底重塑控件外观的。

💎 7.1 XAML 资源系统:视觉属性的统一管理

在开始之前,让我们先理解一个核心问题:为什么需要资源系统?

假设你正在开发一个企业应用,设计规范规定了以下品牌色:

  • 主色:蓝色 (#0078D4)
  • 次要色:灰色 (#666666)
  • 强调色:橙色 (#FF8C00)
  • 背景色:白色 (#FFFFFF)

没有资源系统,你的代码可能是这样的:

<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 资源的定义与引用

资源系统将这个问题优雅地解决了。你在资源字典中定义一次,然后在需要的地方引用:

<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 的属性都会自动重新查找并更新。

<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 属性中,只在该控件及其子控件中可用。

<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>

🎨 7.2 样式:封装视觉属性组

资源解决了"一处定义,处处使用"的问题,但想象这样的场景:你的应用中有 100 个 TextBlock,每个都需要设置 FontSizeFontWeightForegroundMargin 四个属性。即使用了资源,你仍然需要写 400 个属性设置。

样式(Style)将一组相关的属性设置封装成一个单元,一键应用。

📝 7.2.1 样式的定义与使用

<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

样式支持继承,你可以基于一个现有样式创建更具体的样式:

<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)。

<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 事件),视觉树定义控件的外观。控件模板就是视觉树的定义。

<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 是一个特殊的绑定,它将模板内部的元素属性连接到外部控件的属性。

<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 定义这些状态的视觉表现。

<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 节,其中为不同主题定义不同的资源:

<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 的属性会自动更新为对应主题的颜色。

🎚️ 7.4.2 在应用内切换主题

除了跟随系统主题,你还可以在应用内手动切换主题:

// 切换到深色主题
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 基础数据模板

<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)根据数据对象返回合适的模板。

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}" />

🎯 7.6 实战:构建一致的设计系统

让我们将本章学到的知识整合起来,构建一个小型但完整的设计系统。

📁 7.6.1 组织资源文件结构

 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>

📦 7.6.2 合并到 App.xaml

<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 条回复

还没有人回复