样式设置和模板化

Windows Presentation Foundation (WPF) 样式设置和模板化是指一套功能(样式、模板、触发器和演示图板),开发人员和设计人员使用这些功能可以创建更好的视觉效果,也可以为其产品创建统一外观。 尽管开发人员和/或设计人员可以对应用程序的外观逐个进行大量自定义操作,他们还是需要一个功能强大的样式设置和模板化模型,以便在应用程序内部和应用程序之间维护和共享外观。 Windows Presentation Foundation (WPF) 就提供了这样的模型。

WPF 样式设置模型的另一个功能是实现表示形式与逻辑的分离。 这意味着,在开发人员使用 C# 或 Visual Basic 进行逻辑编程时,设计人员只需使用 XAML 即可设计程序的外观。

本概述主要讨论该应用程序的样式设置和模板化方面,而不讨论任何数据绑定概念。 有关数据绑定的信息,请参见数据绑定概述

另外,了解资源也很重要,正是资源使得样式和模板得以重用。 有关资源的更多信息,请参见资源概述

本主题包括下列各节。

  • 样式设置和模板化示例
  • 样式基础知识
  • 数据模板
  • 控件模板
  • 触发器
  • 共享的资源和主题
  • 相关主题

样式设置和模板化示例

本概述中使用的代码示例基于下图中显示的简单照片示例:

带样式的 ListView

该简单照片示例使用样式设置和模板化来产生令人赞叹的用户视觉体验。 该示例有两个 TextBlock 元素和一个绑定到图像列表的 ListBox 控件。 有关完整示例,请参见 Introduction to Styling and Templating Sample(样式和模板化简介示例)。

样式基础知识

您可以将 Style 看作是将一组属性值应用到多个元素的捷径。 例如,考虑下面的 TextBlock 元素及其默认外观:

<TextBlock>My Pictures</TextBlock>
<TextBlock>Check out my new pictures!</TextBlock>

样式示例屏幕快照

通过直接对每个 TextBlock 元素设置 FontSizeFontFamily 等属性可以更改默认外观。 但是,如果希望 TextBlock 元素可以共享某些属性,则可以在 XAML 文件的 Resources 节中创建一个 Style,如下所示:

<Window.Resources>


...


<!--A Style that affects all TextBlocks-->
<Style TargetType="TextBlock">
  <Setter Property="HorizontalAlignment" Value="Center" />
  <Setter Property="FontFamily" Value="Comic Sans MS"/>
  <Setter Property="FontSize" Value="14"/>
</Style>


...


</Window.Resources>

将样式的 TargetType 设置为 TextBlock 类型时,该样式会应用于窗口中的所有 TextBlock 元素。

现在,TextBlock 元素的外观如下:

样式示例屏幕快照

扩展样式

您可能希望两个 TextBlock 元素共享某些属性值(如 FontFamily 和居中的 HorizontalAlignment),同时还希望文本“我的图片”具有某些其他属性。 在第一个样式的基础上创建一个新样式可以达到这一目的,如下所示:

<Window.Resources>


...


<!--A Style that extends the previous TextBlock Style-->
<!--This is a "named style" with an x:Key of TitleText-->
<Style BasedOn="{StaticResource {x:Type TextBlock}}"
       TargetType="TextBlock"
       x:Key="TitleText">
  <Setter Property="FontSize" Value="26"/>
  <Setter Property="Foreground">
  <Setter.Value>
      <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
        <LinearGradientBrush.GradientStops>
          <GradientStop Offset="0.0" Color="#90DDDD" />
          <GradientStop Offset="1.0" Color="#5BFFFF" />
        </LinearGradientBrush.GradientStops>
      </LinearGradientBrush>
    </Setter.Value>
  </Setter>
</Style>


...


</Window.Resources>

请注意,已为上面的样式提供了 x:Key。 若要应用该样式,请将 TextBlockStyle 属性设置为 x:Key 值,如下所示:

<TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
<TextBlock>Check out my new pictures!</TextBlock>

现在,此 TextBlock 样式的 HorizontalAlignment 值为 CenterFontFamily 值为 Comic Sans MS,FontSize 值为 26,Foreground 值设置为示例所示的 LinearGradientBrush。 请注意,它重写基准样式的 FontSize 值。 如果有多个 SetterStyle 的同一属性进行设置,则最后声明的 Setter 优先。

下面演示 TextBlock 元素现在的外观:

带样式的 TextBlock

此 TitleText 样式扩展了为 TextBlock 类型创建的样式。 此外,使用 x:Key 值也可以扩展具有 x:Key 的样式。 有关示例,请参见为 BasedOn 属性提供的示例。

TargetType 属性和 x:Key 特性的关系

如第一个示例所示,如果将 TargetType 属性设置为 TextBlock 而不为样式分配 x:Key,样式就会应用于所有 TextBlock 元素。 这种情况下,x:Key 隐式设置为 {x:Type TextBlock}。 这意味着,如果将 x:Key 值显式设置为 {x:Type TextBlock} 之外的任何值,Style 就不会自动应用于所有 TextBlock 元素。 此时,必须通过使用 x:Key 值,将样式显式应用于 TextBlock 元素。 如果样式位于资源部分,并且未设置样式的 TargetType 属性,则必须提供 x:Key。

除了提供 x:Key 的默认值之外,TargetType 属性还指定要应用 setter 属性的类型。 如果未指定 TargetType,则必须通过语法 Property="ClassName.Property",用类名限定 Setter 对象的属性。 例如,必须将 Property 设置为 "TextBlock.FontSize" 或 "Control.FontSize",而不要设置 Property="FontSize"。

此外,还应注意,很多 WPF 控件都是由其他 WPF 控件组合而成的。 如果创建要应用于某个类型的所有控件的样式,可能会得到意想不到的结果。 例如,如果针对 Window 中的 TextBlock 类型创建一个样式,则该样式会应用于窗口中的所有 TextBlock 控件,即使 TextBlock 是另一个控件(如 ListBox)的组成部分也不例外。

样式和资源

任何派生自 FrameworkElementFrameworkContentElement 的元素都可以使用样式。 声明样式的最常见方式是将样式作为 XAML 文件的 Resources 节中的资源,如前面的示例所示。 由于样式是资源,因此同样遵循所有资源都适用的范围规则:样式的声明位置决定样式的应用范围。 例如,如果在应用程序定义XAML 文件的根元素中声明样式,则该样式可在应用程序中的任何位置使用。 如果创建的是导航应用程序,并在该应用程序的某个 XAML 文件中声明样式,则该样式只能在该 XAML 文件中使用。 有关资源范围规则的更多信息,请参见资源概述

另外,有关样式和资源的更多信息,请参见本概述后面部分的共享的资源和主题。

以编程方式设置样式

若要以编程方式向元素分配命名样式,请从资源集合中获取该样式,然后将其分配给元素的 Style 属性。 请注意,资源集合中的项都属于 Object 类型。 因此,必须将检索到的样式强制转换为 Style,然后才能将其分配给 Style 属性。 例如,若要对名为 textblock1 的 TextBlock 设置定义的 TitleText 样式,请执行以下操作:

textblock1.Style = CType(Me.Resources("TitleText"), Style)
textblock1.Style = (Style)(this.Resources["TitleText"]);

请注意,样式一旦应用,便会密封并且无法更改。 如果要动态更改已应用的样式,必须创建一个新样式来替换现有样式。 有关更多信息,请参见 IsSealed 属性。

您可以创建一个根据自定义逻辑选择要应用的样式的对象。 有关示例,请参见为 StyleSelector 类提供的示例。

绑定、动态资源和事件处理程序

请注意,使用 Setter.Value 属性可以指定 绑定标记扩展DynamicResource 标记扩展。 有关更多信息,请参见为 Setter.Value 属性提供的示例。

