目录

WPF-ResourceDictionary资源类型

目录

1.style(样式)

作用

“对控件的属性进行统一设置,实现样式复用。”

本质

Style 本质是一个 属性设置器集合(Setter Collection),它不会改变控件的内部结构,只是批量修改属性值。

核心组成

组成部分 说明
TargetType 目标控件类型
Setter 设置属性值
Trigger 条件触发(鼠标悬浮、焦点等)
BasedOn 继承另一个 Style

基础示例

<Style x:Key="PrimaryButton" TargetType="Button">
    <Setter Property="FontSize" Value="16"/>
    <Setter Property="FontWeight" Value="Bold"/>
    <Setter Property="Background" Value="#2196F3"/>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="Padding" Value="15,8"/>
    <Setter Property="Cursor" Value="Hand"/>
</Style>

使用:

<Button Content="确定" Style="{StaticResource PrimaryButton}"/>

带Trigger的示例(坑)

<Style x:Key="HoverButton" TargetType="Button">
    <Setter Property="Background" Value="LightGray"/>
    <Setter Property="FontSize" Value="14"/>
    <Style.Triggers>
        <!-- 鼠标悬浮时改变背景色 -->
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="Orange"/>
            <Setter Property="Foreground" Value="White"/>
        </Trigger>
        <!-- 按下时改变背景色 -->
        <Trigger Property="IsPressed" Value="True">
            <Setter Property="Background" Value="DarkOrange"/>
        </Trigger>
        <!-- 禁用时变灰 -->
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Opacity" Value="0.5"/>
        </Trigger>
    </Style.Triggers>
</Style>

这样写是不行的,实际只有IsEnabled生效而 IsMouseOverIsPresseed的Background均不生效。

问题不在于 Trigger本身,而在 Button的默认模板。

需要自己去重写 ControlTemplate:

<Style x:Key="HoverButton" TargetType="Button">
    <Setter Property="Background" Value="LightGray"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="BorderBrush" Value="Gray"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Padding" Value="10,5"/>
    <Setter Property="FontSize" Value="14"/>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        CornerRadius="4">
                    <ContentPresenter
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Margin="{TemplateBinding Padding}"/>
                </Border>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" Property="Background" Value="Orange"/>
                        <Setter Property="Foreground" Value="White"/>
                    </Trigger>

                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="border" Property="Background" Value="DarkOrange"/>
                    </Trigger>

                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Opacity" Value="0.5"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

对于这类视觉变化一般都写到 ControlTemplate.Triggers

隐式样式(省略x:Key)

<!-- 没有 x:Key,自动应用到所有 Button -->
<Style TargetType="Button">
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="Margin" Value="5"/>
</Style>

省略 x:Key = 隐式样式,作用域内所有该类型控件自动应用

样式继承

<!-- 基础样式 -->
<Style x:Key="BaseButton" TargetType="Button">
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="Padding" Value="10,5"/>
    <Setter Property="Margin" Value="5"/>
</Style>

<!-- 继承基础样式,添加更多属性 -->
<Style x:Key="DangerButton" TargetType="Button" BasedOn="{StaticResource BaseButton}">
    <Setter Property="Background" Value="Red"/>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="FontWeight" Value="Bold"/>
</Style>
  • Style只能设置属性,不能改变内部组成
  • 每个控件只能有一个Style,但可以通过 BaseOn 继承
  • 隐式样式只影响当前作用域(Window/App/ResourceDictionary)

2.ControlTemplate(控件模板)

作用

彻底重写控件的视觉结构*,决定控件"长什么样"、“由哪些元素组成”。”

本质

ControlTemplate 本质是一颗 Visual Tree(视觉树),它定义了控件的内部组成元素和布局方式。

与style的根本区别

对比项 Style ControlTemplate
改什么 属性值 控件内部结构
比喻 给房子刷漆、换窗帘 把房子拆了重建
是否改变布局
是否添加新元素

基础示例:重写 Button

<ControlTemplate x:Key="RoundButtonTemplate" TargetType="Button">
    <!-- 控件结构:一个圆角边框 + 内容 -->
    <Border x:Name="border"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            CornerRadius="20"
            Padding="{TemplateBinding Padding}">
        <!-- ContentPresenter = 显示 Button.Content -->
        <ContentPresenter 
            HorizontalAlignment="Center"
            VerticalAlignment="Center"/>
    </Border>
