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

WPF 数据绑定 - 第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (157投票s)

2008 年 9 月 3 日

CPOL

12分钟阅读

viewsIcon

577735

downloadIcon

11910

WPF 数据绑定的简介。

引言

传统上,在 Windows Forms 和 ASP.NET 应用程序中,数据绑定主要用于用信息填充屏幕上的元素;有简单的数据绑定用于显示单个值,以及复杂的数据绑定用于显示和格式化数据集合。数据绑定的美妙之处在于,您可以在编写少量甚至不写代码的情况下填充界面。通过 WPF 中的数据绑定,您可以获取几乎任何对象几乎任何属性的数据,并将其绑定到另一个对象的几乎任何其他依赖属性。

在本系列两部分文章的第一部分中,我将介绍 WPF 中数据绑定的基础知识。在第二部分中,将介绍更高级的数据绑定概念,例如模板以及根据数据特征改变信息显示方式。对于这两部分,您都需要对 WPF 有基本的了解。

必备组件

本文假定读者已对以下 WPF 概念有基本了解

  • WPF 基础知识
  • 依赖属性
  • 依赖属性继承
  • 资源
  • 标记扩展

基础

最基本的形式是,数据绑定将一个值从一个“源”对象复制到“目标”对象上的一个属性。源属性可以是任何属性。目标将是一个依赖属性。将 WPF 元素属性作为源绑定与将其他对象类型作为源绑定的一个关键区别是 WPF 对象具有“更改通知”;一旦与 WPF 元素属性作为源进行绑定,当源发生更改时,目标属性将自动更新。我们来看几个非常简单的 WPF 数据绑定表达式。

<Window x:Class="Example00.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid  >   
        <Grid.RowDefinitions >
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBox Name="mySourceElement" Grid.Row="0" >Hello World</TextBox>
        <TextBlock Grid.Row="1">            
            <TextBlock.Text>
                <Binding ElementName="mySourceElement" Path="Text"  />
            </TextBlock.Text>
        </TextBlock> 
        
        <TextBlock Text="{Binding ElementName=mySourceElement,Path=Text }" Grid.Row="2" />
    </Grid>
</Window>
示例 00

正如您可能猜到的,当您运行此示例时,它将显示“Hello Word”。文本显示了三次。文本的第一个实例显示是因为它是分配给文本框的字面值。接下来的两个实例在文本框中,是数据绑定的结果。两个文本框都创建了一个以名为“mySourceElement”的文本框为源的绑定,并以它们的 Text 属性作为目标。虽然两个文本块绑定到同一个源并产生相同的结果,但使用的语法不同。第一个文本块使用扩展语法,而第二个使用标记扩展来表达相同的绑定。在本文档中,我将使用扩展标记,但请记住,在大多数情况下,两种语法都可以使用。这是一个更有趣的数据绑定示例

属性

描述

转换器

设置要使用的转换器。

ElementName

要进行绑定的元素的名称。

FallbackValue

设置绑定无法返回值时使用的值。

模式

设置绑定的方向。

Path

用作数据源的元素属性的路径。

RelativeSource

通过指定相对于当前元素的元素来设置绑定源。

用于绑定的源对象。

StringFormat

指定元素绑定到字符串属性时,字符串表示形式的值的格式。

UpdateSourceTrigger

设置绑定将发生的事件。

ValidationRules

应用于绑定的验证规则的集合。

<Window x:Class="Example01.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Example 01" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions >
            <RowDefinition Height="40px" />
            <RowDefinition Height="40px" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Slider Name="fontSizeSlider" Minimum="5" 
           Maximum="100" Value="10" Grid.Row="0" />
        <TextBox Name="SizeTextBox" 
           Text="{Binding ElementName=fontSizeSlider, Path=Value}" Grid.Row="1"/>
        <TextBlock Text="Example 01" 
           FontSize="{Binding ElementName=SizeTextBox,  Path=Text}"  Grid.Row="2"/>
    </Grid>