到目前为止,本概述仅讨论了如何使用 setter 设置属性值。 在样式中也可以指定事件处理程序。 有关更多信息,请参见 EventSetter

数据模板

在本示例应用程序中,有一个绑定到照片列表的 ListBox 控件:

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

ListBox 当前的外观如下所示:

应用模板之前的 ListBox

大多数控件都具有某种类型的内容,这些内容通常来自绑定到的数据。 在本示例中,数据为照片列表。 在 WPF 中,使用 DataTemplate 可以定义数据的可视表示形式。 基本上,输入 DataTemplate 的内容决定了数据在呈现的应用程序中的外观。

在我们的示例应用程序中,每个自定义 Photo 对象都具有一个字符串类型的 Source 属性,该属性指定图像的文件路径。 当前,照片对象显示为文件路径。

对于要显示为图像的照片,可以将 DataTemplate 作为资源创建:

<Window.Resources>


...


<!--DataTemplate to display Photos as images
    instead of text strings of Paths-->
<DataTemplate DataType="{x:Type local:Photo}">
  <Border Margin="3">
    <Image Source="{Binding Source}"/>
  </Border>
</DataTemplate>


...


</Window.Resources>

请注意,DataType 属性与 StyleTargetType 属性非常相似。 如果 DataTemplate 位于资源部分,并且将 DataType 属性指定为某个类型,也不为其分配 x:Key,则只要该类型出现,便会应用 DataTemplate。 任何时候都可以为 DataTemplate 分配 x:Key,然后将其设置为 DataTemplate 类型的属性(如 ItemTemplate 属性或 ContentTemplate 属性)的 StaticResource。

实质上,上面示例的 DataTemplate 确定只要存在 Photo 对象,该对象就应作为 Image 显示在 Border 中。 通过此 DataTemplate,应用程序现在的外观如下:

照片图

数据模板化模型还提供其他功能。 例如,如果要使用 HeaderedItemsControl 类型(如 MenuTreeView)显示包含其他集合的集合数据,则可以使用 HierarchicalDataTemplate。 另一个数据模板化功能是 DataTemplateSelector,利用这一功能可以根据自定义逻辑选择要使用的 DataTemplate。 有关更多信息,请参见数据模板化概述,该概述对不同的数据模板化功能进行了更加深入的讨论。

控件模板

在 WPF 中,控件的 ControlTemplate 定义控件的外观。 通过为控件定义新的 ControlTemplate 可以更改控件的结构和外观。 很多情况下,这种方法都足够灵活,您不需要自己编写自定义控件。 有关更多信息,请参见通过创建 ControlTemplate 自定义现有控件的外观

触发器

某个属性值更改时,或某个事件引发时,触发器会相应地设置属性或启动操作(如动画操作)。 StyleControlTemplateDataTemplate 都具有 Triggers 属性,该属性可以包含一组触发器。 有各种类型的触发器。

属性触发器

设置属性值或根据属性值开始操作的 Trigger 称为属性触发器。

为了演示如何使用属性触发器,可以将每个 ListBoxItem 都设置为部分透明(除非它被选中)。 下面的样式将 ListBoxItemOpacity 值设置为 0.5。 但是,当 IsSelected 属性为 true 时,Opacity 设置为 1.0:

<Style TargetType="ListBoxItem">
  <Setter Property="Opacity" Value="0.5" />
  <Setter Property="MaxHeight" Value="75" />
  <Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Setter Property="Opacity" Value="1.0" />
    </Trigger>


...


  </Style.Triggers>
</Style>

此示例使用 Trigger 来设置属性值,但请注意,Trigger 类还具有 EnterActionsExitActions 属性,通过这两个属性,触发器可以执行操作。

请注意,ListBoxItemMaxHeight 属性设置为 75。 在下图中,第三项是选中的项:

带样式的 ListView

EventTrigger 和 Storyboard