</ControlTemplate>

使用:

<Button Content="圆角按钮" 
        Template="{StaticResource RoundButtonTemplate}"
        Background="CornflowerBlue"
        Foreground="White"
        Padding="20,10"/>

关键元素

元素 说明
ContentPresenter 显示 Content 属性的内容(用于ContentControl 类控件)
ItemsPressenter 显示子项集合()
TemplateBinding 绑定到模板化控件的属性
x:Name 给模板中的元素命名,供 Trigger 引用

带Trigger的完整实例

<ControlTemplate x:Key="FancyButton" TargetType="Button">
    <Border x:Name="border"
            Background="#2196F3"
            CornerRadius="8"
            Padding="15,8">
        <ContentPresenter 
            HorizontalAlignment="Center"
            VerticalAlignment="Center"/>
    </Border>
    
    <!-- 模板内的触发器 -->
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="border" Property="Background" Value="#1976D2"/>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter TargetName="border" Property="Background" Value="#0D47A1"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter TargetName="border" Property="Background" Value="#BDBDBD"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Style + ControlTemplate 结合使用(最常见的写法)

<Style x:Key="ModernButton" TargetType="Button">
    <!-- Style 设置属性 -->
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="Cursor" Value="Hand"/>
    <Setter Property="Background" Value="#4CAF50"/>
    
    <!-- Style 中嵌入 ControlTemplate -->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        CornerRadius="6"
                        Padding="20,10">
                    <ContentPresenter 
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" 
                                Property="Background" Value="#388E3C"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

使用:

<Button Content="现代按钮" Style="{StaticResource ModernButton}"/>

重写 TextBox 模板示例

<Style x:Key="ModernTextBox" TargetType="TextBox">
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="Padding" Value="10,8"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="TextBox">
                <Border x:Name="border"
                        Background="White"
                        BorderBrush="#BDBDBD"
                        BorderThickness="0,0,0,2"
                        Padding="{TemplateBinding Padding}">
                    <!-- ScrollViewer 是 TextBox 必须的,用于显示文字 -->
                    <ScrollViewer x:Name="PART_ContentHost"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsFocused" Value="True">
                        <Setter TargetName="border" 
                                Property="BorderBrush" Value="#2196F3"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

PART_ContentHost 是 TextBox 内部约定的名称,必须这样写,否则文字无法显示。

3.DataTemplate(数据模板)

作用

定义数据对象如何被呈现为 UI 元素。”

本质

DataTemplate 是一个 数据 → 视觉 的映射规则。

与 ControlTemplate 的区别

对比项 ControlTemplate DataTemplate
针对什么 控件的外观 数据的展示方式
使用场景 重写 Button、TextBox 等 ListBox、ComboBox、ItemsControl 中展示数据
绑定方式 TemplateBinding Binding
比喻 改变容器的形状 改变容器里内容的展示方式

数据模型

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Avatar { get; set; }
    public double Score { get; set; }
}

基础示例

<DataTemplate x:Key="StudentTemplate">
    <Border BorderBrush="LightGray" BorderThickness="1" 
            CornerRadius="8" Padding="10" Margin="5"
            Background="#FAFAFA">
        <StackPanel Orientation="Horizontal">
            <!-- 头像 -->
            <Ellipse Width="50" Height="50">
                <Ellipse.Fill>
                    <ImageBrush ImageSource="{Binding Avatar}"/>
                </Ellipse.Fill>
            </Ellipse>
            
            <!-- 信息 -->
            <StackPanel Margin="10,0,0,0" VerticalAlignment="Center">
                <TextBlock Text="{Binding Name}" 
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock Text="{Binding Age, StringFormat='年龄: {0}岁'}" 
                           Foreground="Gray"/>
                <TextBlock Text="{Binding Score, StringFormat='成绩: {0}分'}" 
                           Foreground="CornflowerBlue"/>
            </StackPanel>
        </StackPanel>
    </Border>
</DataTemplate>

使用:

<ListBox ItemsSource="{Binding Students}" ItemTemplate="{StaticResource StudentTemplate}"/>

隐式 DataTemplate(自动匹配类型)