</Window>
示例 01

在此示例中,有三个元素绑定在一起。一个 TextBoxText 属性绑定到一个 SliderValue 属性,而一个 TextBlockFontSize 属性绑定到 TextBoxText 属性。如果您运行该程序,您将看到这种链式关系的效果。更改滑块会导致文本框中显示的数字和字体大小相应调整。WPF 能够应用更改,因为元素属性实现了 INotifyPropertyChanged 接口。顾名思义,当属性值发生变化时,WPF 会收到通知,并知道要重新将值应用到目标属性。

另外,请注意,如果您在文本框中单击并手动更改其值,则在您离开文本框后,滑块和文本块都会立即更新。在此示例中,数据似乎双向流动(从滑块到文本框,从文本框到滑块)。数据绑定的这种行为通过绑定模式进行控制。

绑定模式

绑定模式设置数据流动的方向。数据通常从源流向目标。共有五种数据绑定模式。

模式 描述
默认值
OneWayToSource OneWay 的反向版本。
OneWay 从源到目标。
OneTime 属性已初始设置,但源的更新不会复制到目标。
TwoWay 源和目标的更改会相互复制。

OneWayToSource 模式乍一看似乎是多余的。由于它反转了复制数据的方向,所以起初,人们可能会认为通过反转绑定表达式所附加的元素可以实现相同的功能。由于目标元素必须是依赖属性,因此并非始终如此。当我们需要用依赖属性的值设置非依赖属性时,您将需要使用 OneWayToSource 模式。

控制何时发生绑定

正向(从源到目标)的绑定会立即发生。当您更改滑块、文本框中的数字值或组合框值时,文本块的更改会立即发生。但是,您可能已经注意到,当您在文本框中输入数字值时,滑块直到文本框失去焦点才更新。默认情况下,反向进行的绑定不会立即发生。UpdateSourceTrigger 属性可用于设置此行为。

名称

描述

默认值

对于大多数属性,行为与 PropertyChanged 相同。对于 TextBox.Text,这与 LostFocus 相同。默认行为由属性上的元数据控制。

Explicit (显式)

在调用 UpdateSource() 之前不发生绑定。

LostFocus

在目标失去焦点时更新源。

PropertyChanged

绑定立即发生。

绑定到非元素对象的公共属性

到目前为止,我们已经研究了绑定到 WPF 元素。当绑定到非元素属性时,绑定表达式的 ElementName 属性必须设置为以下值之一。

名称

描述

数据上下文 (DataContext)

* 如果未使用 SourceRelativeSource,WPF 将向上搜索元素树,直到遇到 DataContext 对象。一旦找到非空的 DataContext,该对象将用于绑定。适用于将多个属性绑定到同一对象。

RelativeSource

用于相对于当前元素标识源对象。适用于控件模板和数据模板。(数据模板将在本文的第二部分中介绍。)

绑定表达式中的引用是指提供数据的对象。

* - 实际上不会发生运行时搜索。DataContext 属性会继承其值。

如果在绑定表达式中未定义源,则假定源为数据上下文。当多个元素绑定到同一源对象时,数据上下文的隐式使用可以节省大量键入。无需为所有绑定到同一源的元素指定数据源,我们可以将源对象分配给某个父对象的数据上下文。

<Window.Resources>
     <local:Employee 
        x:Key="MyEmployee" EmployeeNumber="123" FirstName="John" 
       LastName="Doe" Department="Product Development" Title="QA Manager" 
    />
</Window.Resources>
<Grid DataContext="{StaticResource MyEmployee}">
    <TextBox Text="{Binding Path=EmployeeNumber}"></TextBox>
    <TextBox Text="{Binding Path=FirstName}"></TextBox>
    <TextBox Text="{Binding Path=LastName}" />
    <TextBox Text="{Binding Path=Title}"></TextBox>
    <TextBox Text="{Binding Path=Department}" />