另一种类型的触发器是 EventTrigger,它根据事件的引发来启动一组操作。 例如,下面的 EventTrigger 对象指定当鼠标指针进入 ListBoxItem 时,MaxHeight 属性在 0.2 秒时间内以动画方式增大为值 90。 当鼠标离开该项时,该属性在 1 秒时间内还原为原始值。 请注意为何无需为 MouseLeave 动画指定 To 值。 这是因为动画能够跟踪原始值。

        <EventTrigger RoutedEvent="Mouse.MouseEnter">
          <EventTrigger.Actions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation
                  Duration="0:0:0.2"
                  Storyboard.TargetProperty="MaxHeight"
                  To="90"  />
              </Storyboard>
            </BeginStoryboard>
          </EventTrigger.Actions>
        </EventTrigger>
        <EventTrigger RoutedEvent="Mouse.MouseLeave">
          <EventTrigger.Actions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation
                  Duration="0:0:1"
                  Storyboard.TargetProperty="MaxHeight"  />
              </Storyboard>
            </BeginStoryboard>
          </EventTrigger.Actions>
        </EventTrigger>

有关更多信息,请参见 演示图板概述

在下图中,鼠标指向第三项:

样式示例屏幕快照

MultiTrigger、DataTrigger 和 MultiDataTrigger

除了 TriggerEventTrigger,还有其他类型的触发器。 MultiTrigger 允许您基于多个条件设置属性值。 如果条件的属性是经过数据绑定的,则可以使用 DataTriggerMultiDataTrigger

共享的资源和主题

典型 Windows Presentation Foundation (WPF) 应用程序可能具有多个在整个应用程序范围内应用的用户界面 (UI) 资源。 总而言之,可以将此资源集视为应用程序的主题。 Windows Presentation Foundation (WPF) 通过使用作为 ResourceDictionary 类封装的资源字典,支持将用户界面 (UI) 资源打包为主题。

Windows Presentation Foundation (WPF) 主题是使用样式设置和模板化机制定义的,Windows Presentation Foundation (WPF) 公开该机制,用于自定义任何元素的可视对象。

Windows Presentation Foundation (WPF) 主题资源存储在嵌入式资源字典中。 这些资源字典必须嵌入到已签名的程序集中,它们既可以嵌入到代码自身所在的程序集中,也可以嵌入到并行程序集中。 对于包含 Windows Presentation Foundation (WPF) 控件的程序集 PresentationFramework.dll,主题资源在一系列并行程序集中。

搜索元素样式时,主题是最后查找的位置。 通常,搜索首先沿元素树向上查找相应资源,然后在应用程序资源集合中查找,最后查询系统。 这为应用程序的开发人员提供了机会,让他们可以在到达主题之前,在树或应用程序级重新定义任何对象的样式。

您可以将各资源字典分别定义为单个文件,这样就可以在多个应用程序中重用某个主题。 通过定义多个提供相同类型资源、但具有不同值的资源字典,也可以创建可交换的主题。 在设计应用程序外观时,建议在应用程序级重新定义这些样式或其他资源。

若要在多个应用程序中共享一组资源(包括样式和模板),可以创建一个 XAML 文件并定义一个 ResourceDictionary。 例如,请查看下图,图中显示了 Styling with ControlTemplates Sample(ControlTemplates 样式设置示例)的一部分:

控件模板示例

如果查看示例中的 XAML 文件,您会注意到所有文件都包含以下内容:

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

这是共享的 shared.xaml,该文件定义一个 ResourceDictionary,该资源字典包含一组样式和画笔资源,使示例中的控件具有了一致的外观。

有关更多信息,请参见合并资源字典

如果要为自定义控件创建主题,请参见控件创作概述中的“外部控件库”一节。

请参见

任务

如何:查找由 ControlTemplate 生成的元素

如何:查找由 DataTemplate 生成的元素

概念

WPF 中的 Pack URI