<!-- 没有 x:Key,指定 DataType -->
<!-- 所有 Student 类型的数据自动使用这个模板 -->
<DataTemplate DataType="{x:Type local:Student}">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Name}" FontWeight="Bold" Margin="0,0,10,0"/>
        <TextBlock Text="{Binding Score, StringFormat='{}{0}分'}" Foreground="Blue"/>
    </StackPanel>
</DataTemplate>

在 ContentControl 中使用

<!-- 比如展示选中的学生详情 -->
<ContentControl Content="{Binding SelectedStudent}" ContentTemplate="{StaticResource StudentDetailTemplate}"/>

4.ItemsPanelTemplate(项面板模板)

作用

定义 ItemsControl(ListBox、ListView 等)的子项用什么布局容器排列。*”

本质

默认情况下,ListBox 的子项是垂直排列的(用 VirtualizingStackPanel)。ItemsPanelTemplate 可以把它改成水平排列、网格排列等。

默认行为

ListBox 默认 → VirtualizingStackPanel(垂直排列)

示例1:水平排列

<ItemsPanelTemplate x:Key="HorizontalPanel">
    <StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>

使用:

<ListBox ItemsSource="{Binding Items}" ItemsPanel="{StaticResource HorizontalPanel}"/>

示例2:WrapPanel(自动换行)

<ItemsPanelTemplate x:Key="WrapLayout">
    <WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
<!-- 标签墙效果 -->
<ItemsControl ItemsSource="{Binding Tags}" 
              ItemsPanel="{StaticResource WrapLayout}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border Background="#E3F2FD" CornerRadius="12" 
                    Padding="10,5" Margin="3">
                <TextBlock Text="{Binding}" Foreground="#1565C0"/>
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

示例3:UniformGrid(均匀网格)

<ItemsPanelTemplate x:Key="GridLayout">
    <UniformGrid Columns="3"/>
</ItemsPanelTemplate>
<!-- 图片网格展示 -->
<ListBox ItemsSource="{Binding Photos}" ItemsPanel="{StaticResource GridLayout}"/>

示例4:Canvas(自由定位)

<ItemsPanelTemplate x:Key="CanvasLayout">
    <Canvas/>
</ItemsPanelTemplate>

常用面板对比

面板 效果 适用场景
StackPanel 垂直 从上到下排列 默认列表
StackPanel 水平 从左到右排列 水平标签
WraPanel 自动换行 标签墙、照片墙
UniformGrid 均匀网格 棋盘、宫格
Canvas 绝对定位 自由拖拽

5.画刷资源

作用

“定义颜色填充方案,供多个控件复用。”

本质

画刷(Brush)是WPF中所有颜色/填充的基础类型,background、Foreground、BorderBrush 等属性都是 Brush 类型。

SolidColorBrush(纯色画刷)

<SolodColorBrush x:key="PrimaryBrush" Color="#2196F3"/>
<SolidColorBrush x:Key="DangerBrush" Color="#F44336"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#4CAF50"/>
<SolidColorBrush x:Key="TextBrush" Color="#212121"/>
<SolidColorBrush x:Key="SubTextBrush" Color="#757575"/>

使用:

<Button Background="{StaticResource PrimaryBrush}" Content="主要按钮"/>
<TextBlock Foreground="{StaticResource SubTextBrush}" Text="次要文字"/>

LinearGradientBrush(线性渐变画刷)

线性渐变:颜色按照一条直线从一个点过渡到另一个点。渐变沿着一条直线进行,可以通过设置渐变的角度来控制渐变的方向。

<LinearGradientBrush x:Key="HeaderGradient" StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="#1A237E" Offset="0"/>
    <GradientStop Color="#4A148C" Offset="0.5"/>
    <GradientStop Color="#880E4F" Offset="1"/>
</LinearGradientBrush>
<Border Background="{StaticResource HeaderGradient}" Height="200">
    <TextBlock Text="渐变背景" Foreground="White" 
               FontSize="24" HorizontalAlignment="Center" 
               VerticalAlignment="Center"/>
</Border>

RadialGradientBrush(径向渐变画刷)

径向渐变:颜色从一个中心点开始,向外辐射到周围的区域。渐变的过渡是从中心点向外扩展的,形成一个环形的渐变效果。