</Grid>
示例 03

绑定到集合

依赖属性支持单值绑定。要绑定到对象集合,您的目标对象必须派生自 ItemsControlItemsControl 类中有三个对数据绑定很重要的属性。

名称

描述

DisplayMemberPath

标识源对象上要显示的属性。如果未指定,WPF 将调用类上的 ToString 方法。除非重写了 ToString,否则它只会显示对象的类。

ItemsSource

标识包含要显示的对象的集合。

ItemsTemplate

接受用于自定义对象显示方式的模板。模板将在本文的第二部分中介绍。

您可以绑定到实现 IEnumerable 接口的任何集合。.NET Collection 类和 Array 类实现了此接口。使用 IEnumerable,您将能够执行只读绑定。要能够编辑数据,必须满足其他要求。

对于此示例,代码绑定到一个 Employee 对象集合。List 控件(继承自 ItemsControl)显示员工姓名列表。选择其中一个员工将导致他们的信息显示在旁边的详细信息区域。为了处理详细信息的显示,我们将利用前面部分提到的 DataContext 对象。详细信息区域中的所有字段都将绑定到同一对象(但不同属性)。所有详细信息字段都在同一个网格控件内。网格控件上的数据上下文绑定到员工列表中选定的项。

<TextBox Text="{Binding Path=FirstName}" />
<TextBox Text="{Binding Path=LastName}" />
<TextBox Text="{Binding Path=Title}" />
<TextBox Text="{Binding Path=Department}" />
示例 04

当列表中的选定项发生更改时,数据上下文会自动更新。当 DataContext 更新时,详细信息区域中绑定到 DataContext 的字段也会更新。

如果在绑定后以编程方式将元素插入或从集合中删除,界面将不会更新。为了使界面能够自动响应插入和删除,界面需要绑定到实现 INotifyCollectionChanged 接口的列表。WPF ObservableCollection 类实现了 INotifyCollectionChanged。对于我们的示例,将数据集合从 List<Employee> 更改为 ObservableCollection<Employee> 是确保界面在将员工插入或从列表中删除时更新所需的唯一更改。示例 5 使用 ObservableCollection 而不是通用 List,并且有一个用于输入新员工信息的表单。当新员工添加到列表中时,ListBox 会自动更新以显示新员工。

绑定到 DataTable

绑定到表与绑定到列表类似。在 WPF 中,您不能直接绑定到 DataTable,而必须绑定到 DataView(这应该不是什么大问题,因为所有表在 DefaultView 属性中都有一个视图)。此限制在 Windows Forms 中也存在,但对开发人员是隐藏的。由于 DataTable 实现了 IBindingList 接口,WPF 能够检测到何时添加了新行。删除项目时,请确保不要通过从数据集中删除行来实现删除功能。而是通过调用 Row.Delete() 将行标记为已删除(这将导致视图不返回该行)。

值转换

有时,您需要对正在绑定的数据进行更改,以便它更适合在屏幕上显示。例如,如果您的源数据有一个属性包含图像路径,您可能希望显示图像而不是图像路径。值转换器负责执行此任务。

要创建值转换器,请声明一个继承自 IValueConverter 的类。该类需要应用 ValueConversion 属性。该属性接受两个参数,源数据的类型和转换到的类型。实现 ConvertConvertBack 方法以执行转换。对于图像转换器的情况,我们不需要能够将图像转换回其加载的路径,因此,而不是实现 ConvertBack,我们可以抛出 NotImplemented 异常。WPF 将抑制异常(稍后将详细介绍 WPF 异常抑制)。

IValueConverters 可以以一些颇具创造性的方式使用。假设您正在显示销售数字列表,并且想要通过更改文本颜色来突出显示高于或低于特定级别的数字。值转换器可用于此处,将值“转换”为适当的颜色。对于以下代码示例,值转换器用于将华氏温度转换为摄氏度,并更改温度文本的颜色以指示温度是热还是冷。相同的转换器用于摄氏度和华氏温度。为了使转换器适用于两种数值范围,转换器会公开属性,允许设置热和冷的限值。

