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

WPF 导览 - 第三部分 (数据绑定)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (105投票s)

2007 年 4 月 3 日

CPOL

8分钟阅读

viewsIcon

621362

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

目录

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

引言

这是关于 Windows Presentation Foundation 的入门系列文章的第三篇。在上一篇文章中,我们探讨了布局面板以及如何使用它们来创建 WPF 用户界面。在本文中,我们将深入探讨数据绑定的世界,以及它如何在 WPF 赛马演示应用程序中使用 (该应用程序可在此系列第一篇文章的顶部下载)。

要全面了解 WPF 数据绑定,请务必参阅页面底部的“外部链接”部分所列的链接。本文涵盖了 WPF 数据绑定的基本要点,并演示了 WPF 赛马应用程序使用数据绑定的各种方式。

背景

用户界面层中的数据绑定并非新鲜事物。它早已存在于各种 UI 平台中,既适用于桌面应用程序,也适用于 Web 应用程序。基本思想是将用户界面中的视觉元素(控件)“绑定”到它们要显示的数据对象。然后,绑定基础结构将负责管理后续的数据交互,从而使 UI 控件的修改能够反映到数据对象中,反之亦然。使用数据绑定的主要好处在于,它减少了应用程序开发人员需要编写的代码量。

一些早期 UI 平台的架构师在将数据绑定与框架的其余部分集成方面做得很好。WPF 的架构师在将数据绑定与框架的各个方面集成方面做得非常出色。WPF 中的数据绑定无处不在且无缝。它如此强大和灵活,以至于它会迫使你改变设计和开发用户界面的思维方式。

通过一个简单的 API,您可以绑定到域/业务对象、XML 数据、视觉元素、ADO.NET 数据容器、集合,基本上任何您能想到的。您可以使用值转换器在绑定的值来回传递时执行任意数据操作。通过创建自定义验证规则并将其应用于绑定,您可以执行数据验证。还有更多。WPF 中的数据绑定确实是一个巨大的进步。

依赖属性

在深入研究 WPF 数据绑定的内部机制和优点之前,有必要先绕道简要讨论一下 WPF 实现这一功能所依赖的另一个基本特性。在 WPF 中,只有依赖属性才能被绑定。

依赖属性就像是普通 .NET 属性的“增强版”。它有一个完整的依赖属性基础结构,为应用程序开发人员提供了一系列功能,让他们的工作更轻松。在本文中,对于我们而言,了解以下关于依赖属性的知识很重要:

  1. 它们可以通过从 Binding 对象检索值来确定其值 (即,它们可以被绑定)。
  2. 它们可以参与属性值继承,这意味着如果一个元素上的依赖属性没有值,它将使用逻辑树中祖先元素上该属性的值。这在某种程度上类似于 Windows Forms 中的“环境属性”。
  3. 它们可以像普通属性一样在 XAML 中设置。

依赖属性的这些方面的重要性稍后将变得清晰,因为我们将研究数据绑定如何使用它们。有关依赖属性的更多信息,请参阅页面底部的“外部链接”部分中的“依赖属性”子部分。

数据上下文 (DataContext)

WPF 中的用户界面元素具有一个 DataContext 依赖属性。该属性启用了上述“值继承”功能,因此如果您将一个元素上的 DataContext 设置为一个 Foo 对象,那么该元素的所有逻辑后代元素上的 DataContext 属性也将引用该 Foo 对象。这意味着,除非显式告知,否则该根元素元素树内包含的所有数据绑定将自动以 Foo 对象为源进行绑定。正如我们很快将看到的,此功能非常有用。

Binding 类

WPF 中的数据绑定围绕着 Binding 类。可以说,Binding 是数据绑定太阳系的“太阳”。

该类具有相当简单直观的 API,但功能非常强大。WPF 赛马演示应用程序并未完全使用 Binding 类的所有功能,但它确实使用了一些常见的功能。让我们来看看我们将在本文后面看到的 Binding 成员。

  • Source - 引用 Binding 的数据源。默认情况下,此对象引用元素的 DataContext 值,该值可能从祖先元素的 DataContext 继承,如上一节所述。如果将此属性设置为非 null 值,则数据绑定操作会将该值视为数据被推送和拉取的位置。
  • Path - 用于指示要从源对象的哪个属性获取和设置绑定的数据值。它是 PropertyPath 类型的属性,因此它支持复杂的路径表达式。
  • ElementName - 可用作前面描述的 Source 属性的替代。它允许您指定要用作数据源的元素的名称。这在将一个元素的属性绑定到另一个元素的属性时非常有用,尤其是在 XAML 中声明绑定时。
  • Converter - 类型为 IValueConverter。您可以将此属性设置为实现该接口的类的实例,以拦截数据从绑定源到绑定目标的任何移动,反之亦然。值转换器非常方便,而且经常使用。

WPF 赛马如何使用数据绑定

既然我们对数据绑定的结构有了一个大致的了解,是时候看看 WPF 赛马演示应用程序是如何使用它的了。我们不会详细介绍应用程序中数据绑定的每一个地方;其中一些最好留到本系列的后续文章中介绍。应用程序中的所有数据绑定逻辑都用 XAML 表示,但请记住,在代码隐藏中完成所有这些也是完全可能的。

显示赛马的名字

打开 RaceHorseDataTemplate.xaml 文件,其中包含 RaceHorse 类的 DataTemplate (如果您还不知道 DataTemplate 是什么,请不用担心,本系列的下一篇文章将介绍该主题)。在该文件中,有一个名为 'horseName' 的 TextBlock,它绑定到 RaceHorse 对象的 Name 属性。以下是我所指 XAML 的简化版本:

<TextBlock x:Name="horseName" Text="{Binding Name}" />

当一个名为“Sweet Fate”的 RaceHorse 在 UI 中显示时,该 TextBlock 会显示它的名字,如下所示:

Displaying a horse's name

请注意,上面 XAML 代码片段中的绑定语句没有明确说明它正在设置 BindingPath 属性。标记扩展,例如 Binding,在 XAML 中设置时可以有一个作为“默认”属性的属性。Binding 的默认属性是 Path。如果您不喜欢使用这种隐式表示法,下面的 XAML 与前面的代码片段是等效的:

<TextBlock x:Name="horseName" Text="{Binding Path=Name}" />

旋转赛道

在主窗口的 XAML 文件 (Window1.xaml) 中,有一个 Slider 控件,它会影响“赛道”ItemsControl 的旋转角度。ItemsControlLayoutTransform 属性设置为一个 RotateTransform 资源,因此 Slider 必须绑定到该 RotateTransform 才能影响 ItemsControl 的旋转。此绑定的结果是,当用户移动 Slider 中的滑块时,“赛道”会旋转,如下所示:

Rotating the race track

以下 XAML 是窗口的骨架大纲,仅显示与此特定绑定相关的标记:

<Window>
  <Grid>
    <Grid.RowDefinitions>
      <!-- The top row is for the race track. -->
      <RowDefinition Height="*" />
      <!-- The bottom row is for the command strip. -->
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Grid.Resources>
      <RotateTransform x:Key="trans" Angle="0" />
    </Grid.Resources>

    <!-- The 'Race Track' area. -->
    <ItemsControl LayoutTransform="{StaticResource trans}" />

    <!-- The 'Command Strip' area -->
    <Border Grid.Row="1">
      <Slider Value="{Binding Source={StaticResource trans}, Path=Angle}" />
    </Border>
  </Grid>
</Window>

显示旋转角度

在主窗口 XAML 中,紧挨着 Slider 的下方是一个 TextBlock,其 Text 属性绑定到 SliderValue 属性。此 TextBlock 的目的是显示应用于赛道的旋转角度。由于这应该显示一个用户友好的数字,而 SliderValue 属性是 double 类型,因此使用值转换器来删除所有小数点 (基本上是将 double 转换为 int)。以下是它的工作原理:

<StackPanel>
  <StackPanel.Resources>
    <local:DoubleToIntegerConverter x:Key="conv" />
  </StackPanel.Resources>
  ...
  <Slider x:Name="rotationSlider" />

  <TextBlock Text="{Binding
    ElementName=rotationSlider,
    Path=Value,
    Converter={StaticResource conv}}"
    />
  ...
</StackPanel>

该绑定使用 ElementName 属性来引用 Slider 元素。它还使用自定义值转换器将 SliderValue 属性转换为整数。以下是值转换器的代码:

public class DoubleToIntegerConverter : IValueConverter
{
 public object Convert(
  object value, Type targetType,
  object parameter, CultureInfo culture )
 {
  return (int)(double)value;
 }

 public object ConvertBack(
  object value, Type targetType,
  object parameter, CultureInfo culture )
 {
  throw new NotSupportedException( "Cannot convert back" );
 }
}

计算赛马进度指示器的宽度

当一匹赛马“跑完比赛”时,它后面会有一个绿色的进度指示器。该指示器的宽度代表了马完成了比赛的百分比,如下所示:

The progress indicator in action

确定进度指示器的宽度需要两个信息:赛马完成比赛的百分比,以及赛马到达终点线所需的总距离。基于我们到目前为止所了解的 WPF 数据绑定,这似乎是一个问题。如何将元素的宽度绑定到需要两个数字计算的值?这就是 MultiBindingIMultiValueConverter 类型发挥作用的地方。

以下是其工作原理的近似描述:

<Border x:Name="racePit">
  <Grid>
    <StackPanel>
      <StackPanel.Resources>
        <local:RaceHorseProgressIndicatorWidthConverter x:Key="WidthConv" />
      </StackPanel.Resources>
      <!-- This Rectangle "follows" a horse as it runs the race. -->
      <Rectangle x:Name="progressIndicator"
        Fill="{StaticResource RaceInProgressBrush}"
        >
        <!-- The progress indicator width is calculated by an instance
             of the RaceHorseProgressIndicatorWidthConverter class. -->
        <Rectangle.Width>
          <MultiBinding Converter="{StaticResource WidthConv}">
            <Binding Path="PercentComplete" />
            <Binding ElementName="racePit" Path="ActualWidth" />
          </MultiBinding>
        </Rectangle.Width>
      </Rectangle>
    </StackPanel>
  </Grid>
</Border>

用于计算进度指示器宽度的多值转换器如下所示:

public class RaceHorseProgressIndicatorWidthConverter : IMultiValueConverter
{
 public object Convert(
  object[] values, Type targetType,
  object parameter, CultureInfo culture )
 {
  int percentComplete = (int)values[0];
  double availableWidth = (double)values[1];
  return availableWidth * (percentComplete / 100.0);
 }
 public object[] ConvertBack(
  object value, Type[] targetTypes,
  object parameter, CultureInfo culture )
 {
  throw new NotSupportedException( "Cannot convert back" );
 }
}

外部链接

依赖属性

数据绑定

历史

  • 2007 年 4 月 3 日 – 创建了本文。
© . All rights reserved.