<RadialGradientBrush x:Key="SpotlightBrush">
    <GradientStop Color="White" Offset="0"/>
    <GradientStop Color="Transparent" Offset="1"/>
</RadialGradientBrush>

ImageBrush(图像画刷)

<ImageBrush x:Key="BackgroundImage" 
            ImageSource="/Assets/bg.jpg" 
            Stretch="UniformToFill"/>
<Border Background="{StaticResource BackgroundImage}" Height="300"/>

画刷类型总结

类型 效果 场景
SolidColorBrush 纯色 最常用
LinearGradientBrush 线性渐变 标题栏、按钮
RadialGradientBrush 径向渐变 聚光灯、光晕
ImageBrush 图片填充 背景图
VisualBrush 用 Visual 元素填充 倒影、预览
DrawingBrush 用 Drawing 填充 纹理、图案

6.Color(颜色)

作用

“定义颜色值,供画刷或其它需要颜色的地方引用。”

本质

Color 是一个结构体值,不是画刷。它需要被画刷包裹后才能赋给 Background 等属性。

与SolidColorBrush的区别

类型 本质 能否直接赋给Background
Color 颜色值(结构体) 不能
SolidColorBrush 画刷(对象) 可以

示例

<!-- 定义颜色 -->
<Color x:Key="PrimaryColor">#2196F3</Color>
<Color x:Key="DangerColor">#F44336</Color>
<Color x:Key="TextColor">#212121</Color>

<!-- 颜色用于画刷 -->
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
<SolidColorBrush x:Key="DangerBrush" Color="{StaticResource DangerColor}"/>

“先定义Color,再用Color创建Brush,这样修改颜色只需这一处。”

颜色用于渐变

<Color x:Key="GradientStart">#1A237E</Color>
<Color x:Key="GradientEnd">#E91E63</Color>

<LinearGradientBrush x:Key="MyGradient" StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="{StaticResource GradientStart}" Offset="0"/>
    <GradientStop Color="{StaticResource GradientEnd}" Offset="1"/>
</LinearGradientBrush>

颜色用于动画

<Storyboard x:Key="ColorChange">
    <ColorAnimation
        Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
        To="{StaticResource DangerColor}"
        Duration="0:0:0.3"/>
</Storyboard>

“动画中必须用 Color,不能用 Brush。”

7.Thickness(边距/间距)

作用

“定义Margin、Padding、BorderThickness等需要上下左右值的属性。”

本质

Thickness 是一个结构体,包含 Left、Top、Right、Button四个值。

示例

<!-- 统一间距 -->
<Thickness x:Key="DefaultMargin">10</Thickness>
<!-- 等价于 10,10,10,10 -->

<!-- 水平/垂直不同 -->
<Thickness x:Key="CardPadding">15,10</Thickness>
<!-- 等价于 左右15,上下10 -->

<!-- 四个方向分别指定 -->
<Thickness x:Key="HeaderMargin">0,0,0,20</Thickness>
<!-- 左0,上0,右0,下20 -->

<!-- 边框粗细 -->
<Thickness x:Key="InputBorder">0,0,0,2</Thickness>
<!-- 只有底部有边框 -->

使用:

<Button Margin="{StaticResource DefaultMargin}" Content="按钮"/>
<Border Padding="{StaticResource CardPadding}">
    <TextBlock Text="卡片内容"/>
</Border>
<TextBox BorderThickness="{StaticResource InputBorder}"/>

其它常见简单值类型

<!-- 字体大小 -->
<sys:Double x:Key="TitleFontSize">24</sys:Double>
<sys:Double x:Key="BodyFontSize">14</sys:Double>

<!-- 圆角半径 -->
<CornerRadius x:Key="CardRadius">8</CornerRadius>
<CornerRadius x:Key="ButtonRadius">20</CornerRadius>

<!-- 字体粗细 -->
<FontWeight x:Key="TitleWeight">Bold</FontWeight>

<!-- 字符串 -->
<sys:String x:Key="AppTitle">我的应用程序</sys:String>

需要引入命名空间:xmlns:sys="clr-namespace:System;assembly=mscorlib"

8.StoryBoard(动画故事板)

作用

“定义动画序列,控制属性随时间变化。”

本质

StoryBoard 是一个 动画时间线容器,内部包含一个或多个 Animation 对象,每个Animation 负责改变一个属性。

常用动画类型

