65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 导览 - 第四部分 (数据模板和触发器)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (105投票s)

2007年4月6日

CPOL

9分钟阅读

viewsIcon

486664

Windows Presentation Foundation 的导览,一次介绍一个功能。

目录

  • 第 1 部分 (XAML):了解 XAML 及其在 WPF 应用程序中的使用方式。
  • 第 2 部分 (布局):了解布局面板及其如何用于构建用户界面。
  • 第 3 部分 (数据绑定):了解 WPF 数据绑定如何工作以及如何使用。
  • 第 4 部分(数据模板和触发器):了解数据模板和触发器如何工作以及如何使用。
  • 第 5 部分 (样式):了解 WPF 中如何设置 UI 样式。

引言

这是介绍 Windows Presentation Foundation 系列的第四篇文章。在上一篇文章中,我们研究了数据绑定及其如何用于在 WPF 用户界面中显示信息。在本文中,我们将研究数据模板和触发器,以及它们如何用于 WPF 赛马演示应用程序(可在本系列第一篇文章顶部下载)。

与本系列中的其他文章一样,本文并未详尽地介绍其主题。相反,我们将仅研究足够的基础知识,以便了解这些功能在演示应用程序中如何使用。如果您想了解有关数据模板和触发器如何使用的更多信息,请参阅“外部链接”部分以获取更多信息。

背景

到目前为止,在本系列文章中,我们已经了解了 XAML、布局面板和数据绑定如何用于创建显示简单数据的用户界面。然而,这些基本构建块是 WPF 更强大和更引人入胜的功能所依赖的基础。

其中一个更高级的功能允许您轻松描述任何类型的对象如何呈现。换句话说,它使您能够在 XAML 中创建可视化元素的模板,这些模板可以在运行时“展开”以呈现数据对象。当然,我所指的功能就是“数据模板”。

数据模板只是 WPF 中一种模板类型。事实上,本文,乃至整个系列文章,都不会探讨其他类型的模板,因为 WPF 赛马演示应用程序不使用它们。如果您有兴趣,还有控件模板、项面板模板和分层数据模板。请参阅本文底部“外部链接”部分的“其他模板”子部分,以获取有关它们的信息链接。

本文介绍的另一个 WPF 功能称为“触发器”。触发器是 WPF 中的另一个基本构建块,框架的许多部分都依赖于它。它们通常是一种有条件地将值应用于属性的方法。触发器在编写 XAML 时特别有用,因为它们提供了一种在运行时评估属性并根据其值执行某些操作的方法。从这个意义上说,触发器介于 XAML 标记的声明性世界和代码隐藏的命令性世界之间,处于一个灰色地带。

DataTemplate 类

WPF 的所有模板功能都基于 FrameworkTemplate 类。DataTemplate 派生自 FrameworkTemplate,所有其他用于模板目的的类也如此。这种模板类之间的家族关系对于典型的 WPF 开发场景来说很大程度上是不相关的,因为在实践中,您将在 XAML 中创建模板,而不会以多态方式使用它们。

在 XAML 中创建 DataTemplate 比以编程方式创建要简单快捷得多。创建模板的代码实际上非常繁琐,而创建相同模板的 XAML 则简洁明了。技术上讲,以编程方式构建数据模板是可能的,但即使是 Microsoft 也不推荐这样做

没有 DataTemplate

在我们深入研究 WPF 赛马应用程序中使用的数据模板之前,让我们先看看一个简单的例子是如何工作的。首先看一个表示河流的简单类

namespace WPF_Test
{
 public class River
 {
  string name;
  int milesLong;

  public string Name
  {
   get { return name; }
   set { name = value; }
  }

  public int MilesLong
  {
   get { return milesLong; }
   set { milesLong = value; }
  }
 }
}

如果我们要将这个类的实例显示在 ContentControl 中,XAML 会像这样

<StackPanel>
  <StackPanel.Resources>
    <local:River x:Key="theRiver" Name="Colorado River" MilesLong="1450" />
  </StackPanel.Resources>
  <ContentControl Content="{StaticResource theRiver }" />
</StackPanel>

该 XAML 创建的用户界面如下所示

Without a data template.

那确实不太令人印象深刻。您在上面看到的是调用 ContentControl 引用的 River 对象上的 ToString() 的结果。让我们花点时间检查一下这里发生了什么。

上面的示例创建了 River 类的一个实例,它代表科罗拉多河。它还创建了一个 ContentControl,被告知以某种方式显示该 River 对象。ContentControl 检查 River 对象并尝试弄清楚如何呈现它,但由于 River 不派生自 UIElement,它无法知道如何做。一旦 ContentControl 没有其他选择,它最终会调用 River 对象上的 ToString(),然后显示该文本。