<Window.Resources>
    <local:FarenheitToCelciusConverter x:Key="myTemperatureConverter" />
    <local:TemperatureToColorConverter x:Key="myFRangeIndicator" 
           HotTemperature="80" ColdTemperature="65" />
  <local:TemperatureToColorConverter x:Key="myCRangeIndicator" 
         HotTemperature="26.6" ColdTemperature="18.8" />
</Window.Resources>
<TextBox Name="txtFarenheit" 
         Foreground="{Binding Path=Text, ElementName=txtFarenheit, 
         Converter={StaticResource myFRangeIndicator}}"
/>
<TextBox Name="txtCelcius" 
         Text="{Binding  UpdateSourceTrigger=PropertyChanged, 
                Path=Text,ElementName=txtFarenheit, 
                Converter={StaticResource myTemperatureConverter}}" 
                Foreground="{Binding Path=Text, ElementName=txtCelcius, 
                Converter={StaticResource myCRangeIndicator}}" 
/>
示例 06
[ValueConversion(typeof(double), typeof(double))]
public class FarenheitToCelciusConverter : IValueConverter
{
    #region IValueConverter Members
    public object Convert(object value, Type targetType, 
           object parameter, System.Globalization.CultureInfo culture)
    {
        string sourceValue = value.ToString();
        double decimalValue = 0;
        if (Double.TryParse(sourceValue, out decimalValue))
        {
            return (decimalValue - 32.0) * (5.0 / 9.0);
        }
        return value;
    }
 
    public object ConvertBack(object value, Type targetType, 
           object parameter, System.Globalization.CultureInfo culture)
    {
        string sourceValue = value.ToString();
        double decimalValue = 0;
        if (Double.TryParse(sourceValue, out decimalValue))
        {
            return (decimalValue * (9.0 / 5.0)) + 32.0;
        }
        return value;
    }
    #endregion
}
示例 06 值转换器代码

验证

为了防止用户输入无效数据,您的类可能包含逻辑来阻止设置无效值。但是,如果在设置无效值时抛出异常,用户将不会收到解释值无效的反馈。WPF 会自动抑制绑定过程中发生的异常。为了提供用户对验证问题的反馈,WPF 有一个名为 Validation Rules 的机制。内置的验证规则之一是 ExceptionValidationRule。下面显示了应用此规则的标记。应用后,用户将收到有关绑定过程中发生的异常的通知。

<Binding Path="EventDate"  UpdateSourceTrigger="PropertyChanged">
   <Binding.ValidationRules>
      <ExceptionValidationRule />
   </Binding.ValidationRules>
</Binding>
示例 07

您可以创建自己的验证规则,方法是继承 ValidationRule 类并重写 Validate 方法。如果值有效,则应返回一个 ValidationResult 对象,并将其 IsValid 属性设置为 true。否则,此值应设置为 false,并且其 ErrorContent 属性应包含失败的详细信息。验证在转换之前发生,因此您的验证逻辑必须针对未转换的值编写。

如果您想要一个在绑定过程中遇到验证错误的元素列表,可以遍历表单层次结构,测试每个元素的 HasError 属性。如果元素有错误,则可以通过调用其 GetErrors() 方法来获取它遇到的错误集合。

下一节...

在下一部分关于数据绑定的两部分系列文章中,我将介绍更多关于验证和规则的详细信息,并介绍数据模板(用于自定义 ItemControl 中信息的显示方式)、数据源(允许从其他源拉取数据)和数据视图(用于排序、过滤和分组数据)。在继续阅读本文的第二部分之前,我建议您多练习 WPF 数据绑定。

WPF 数据绑定 - 第 1 部分 - CodeProject - 代码之家
© . All rights reserved.