动画类型 改变什么 示例
DoubleAnimation double值 Opacity、Width、Height
ColorAnimation 颜色 Background.Color
ThicknessAnimation Thickness值 Margin、Padding
ObjectAnimationUsingKeyFrames 任意对象 Visibility

示例1:淡入动画

<StoryBoard x:Key="FadenIn">
    <DoubleAnimation 
         Storyboard.TargetProperty="Opacity"
         From="0"
         To="1"
         Duration="0:0:0.5"/>              
</StoryBoard>

使用:

<Grid>
    <Button
        Width="100"
        Height="50"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Content="Click Me">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
                <BeginStoryboard Storyboard="{StaticResource FadenIn}" />
            </EventTrigger>
        </Button.Triggers>
    </Button>
</Grid>

示例2:淡出动画

<Storyboard x:Key="FadeOut">
    <DoubleAnimation 
        Storyboard.TargetProperty="Opacity"
        From="1" To="0"
        Duration="0:0:0.3"/>
</Storyboard>

示例3:颜色变化

<Storyboard x:Key="ColorPulse">
    <ColorAnimation 
        Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
        To="Red"
        Duration="0:0:0.3"
        AutoReverse="True"/>
</Storyboard>

示例4:缩放动画(需要RenderTransform)

<Storyboard x:Key="ScaleUp">
    <DoubleAnimation 
        Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
        To="1.1" Duration="0:0:0.2"/>
    <DoubleAnimation 
        Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
        To="1.1" Duration="0:0:0.2"/>
</Storyboard>

使用:

<Button
    x:Name="MyButton"
    Width="200"
    Height="50"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    Content="Click Me">
    <Button.RenderTransform>
        <ScaleTransform x:Name="ScaleTransform" />
    </Button.RenderTransform>

    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
            <BeginStoryboard Storyboard="{StaticResource ScaleUp}" />
        </EventTrigger>
    </Button.Triggers>
</Button>

在Style中使用动画

<Style x:Key="AnimatedButton" TargetType="Button">
    <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
    <Setter Property="RenderTransform">
        <Setter.Value>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="border" Background="#2196F3" 
                        CornerRadius="8" Padding="15,8">
                    <ContentPresenter HorizontalAlignment="Center" 
                                      VerticalAlignment="Center"/>
                </Border>
                <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="MouseEnter">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation 
                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
                                    To="1.05" Duration="0:0:0.15"/>
                                <DoubleAnimation 
                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                    To="1.05" Duration="0:0:0.15"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="MouseLeave">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation 
                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
                                    To="1.0" Duration="0:0:0.15"/>
                                <DoubleAnimation 
                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                    To="1.0" Duration="0:0:0.15"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Button Style="{StaticResource AnimatedButton}" Content="Hover Me" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="50"/>

9.Converter(转换器)

作用

“在数据绑定时,将源数据转换为目标属性需要的格式。

本质

Converter 本身是一个 C# 类,实现 IValueConverter 接口。在 ResourceDictionary 中注册资源,供 XAML 绑定使用。

定义转换器(C#代码)

public class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool b)
        {
            return b ? Visibility.Visible : Visibility.Collapsed;
        }

        return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Visibility v)
        {
            return v == Visibility.Visible;
        }

        return false;
    }
}

在ResourceDictionary中注册

<ResourceDictionary xmlns:local="clr-namespace:MyApp.Converters">
    <local:BoolToVisibilityConverter x:Key="BoolToVisibility"/>
</ResourceDictionary>

使用

<TextBlock Text="加载中..." 
           Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}"/>

更多常用转换器示例

反转 Bool

public class InverseBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
                          object parameter, CultureInfo culture)
    {
        return value is bool b ? !b : value;
    }

    public object ConvertBack(object value, Type targetType, 
                              object parameter, CultureInfo culture)
    {
        return value is bool b ? !b : value;
    }
}

数值 → 百分比文字