使用 DataTemplate

既然我们已经看到了没有数据模板的情况下 River 对象有多么无聊,现在是时候加入一个了。这里是之前使用的相同 XAML,只是这次为 River 类提供了一个模板

<StackPanel>
  <StackPanel.Resources>
    <DataTemplate DataType="{x:Type local:River}">
      <Border BorderBrush="Blue" BorderThickness="3" CornerRadius="12">
        <Grid Margin="4">
          <TextBlock>
            <Run Text="The"/>
            <TextBlock Text="{Binding Name}"/>
            <Run Text="is"/>
            <TextBlock Text="{Binding MilesLong}" />
            <Run Text="miles long." />
          </TextBlock>
        </Grid>
      </Border>
    </DataTemplate>

    <local:River x:Key="theRiver" Name="Colorado River" MilesLong="1450" />
  </StackPanel.Resources>

  <ContentControl Content="{StaticResource theRiver}" />
</StackPanel>

这是该 XAML 的渲染效果

With a data template.

当应用数据模板时,River 对象的显示更智能。其中包含的信息现在作为句子的一部分显示,并且该句子被包裹在一个弯曲的蓝色边框中。请记住,上面看到的 River 对象的呈现方式是完全任意的。它可以以适合其所在应用程序的任何方式显示。

触发器

WPF 的另一个经常与模板结合使用的功能是“触发器”。通常,触发器有点像过程代码中的 if 块;它只在某个条件评估为 true 时执行其包含的内容。

例如,这是上一节中 XAML 的修改版本。这次 DataTemplate 中有一个 Trigger,当鼠标光标在 Border 上的任何位置时,它会将 BorderBackground 属性设置为“LightGray”,并将 TextBlockForeground 属性设置为“Red”。

<StackPanel>
  <StackPanel.Resources>
    <DataTemplate DataType="{x:Type local:River}">
      <Border x:Name="bdr"
        BorderBrush="Blue" BorderThickness="3" CornerRadius="12"
        >
        <Grid Margin="4">
          <TextBlock x:Name="txt">
            <Run Text="The"/>
            <TextBlock Text="{Binding Name}"/>
            <Run Text="is"/>
            <TextBlock Text="{Binding MilesLong}" />
            <Run Text="miles long." />
          </TextBlock>
        </Grid>
      </Border>

      <DataTemplate.Triggers>
        <Trigger SourceName="bdr" Property="IsMouseOver" Value="True">
         <Setter TargetName="bdr" Property="Background" Value="LightGray"/>
         <Setter TargetName="txt" Property="Foreground" Value="Red"/>
        </Trigger>
      </DataTemplate.Triggers>
    </DataTemplate>

    <local:River x:Key="theRiver" Name="Colorado River" MilesLong="1450" />
  </StackPanel.Resources>

  <ContentControl Content="{StaticResource theRiver}" />
</StackPanel>

当使用该 XAML 时,当光标在 River 视觉效果上方时,UI 看起来像这样

Mouse over river template with trigger.

当光标移开 River 视觉元素时,背景和前景色会自动恢复到之前的值。触发器为我们处理了所有这些功能,如下所示

Triggers undo their settings when not applied.

触发器有多种,每种都有不同的方式来确定何时执行。在本文的其余部分,我们将只关注 WPF 赛马演示应用程序中使用的两种触发器类型:DataTriggerMultiDataTrigger

触发器可以用于模板之外的其他地方,例如在 Style 中和任何 FrameworkElement 子类上,但本文仅展示了它们在 DataTemplate 中的用法。有关触发器的更多信息,请参阅本文底部“外部链接”部分的“触发器”子部分。

WPF 赛马如何使用数据模板和触发器

WPF 赛马演示应用程序有一个自定义的 DataTemplate,用于渲染 RaceHorse 类的实例。模板声明可以在 RaceHorseDataTemplate.xaml 文件中找到。本文没有显示完整的模板 XAML,因为它相当长,并且完全显示出来没有任何意义。相反,我们将分段检查它。

赛马的视觉效果

RaceHorse 数据模板中的视觉元素可以归结为这个基本结构

<Border>
  <Grid>
    <StackPanel Orientation="Horizontal">
      <!-- This Rectangle is the "progress indicator"
           which follows the horse. -->
      <Rectangle />
      <!--This Image displays the picture of a race horse. -->
      <Image />
    </StackPanel>

    <!-- Displays the horse's name. -->
    <TextBlock />
  </Grid>
