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






4.65/5 (105投票s)
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 属性的“增强版”。它有一个完整的依赖属性基础结构,为应用程序开发人员提供了一系列功能,让他们的工作更轻松。在本文中,对于我们而言,了解以下关于依赖属性的知识很重要:
- 它们可以通过从
Binding
对象检索值来确定其值 (即,它们可以被绑定)。 - 它们可以参与属性值继承,这意味着如果一个元素上的依赖属性没有值,它将使用逻辑树中祖先元素上该属性的值。这在某种程度上类似于 Windows Forms 中的“环境属性”。
- 它们可以像普通属性一样在 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
会显示它的名字,如下所示:

请注意,上面 XAML 代码片段中的绑定语句没有明确说明它正在设置 Binding
的 Path
属性。标记扩展,例如 Binding
,在 XAML 中设置时可以有一个作为“默认”属性的属性。Binding
的默认属性是 Path
。如果您不喜欢使用这种隐式表示法,下面的 XAML 与前面的代码片段是等效的:
<TextBlock x:Name="horseName" Text="{Binding Path=Name}" />
旋转赛道
在主窗口的 XAML 文件 (Window1.xaml) 中,有一个 Slider
控件,它会影响“赛道”ItemsControl
的旋转角度。ItemsControl
的 LayoutTransform
属性设置为一个 RotateTransform
资源,因此 Slider
必须绑定到该 RotateTransform
才能影响 ItemsControl
的旋转。此绑定的结果是,当用户移动 Slider
中的滑块时,“赛道”会旋转,如下所示:
以下 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
属性绑定到 Slider
的 Value
属性。此 TextBlock
的目的是显示应用于赛道的旋转角度。由于这应该显示一个用户友好的数字,而 Slider
的 Value
属性是 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
元素。它还使用自定义值转换器将 Slider
的 Value
属性转换为整数。以下是值转换器的代码:
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" );
}
}
计算赛马进度指示器的宽度
当一匹赛马“跑完比赛”时,它后面会有一个绿色的进度指示器。该指示器的宽度代表了马完成了比赛的百分比,如下所示:
确定进度指示器的宽度需要两个信息:赛马完成比赛的百分比,以及赛马到达终点线所需的总距离。基于我们到目前为止所了解的 WPF 数据绑定,这似乎是一个问题。如何将元素的宽度绑定到需要两个数字计算的值?这就是 MultiBinding 和 IMultiValueConverter 类型发挥作用的地方。
以下是其工作原理的近似描述:
<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" );
}
}
外部链接
依赖属性
数据绑定
- 数据绑定概述 – 强烈推荐
- 绑定源概述
- Binding 类
- WPF 中管道式值转换器
历史
- 2007 年 4 月 3 日 – 创建了本文。