public class PercentConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
                          object parameter, CultureInfo culture)
    {
        if (value is double d)
            return $"{d:P0}"; // 例如 0.85 → "85%"
        return "0%";
    }

    public object ConvertBack(object value, Type targetType, 
                              object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

空字符串 → 显示占位符

public class NullToPlaceholderConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
                          object parameter, CultureInfo culture)
    {
        string s = value as string;
        return string.IsNullOrEmpty(s) ? (parameter ?? "暂无数据") : s;
    }

    public object ConvertBack(object value, Type targetType, 
                              object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

多值转换器(IMultiValueConverter)

// 姓 + 名 → 全名
public class FullNameConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, 
                          object parameter, CultureInfo culture)
    {
        string firstName = values[0] as string ?? "";
        string lastName = values[1] as string ?? "";
        return $"{lastName}{firstName}";
    }

    public object[] ConvertBack(object value, Type[] targetTypes, 
                                object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
<local:FullNameConverter x:Key="FullNameConverter"/>
<TextBlock>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource FullNameConverter}">
            <Binding Path="FirstName"/>
            <Binding Path="LastName"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

10.Geometry(几何图形)

作用

“定义矢量图形路径,用于图标、形状绘制。”

本质

Geometry 是一个数学描述的形状轮廓,它不会自己渲染,需要配合 Path 控件使用。

与 Shape 的区别

类型 说明
Geometry 纯数据(形状描述),轻量
Path UI控件,内部使用 Geomotry 来绘制

示例:定义图标路径

<!-- 关闭图标 -->
<Geometry x:Key="CloseIcon">
    M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12Z
</Geometry>

<!-- 搜索图标 -->
<Geometry x:Key="SearchIcon">
    M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z
</Geometry>

<!-- 菜单图标 -->
<Geometry x:Key="MenuIcon">
    M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z
</Geometry>

<!-- 箭头图标 -->
<Geometry x:Key="ArrowRightIcon">
    M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z
</Geometry>

使用

<!-- 方式1:Path 控件 -->
<Path Data="{StaticResource CloseIcon}" 
      Fill="Red" Width="24" Height="24"
      Stretch="Uniform"/>

<!-- 方式2:Button 中使用图标 -->
<Button Width="40" Height="40" Background="Transparent" BorderThickness="0">
    <Path Data="{StaticResource SearchIcon}" 
          Fill="#757575" Width="20" Height="20"
          Stretch="Uniform"/>
</Button>

封装为图标按钮 Style

<Style x:Key="IconButton" TargetType="Button">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Cursor" Value="Hand"/>
    <Setter Property="Width" Value="36"/>
    <Setter Property="Height" Value="36"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="border" Background="{TemplateBinding Background}"
                        CornerRadius="18">
                    <ContentPresenter HorizontalAlignment="Center" 
                                      VerticalAlignment="Center"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" 
                                Property="Background" Value="#1A000000"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Button Style="{StaticResource IconButton}">
    <Path Data="{StaticResource MenuIcon}" 
          Fill="#333" Width="18" Height="18" 
          Stretch="Uniform"/>
</Button>

Geometry 来源

通常不手写路径数据,而是从以下来源获取:

来源 说明
Material Design Icons Google 图标库,大量现成路径
SVG 转换 将 SVG 文件转换为 Geometry
Blend 设计工具 直接绘制后导出
Font Awesome 字体图标转矢量

11. 其他常见资源类型补充

FontFamily(字体)

<FontFamily x:Key="TitleFont">Microsoft YaHei UI</FontFamily>
<FontFamily x:Key="CodeFont">Consolas</FontFamily>
<TextBlock FontFamily="{StaticResource TitleFont}" Text="标题"/>

BitmapImage(位图)

<BitmapImage x:Key="Logo" UriSource="/Assets/logo.png"/>
<BitmapImage x:Key="DefaultAvatar" UriSource="/Assets/avatar.png"/>
<Image Source="{StaticResource Logo}" Width="100"/>

GridLength / ColumnDefinition(不常放资源里但可以)

一般不放 ResourceDictionary,但理论上可以。

ContextMenu

<ContextMenu x:Key="EditContextMenu">
    <MenuItem Header="剪切" Command="ApplicationCommands.Cut"/>
    <MenuItem Header="复制" Command="ApplicationCommands.Copy"/>
    <MenuItem Header="粘贴" Command="ApplicationCommands.Paste"/>
    <Separator/>
    <MenuItem Header="全选" Command="ApplicationCommands.SelectAll"/>
</ContextMenu>
<TextBox ContextMenu="{StaticResource EditContextMenu}"/>

DrawingImage(矢量图标作为 Image 源)