</Border>

以下是模板中的元素如何与您在赛马比赛中看到的内容相对应的视觉解释

A visual mapping of the data template to its resultant visuals.

注意: 上图中代表 StackPanel 的黄色矩形,只是为了清晰起见而添加的。应用程序运行时它实际上不会显示在屏幕上。)

Border 元素定义了 RaceHorse 的“赛道”边界(它在其中奔跑的区域)。Border 包含一个 Grid 面板,该面板又包含一个 StackPanel 和一个 TextBlockStackPanel 包含 RaceHorse 的进度指示器和一匹马的图片。使用 StackPanel 来容纳这两个元素,这样当 Rectangle(即进度指示器)变宽时,Image 将随之向右移动。

TextBlock 显示 RaceHorse 的名称。由于 TextBlockStackPanel(词法上)之下声明,因此它将相对于 z 轴在 StackPanel 之上呈现。这确保了马匹的名称始终可见。

设置比赛获胜者的属性

声明模板的视觉效果后,还有一些触发器声明。其中一个是 DataTrigger。当提供的 Binding 返回特定值时,DataTrigger 会执行其内容。以下是该触发器的 XAML

<!-- Set special values for the winner of the race. -->
<DataTrigger Binding="{Binding IsWinner}" Value="True">
    <Setter TargetName="progressIndicator"
            Property="Fill" Value="{StaticResource WinnerBrush}" />

    <Setter TargetName="horseName"
            Property="Foreground" Value="Black" />

    <Setter TargetName="horseName"
            Property="FontWeight" Value="Bold" />

    <Setter TargetName="horseName"
            Property="HorizontalAlignment" Value="Center" />
</DataTrigger>

DataTrigger 等待模板化的 RaceHorseIsWinner 属性从 false 更改为 true。当发生这种情况时(即 RaceHorse 赢得比赛),其中包含的所有 Setter 都将执行。

Setter 类提供了一种为属性赋值的方法。在 XAML 中使用时尤其方便,但也可以在代码隐藏中使用。当 RaceHorse 赢得比赛时,Setter 会修改模板的视觉效果,使进度指示器以金色画笔渲染,其名称在视觉上脱颖而出。

在下面的截图中,名为“Fresh Spice”的 RaceHorse 赢得了比赛

Fresh Spice won the race.

当下一场比赛开始并且 Fresh Spice 的 IsWinner 属性被设置回 false 时,受该触发器影响的属性将自动恢复到它们之前的值。

如果您好奇 DataTrigger 如何知道 IsWinner 属性值何时更改,答案在于 RaceHorse 实现了 INotifyPropertyChanged。当 RaceHorse 对象的 IsWinner 属性值更改时,会引发其 PropertyChanged 事件。Binding 类知道 INotifyPropertyChanged 接口,并在 IsWinner 属性的 PropertyChanged 事件引发时收到通知。

逐渐消失的落败赛马

当一匹赛马完成比赛但未能获得第一名时,它会变成红色,然后逐渐消失。我所说的“逐渐消失”,是指 RaceHorseBorder 元素的 Opacity 属性在四分之三秒内动画到 60%。本文不涉及 WPF 对动画的支持,但重要的是要知道动画可以响应触发器的执行而启动。

为了改变输掉比赛的 RaceHorse 的颜色并使其逐渐消失,触发器必须知道两点信息:RaceHorse 是否已经完成比赛,以及它是否输掉了比赛。这种“AND”表达式可以使用 MultiDataTrigger 实现,如下所示

<!-- This MultiDataTrigger affects losers of the race. -->
<MultiDataTrigger>
  <MultiDataTrigger.Conditions>
    <Condition Binding="{Binding IsFinished}" Value="True" />
    <Condition Binding="{Binding IsWinner}" Value="False" />
  </MultiDataTrigger.Conditions>

  <!-- Apply the "finished the race" brush to
       the horse's progress indicator. -->
  <Setter TargetName="progressIndicator"
          Property="Fill" Value="{StaticResource FinishedBrush}" />

  <!-- Fade the race pit in and out if the horse lost the race. -->
  <MultiDataTrigger.EnterActions>
    <!-- Fade away the RaceHorse's Border element when it loses a race. -->
  </MultiDataTrigger.EnterActions>

  <MultiDataTrigger.ExitActions>
    <!-- Fade in the RaceHorse's Border element when a new race starts. -->
  </MultiDataTrigger.ExitActions>
</MultiDataTrigger>

外部链接

DataTemplates

触发器

其他模板

历史

  • 2007 年 4 月 6 日 – 创建文章。
© . All rights reserved.