<DrawingImage x:Key="CheckIcon">
    <DrawingImage.Drawing>
        <GeometryDrawing Brush="Green">
            <GeometryDrawing.Geometry>
                <PathGeometry Figures="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z"/>
            </GeometryDrawing.Geometry>
        </GeometryDrawing>
    </DrawingImage.Drawing>
</DrawingImage>
<Image Source="{StaticResource CheckIcon}" Width="24" Height="24"/>

12. ResourceDictionary 本身的用法

在 Window 中定义局部资源

<Window.Resources>
    <SolidColorBrush x:Key="MyBrush" Color="Red"/>
    <Style x:Key="MyStyle" TargetType="Button">
        <Setter Property="Background" Value="{StaticResource MyBrush}"/>
    </Style>
</Window.Resources>

独立的资源文件

文件:Themes/ButtonStyles.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <SolidColorBrush x:Key="PrimaryBrush" Color="#2196F3"/>
    
    <Style x:Key="PrimaryButton" TargetType="Button">
        <Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
    
</ResourceDictionary>

在 App.xaml 中合并

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Themes/Colors.xaml"/>
            <ResourceDictionary Source="Themes/Brushes.xaml"/>
            <ResourceDictionary Source="Themes/ButtonStyles.xaml"/>
            <ResourceDictionary Source="Themes/TextBoxStyles.xaml"/>
            <ResourceDictionary Source="Themes/Converters.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

推荐的文件组织结构

MyApp/
├── Themes/
│   ├── Colors.xaml          ← Color 定义
│   ├── Brushes.xaml         ← Brush 定义(引用 Colors)
│   ├── Sizes.xaml           ← Thickness、Double、CornerRadius
│   ├── Fonts.xaml           ← FontFamily
│   ├── Icons.xaml           ← Geometry 图标
│   ├── Converters.xaml      ← 转换器注册
│   ├── ButtonStyles.xaml    ← Button 的 Style + Template
│   ├── TextBoxStyles.xaml   ← TextBox 的 Style + Template
│   ├── ListBoxStyles.xaml   ← ListBox 的 Style + Template
│   └── DataTemplates.xaml   ← DataTemplate
├── Converters/
│   ├── BoolToVisibilityConverter.cs
│   └── PercentConverter.cs
├── ViewModels/
├── Views/
└── App.xaml                 ← 合并所有资源字典

资源查找顺序

控件自身 Resources
父容器 Resources
Window Resources
App.xaml Resources
Theme Resources(Generic.xaml)

就近原则:先从自身找,找不到往上找。

13. 总结对比表

资源类型 本质 作用 关键用途
Style 属性设置器集合 批量设置控件属性 统一外观、触发器
ControlTemplate 视觉树 重写控件内部结构 自定义控件外观
DataTemplate 数据→UI 映射 定义数据如何展示 ListBox、ContentControl
ItemsPanelTemplate 布局容器定义 改变子项排列方式 横向列表、网格
SolidColorBrush 画刷对象 颜色填充 Background、Foreground
LinearGradientBrush 渐变画刷 渐变效果 标题栏、装饰
Color 颜色值(结构体) 定义颜色 供 Brush 和动画使用
Thickness 四边值(结构体) Margin/Padding/Border 间距统一管理
Storyboard 动画时间线容器 属性动画 过渡效果、交互反馈
Converter C# 类实例 数据绑定转换 Bool→Visibility 等
Geometry 矢量路径数据 图标/形状 矢量图标
BitmapImage 位图引用 图片资源 Logo、头像
FontFamily 字体引用 字体定义 全局字体
CornerRadius 圆角值 Border 圆角 卡片、按钮
sys:Double 数值 字体大小等 统一尺寸
sys:String 字符串 文本内容 国际化、常量

一句话记忆

类型 一句话
Style 给控件穿衣服
ControlTemplate 给控件换骨骼
DataTemplate 给数据化妆
ItemsPanelTemplate 给列表换队形
Brush 颜料
Color 颜料的配方
Storyboard 动画剧本
Converter 翻译官
Geometry 图标的蓝图

“ResourceDictionary 就是一个大仓库,里面放着各种“可复用的零件”。Style 和 ControlTemplate只是其中最常见的两种,实际上任何可序列化的对象都可以作为资源存放。”