4.精通数据绑定

在本章中,我们将研究用于将数据源连接到 UI 控件的数据绑定语法。 我们将研究如何声明依赖属性,以及执行此操作时拥有的所有各种选项。 我们将了解声明的绑定的范围并揭示数据模板的更详细的细节。

WPF 中的数据绑定使其能够与 MVVM 模式完美配合。 它为视图和视图模型组件之间的双向通信提供连接。 然而,与使用传统的 UI 到业务逻辑通信方法相比,这种抽象通常会导致混乱,并使问题追踪变得更加困难。

由于数据绑定是 MVVM 模式的重要组成部分,因此我们将全面介绍该主题,从基础知识到高级概念,并且我们将确保能够满足我们可能收到的任何绑定要求。

数据绑定基础

在 WPF 中,我们使用 Binding 类来创建绑定。 一般来说,可以公平地说,每个装订都包含四个组成部分。 现在让我们来看看它们:

  • 首先是绑定源; 通常,这将是我们的视图模型之一。

  • 第二个是从我们想要数据绑定的源对象到属性的路径。

  • 第二个是绑定目标; 这通常是一个 UI 控件。

  • 第四个是我们想要数据绑定的绑定目标的属性的路径。

如果我们的绑定之一不起作用,则很可能是这四件事之一没有正确设置。 需要强调的是,目标属性通常来自 UI 控件,因为数据绑定规则规定绑定目标必须是依赖属性。 大多数 UI 控件的属性都是依赖属性,因此,此规则只是强制数据通常从视图模型数据源到绑定目标 UI 控件的方向传输。

我们将在本章后面讨论数据绑定数据遍历的方向,但首先让我们关注用于指定 Binding.Path 属性值的语法。

绑定路径语法

绑定可以用普通方式声明,在 XAML 中定义实际的 Binding 元素,也可以用速记方式声明,使用由 XAML 翻译为 Binding 元素的标记语言。 我们将主要关注速记符号,因为这是我们在整本书中主要使用的符号。

Binding.Path 属性的类型为 PropertyPath。 此类型支持可以使用 XAML 标记扩展在 XAML 中表达的独特语法。 虽然有时可能会令人困惑,但我们可以学习一些特定的规则来使其变得更容易。 我们来调查一下。

首先,让我们了解绑定路径是相对于绑定源的,并且绑定源通常由 DataContext 属性或路径本身设置。 为了绑定到整个绑定源,我们可以像这样指定我们的绑定:

{
   Binding Path=.}

也可以这样指定:

{
   Binding .}

最简单的是,我们可以像这样指定我们的绑定:

{
   Binding}

请注意,当首先声明路径值时,在此语法中显式声明 Path 属性名称是可选的。 前面的三个例子都是相同的。 为了简洁起见,我们将在本书的绑定中省略 Path 属性声明。 现在让我们看看迷你语言的剩余属性路径语法。

为了将数据绑定到大多数属性路径,我们使用与代码中使用的相同的符号。 例如,当直接绑定到数据绑定对象的属性时,我们只需使用属性名称:

{
   Binding PropertyName}

为了将数据绑定到由绑定源的属性直接引用的对象的属性,我们再次使用与代码中相同的语法。 这称为间接属性定位:

{
   Binding PropertyName.AnotherPropertyName}

同样,当数据绑定到集合中的项目或集合项目的属性时,我们使用代码中的索引表示法。 例如,这是我们从数据绑定绑定源中的第一项访问属性的方式:

{
   Binding [0].PropertyName}

当然,如果我们想要访问第二项,我们使用键值 1,如果我们想要访问第三项,则使用键值 2,依此类推。 同样,为了间接定位集合项的属性(其中集合是绑定源的属性),我们使用以下语法:

{
   Binding CollectionPropertyName[0].PropertyName}

正如您所看到的,我们可以自由地组合这些不同的语法选项来生成更复杂的绑定路径。 多维集合的访问方式也与我们在代码中引用它们的方式相同:

{
   Binding CollectionPropertyName[0, 0].PropertyName}
{
   Binding CollectionPropertyName[0, 0, 0].PropertyName}
...

在讨论到集合的数据绑定时,请注意有一种特殊的正斜杠(/)语法,我们可以随时使用它来访问所选项目:

{
   Binding CollectionPropertyName/PropertyName}

此特定示例将绑定到由 CollectionPropertyName 属性指定的集合的当前项的 PropertyName 属性。 让我们快速看一个更实际的例子:

<StackPanel>
    <ListBox ItemsSource="{Binding Users}"
             IsSynchronizedWithCurrentItem="True" />
    <TextBlock Text="Selected User's Name:" />
    <TextBlock Text="{Binding Users/Name}" />
</StackPanel>

在这个使用 UsersViewModel 的基本示例中,我们将 Users 集合数据绑定到列表框。 在下面,我们输出当前所选项目的 Name 属性的值。 请注意 IsSynchronizedWithCurrentItem 属性的设置,如果没有它,此正斜杠绑定将无法正常工作。

尝试从示例中删除 IsSynchronizedWithCurrentItem 属性并再次运行应用程序,您将看到当前用户的名称最初将被输出,但在更改所选项目后不会更新。

将此属性设置为 True 将确保每次选择更改时都会更新 ListBox.Items 集合中的 ItemCollection.CurrentItem 属性。 请注意,我们还可以使用 ListBox.SelectedItem 属性而不是此正斜杠符号来实现相同的输出:

<StackPanel>
    <ListBox Name="ListBox" ItemsSource="{Binding Users}"
             IsSynchronizedWithCurrentItem="True" />
    <TextBlock Text="Selected User's Name:" />
    <TextBlock Text="{Binding SelectedItem.Name, ElementName=ListBox}" />
</StackPanel>

现在不需要 IsSynchronizedWithCurrentItem 属性来更新 TextBlock 中所选用户的名称,因为 SelectedItem 属性将处理该问题。 但是,在这种情况下将其设置为 True 将确保选择 ListBox 中的第一个项目,并且 TextBlock 最初将输出该项目的用户的名称。 让我们继续看看正斜杠符号。

如果您尝试将数据绑定到集合中项目的属性,其中该集合本身就是父集合的项目,我们可以在单个绑定路径中多次使用正斜杠表示法:

{
   Binding CollectionPropertyName/InnerCollectionPropertyName/PropertyName}

为了澄清,此路径将绑定到 InnerCollectionPropertyName 属性指定的集合的选定项的 PropertyName 属性,该属性本身就是 CollectionPropertyName 属性指定的集合的选定项。

现在让我们从集合转到附加属性。 为了将数据绑定到附加属性,我们需要使用与代码中使用的语法略有不同的语法; 我们需要将属性名称与类名称一起括在括号中:

{
   Binding (ClassName.PropertyName)}

请注意,当附加属性是自定义声明的属性时,我们必须在括号内包含 XAML 命名空间前缀及其分隔冒号:

{
   Binding (XmlNamespacePrefix:ClassName.PropertyName)}

通常,当绑定到附加属性时,我们还需要指定绑定目标以及目标属性。 绑定目标通常是设置绑定的对象,或者是另一个 UI 元素,因此我们倾向于看到在这些情况下使用relativeSourceElementName属性:

{
   Binding Path=(Attached:TextBoxProperties.Label),  
 			  RelativeSource={
   RelativeSource AncestorType={
   x:Type TextBox}}}

我们将在本书后面看到此示例的扩展版本,但简而言之,它绑定到 TextBox 类型的父控件的 TextBoxProperties.Label 附加属性。 它是从 ControlTemplate 中调用的,因此父文本框是数据绑定控件的模板化父控件。

转义无效字符

当使用 PropertyPath 语法迷你语言时,可能会出现奇怪的情况,我们需要转义语法中使用的某些字符。 一般来说,反斜杠(\)用作转义字符,我们需要转义的字符如下。

我们在绑定路径中可能需要转义的最常见字符是右大括号 (}),它表示标记部分的结束。 另外,如果您需要在绑定路径中使用实际的反斜杠,则必须在其前面加上另一个反斜杠来转义它。

我们需要转义的唯一两个其他字符是等号 (=) 和逗号字符 (,),它们都用于定义绑定路径。 我们可能在绑定路径中使用的所有其他字符都被视为有效。

请注意,如果我们需要在索引器绑定表达式内转义字符,则可以使用特殊字符。 在这些情况下,我们需要使用脱字符号 (^) 作为转义字符,而不是使用反斜杠字符。

另请注意,在 XAML 中显式声明绑定时,我们需要通过将与号 (&) 和大于号 (>) 替换为其 XML 实体形式来转义它们。 如果需要使用这些字符,请将 & 符号替换为 & 并将大于号替换为 &gt

探索绑定类

Binding 类的属性比我们在这里讨论的要多,但我们将很快详细介绍最重要的属性,并简要介绍其他值得注意的属性。 Binding 类是每个绑定中的顶级类,但在内部它使用较低级别的类来维护绑定源和绑定目标之间的连接。

BindingExpression 类是该基础对象。 使用 MVVM 时,开发人员通常不会访问此内部类,因为我们倾向于将功能保留在视图模型中。 但是,如果我们正在编写自定义控件,那么了解它可能会很有用。

在某些情况下,它可用于以编程方式更新关联的绑定源,我们将在本章后面了解这一点。 现在,让我们关注 Binding 类可以为我们做什么。

在 .NET 4.5 中,Binding 类中添加了一个很棒的新属性。 Delay 属性使我们能够指定在绑定目标属性值发生更改后延迟更新绑定源的时间(以毫秒为单位)。

例如,如果我们要执行一些繁重的计算验证或其他依赖于用户在 TextBox 元素中的输入的处理,这非常有用。 为了进一步阐明此功能,每次数据绑定属性值更改或在我们的示例中每次按下按键时,实际上都会重新启动此延迟。 它通常用于在每次用户暂停或完成输入时以块的形式更新绑定源,有点像缓冲:

<TextBox Text="{Binding Description,UpdateSourceTrigger=PropertyChanged, Delay=400}" />

FallbackValue 属性是另一个在性能方面有用的属性。 为了从每个绑定返回一个值,WPF 框架最多执行四件事。 第一个是简单地使用数据绑定值验证目标属性类型。 如果成功,它将尝试解析绑定路径。

大多数时候,这会起作用,但如果不起作用,它将尝试找到一个转换器来返回该值。 如果找不到,或者找到的转换器返回 DependencyProperty.UnsetValue 值,则它将查看 FallbackValue 属性是否有可提供的值。 如果没有回退值,则需要查找来查找目标依赖属性的默认值。

通过设置 FallbackValue 属性,我们可以做两件事来提高性能,尽管幅度很小。 首先,它将阻止 WPF 框架执行目标依赖属性的默认值的查找。 第二个是它会阻止跟踪语句被馈送到 Visual Studio 中的“输出”窗口以及已设置的任何其他跟踪输出。

TargetNullValue 属性与 FallbackValue 属性类似,它使我们能够在绑定源没有数据绑定值时提供一些输出。 不同之处在于,当无法解析数据绑定值时,会输出 FallbackValue 属性值,而当成功解析的数据绑定值为 null 时,会使用 TargetNullValue 属性值。

我们可以使用此功能来显示比 null 更人性化的值,甚至可以在文本框控件中提供默认消息。 为此,我们可以将数据绑定字符串属性设置为 null,并为 TargetNullValue 属性设置合适的值:

<TextBox Text="{Binding Name, TargetNullValue='Please enter your name'}" />

当然,此消息实际上会出现在 TextBox 控件中,因此它不是提供此功能的理想方式。 我们将在本书后面看到一个更好的示例,但是现在,让我们继续探索 Binding 类。

如果我们的视图模型中有任何异步访问其数据的属性,或者如果它们是通过繁重的计算过程计算的,那么我们需要在绑定上将 IsAsync 方法设置为 True

<Image Source="{Binding InternetSource,
               IsAsync=True,
               FallbackValue='pack://application:,,,/CompanyName.ApplicationName;
               component/Images/Default.png'}" />

这可以防止 UI 在等待计算或以其他方式解析数据绑定属性时被阻塞。 在解析绑定源之前,将使用回退值(如果已设置),否则将使用默认值。 在此示例中,我们提供要显示的默认图像,直到从互联网下载实际图像并解析绑定源为止。

Binding 类的另一个有用的属性是 StringFormat 属性。 正如名称所暗示的,这在内部使用 string.Format 方法来格式化我们的数据绑定文本输出。 然而,使用此功能有一些注意事项。 首先,我们只能使用单个格式项,即由普通绑定中的单个数据绑定值表示。 我们将在本章后面了解如何使用多个值。

其次,我们需要仔细声明我们的格式,因为标记扩展使用大括号,并且我们不能使用双引号字符 ("),因为绑定已经在双引号内声明。一种解决方案是使用单引号将其括起来 我们的格式字符串:

<TextBlock Text="{Binding Price, StringFormat='{0:C2}'}" />

另一种选择是通过在格式前面加上一对花括号来转义格式

<TextBlock Text="{Binding Price, StringFormat={}{0:C2}}" />

现在,这里已经讨论了大多数有用的绑定属性,但应该注意的是,Binding 类中有许多属性在使用 MVVM 构建 WPF 应用程序时通常不会使用。 这是因为它们涉及事件处理程序,而我们在使用 MVVM 时通常不会实现事件处理程序。

例如,NotifyOnSourceUpdatedNotifyOnTargetUpdatedNotifyOnValidationError 三个属性与 Binding.SourceUpdatedBinding.TargetUpdatedValidation.Error 附加事件的引发相关。

同样,三个 ValidatesOnDataErrorsValidatesOnExceptionsValidatesOnNotifyDataErrorsValidationRules 属性都与 ValidationRule 类的使用相关。 这是一种与 UI 非常相关的验证方式,但这将我们的业务逻辑直接放入我们的 Views 组件中。

使用 MVVM 时,我们希望避免这种组件混合。 因此,我们倾向于使用数据元素而不是 UI 元素,因此我们在数据模型和/或视图模型类中执行此类职责。 我们将在本书后面的第 9 章“实现响应式数据验证”中看到这一点,但现在让我们更深入地了解 Binding 类最重要的属性。

引导数据绑定流向

每个绑定中数据遍历的方向由 Binding.Mode 属性指定。 BindingMode 枚举中声明了四个不同的方向实例,外加一个附加值。 我们首先看一下方向值及其代表的含义。

第一个也是最常见的值反映了最常见的情况,其中数据从绑定源(例如我们的视图模型之一)流向由 UI 控件表示的绑定目标。 这种绑定模式称为 One-Way,由 OneWay 枚举实例指定。 此模式主要用于仅显示或只读目的,以及无法在 UI 中更改数据绑定值的情况。

下一个最常见的传输方向由 TwoWay 枚举实例表示,表示数据可以自由地从视图模型传输到 UI 控件,也可以沿相反方向传输。 当数据绑定到表单控件时,当我们希望用户的更改反映在视图模型中时,这是最常用的模式。

第三个定向枚举实例是 OneWayToSource 实例,与 OneWay 实例相反。 也就是说,它指定数据只能从由 UI 控件表示的绑定目标传输到绑定源,例如我们的视图模型之一。 当我们不需要更改数据绑定值时,此模式对于捕获用户输入的日期也很有用。

最终定向实例与 OneWay 实例类似,不同之处在于它仅工作一次并由 OneTime 实例表示。 虽然此模式确实只能工作一次,但在实例化其包含的控件时,它实际上还会在每次设置相关绑定的 DataContext 属性时更新其值。 然而,它的目的是提供比 OneWay 成员更好的性能,并且只适合绑定到不更改的数据,因此如果数据将被更新,这不是正确使用的定向实例。

最终实例名为 Default,正如名称所暗示的那样,它是 Binding.Mode 枚举的默认值。 它指示绑定使用从指定目标属性声明的绑定模式。 当声明每个依赖属性时,我们可以指定默认使用单向绑定模式还是双向绑定模式。 如果没有明确声明,则该属性将被分配单向模式。 我们将在本章后面对此进行更详细的解释。

绑定到不同的来源

我们通常使用 FrameworkElement.DataContext 属性设置绑定源。 所有 UI 控件都扩展了 FrameworkElement 类,因此我们可以在其中任何控件上设置绑定源。 必须设置此项才能使绑定正常工作,尽管它可以在 Path 属性中指定,或者从祖先控件继承,因此不必显式设置。 看一下这个简单的示例,它假设已在父控件上正确设置了合适的绑定源:

<StackPanel>
    <TextBlock DataContext="{Binding User}" Text="{Binding Name}" />
    <TextBlock DataContext="{Binding User}" Text="{Binding Age}" />
</StackPanel>

在这里,我们将第一个 TextBlock 的绑定源设置为 User 对象,以及从该源到 Name 属性的路径。 第二个设置类似,但绑定源路径指向 Age 属性。 请注意,我们已将 DataContext 属性分别设置为每个 TextBox 控件上的 User 对象。

虽然这是完全有效的 XAML,但您可以想象,在我们想要以大表单进行数据绑定的每个控件上执行此操作会有多烦人。 因此,我们倾向于利用 DataContext 属性可以从其任何祖先控件继承其值的事实。 这样,我们可以通过在父控件上设置 DataContext 来简化此代码:

<StackPanel DataContext="{Binding User}">
    <TextBlock Text="{Binding Name}" />
    <TextBlock Text="{Binding Age}" />
</StackPanel>

事实上,在开发每个Window或UserControl时,习惯上在这些顶级控件上设置DataContext,以便每个包含的控件都可以访问相同的绑定源。 这就是为什么我们为每个 Window 或 UserControl 创建一个视图模型,并指定每个视图模型负责提供其相关视图所需的所有数据和功能。

除了设置 DataContext 属性之外,还有几种指定绑定源的替代方法。 一种方法是使用绑定的 Source 属性,这使我们能够显式覆盖从父 DataContext 继承的绑定源(如果已设置)。 使用 Source 属性,我们还可以将数据绑定到资源(如视图模型定位器示例中所示)或静态值,如以下代码片段所示:

<TextBlock Text="{Binding Source={x:Static System:DateTime.Today},
                 Mode=OneTime, StringFormat='{}© {0:yyyy} CompanyName'}" />

另一种方法涉及使用绑定的RelativeSource 属性。 使用RelativeSource 类型的这个非常有用的属性,我们可以指定要使用目标控件或该控件的父控件作为绑定源。

它还使我们能够覆盖 DataContext 中的绑定源,并且在尝试将数据绑定到 DataTemplate 元素中的视图模型属性时通常是必不可少的。 让我们调整之前的用户数据模型的 DataTemplate,以从由 DataTemplate 设置的正常 DataContext 中输出一个属性,并使用relativeSource 类的 AncestorType 属性从设置为父控件的 DataContext 的视图模型中输出一个属性 :

<DataTemplate DataType="{x:Type DataModels:User}">
    <StackPanel>
        <TextBlock Text="{Binding Name}" />
        <TextBlock Text="{Binding DataContext.UserCount,
                         RelativeSource={RelativeSource Mode=FindAncestor,
                         AncestorType={x:Type Views:UserView}}}" />
    </StackPanel>
</DataTemplate>

请注意,设置 Mode 属性(指定绑定源与绑定目标相比的相对位置)在这里是可选的。 使用 AncestorType 属性隐式地将 Mode 属性设置为 FindAncestor 实例,因此我们可以在没有它的情况下声明相同的绑定,如下所示:

<TextBlock Text="{Binding DataContext.UserCount,
                 RelativeSource={RelativeSource
                 AncestorType={x:Type Views:UserView}}}" />

Mode 属性属于RelativeSourceMode 枚举类型,有四个成员。 我们已经看到了一个实例(FindAncestor 成员)的示例,尽管可以使用相关的relativeSource.AncestorLevel 属性来扩展该实例,该属性指定在哪个级别的祖先中查找绑定源。 仅当控件具有相同类型的多个祖先时,此属性才真正有用,如以下简化示例所示:

<StackPanel Tag="Outer">
    ...
    <StackPanel Orientation="Horizontal" Tag="Inner">
        <TextBlock Text="{Binding Tag, RelativeSource={RelativeSource
                         Mode=FindAncestor, AncestorType={x:Type StackPanel},
                         AncestorLevel=2}}" />
        ...
    </StackPanel>
</StackPanel>

本例中的 TextBox 将在运行时输出单词“Outer”,因为我们已经声明绑定源应该是 StackPanel 类型的第二个祖先。 如果 AncestorLevel 属性已设置为 1 或在绑定中省略,则 TextBox 将在运行时输出单词“Inner”。

下一个RelativeSourceMode枚举实例是Self,它指定绑定源与绑定目标是同一个对象。 请注意,使用RelativeSource.Self 属性时,Mode 属性会隐式设置为Self 实例。 我们可以使用此属性将 UI 控件的一个属性数据绑定到另一个属性,如下面的示例所示,该示例将控件的宽度值设置为其 Height 属性,以确保无论宽度如何,它都保持为正方形:

<Rectangle Height="{Binding ActualWidth,RelativeSource={RelativeSource Self}}" Fill="Red" />

relativeSource.TemplatedParent 属性仅用于从 ControlTemplate 内部访问控件的属性。 模板化父对象是指应用了 ControlTemplate 的对象。 使用 TemplatedParent 属性时,Mode 属性会隐式设置为relativeSourceMode 枚举的 TemplatedParent 实例。 让我们看一个例子:

<ControlTemplate x:Key="ProgressBar" TargetType="{x:Type ProgressBar}">
    ...
    <TextBlock Text="{Binding Value,RelativeSource={RelativeSource TemplatedParent}}" />
    ...
</ControlTemplate>

在此示例中,模板化父级是 ProgressBar 的实例,该实例将应用此模板,因此,使用 TemplatedParent 属性,我们可以从 ControlTemplate 中访问 ProgressBar 类的各种属性。 此外,任何将数据绑定到模板化父级的 Value 属性的绑定源也将数据绑定到此内部 TextBox 元素的 Text 属性。

继续讨论最后一个relativesource属性,previousdata仅在为集合中的项目定义datatemplate时才真正有用。 它用于将集合中的前一项设置为绑定源。 虽然不经常使用,但在某些情况下我们可能需要比较集合中相邻项目之间的值,我们将在本章后面看到完整的示例。

虽然这是一个简单得多的选项,但 Binding 类的 ElementName 属性还使我们能够覆盖 DataContext 设置的绑定源。 它用于将一个 UI 控件的属性数据绑定到另一个控件的属性或同一控件上的另一个属性。 使用此属性的唯一要求是我们需要在当前控件中命名要数据绑定的元素。 让我们看一个例子:

<StackPanel Orientation="Horizontal" Margin="20">
    <CheckBox Name="Checkbox" Content="Service" Margin="0,0,10,0" />
    <TextBox Text="{Binding Service}"
             Visibility="{Binding IsChecked, ElementName=Checkbox,
                         Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>

在此示例中,我们有一个 CheckBox 元素和一个 TextBlock 元素。 TextBlock 元素的 Visibility 属性是绑定到 CheckBox 元素的 IsChecked 属性的数据,我们利用之前看到的 BoolToVisibilityConverter 类将 bool 值转换为 Visibility 实例。 因此,当用户选中 CheckBox 元素时,TextBlock 元素将变得可见。

ElementName 属性还可以用作访问父控件的 DataContext 的快捷方式。 例如,如果我们将 View This 命名,那么我们可以使用数据模板中的 ElementName 属性将数据绑定到父视图模型中的属性:

<DataTemplate DataType="{x:Type DataModels:User}">
    <StackPanel>
        <TextBlock Text="{Binding Name}" />
        <TextBlock Text="{Binding DataContext.UserCount, ElementName=This}" />
    </StackPanel>
</DataTemplate>

在指定这些替代绑定源时,重要的是要知道我们只能同时使用这三种不同方法中的一种。 如果我们要设置多个绑定 Source、RelativeSource 或 ElementName 属性,则绑定会引发异常。

优先绑定

在奇怪的情况下,我们可能需要指定多个源绑定路径并希望将它们映射到单个绑定目标属性。 我们可以做到这一点的一种方法是使用 MultiBinding 类,我们将在本章的最后一节中看到一个示例。 但是,我们可以使用另一个类来为我们提供一些附加功能。

PriorityBinding 类使我们能够指定多个绑定并为每个绑定赋予优先级,首先声明的绑定具有最高优先级。 该类的特殊功能是,它将显示第一个返回有效值的绑定的值,如果该绑定不是具有最高优先级的绑定,则它将使用最高优先级绑定的值更新显示。 已成功解决。

为了进一步澄清,这使我们能够指定对将立即解析的普通属性的绑定,而我们想要数据绑定的实际值正在下载、计算或以其他方式随着时间的推移而解析。 这使我们能够在下载实际所需图像时提供默认图像源,或者输出消息直到计算值准备好显示。 让我们看一个简单的 XAML 示例:

<TextBlock>
    <TextBlock.Text>
        <PriorityBinding>
            <Binding Path="SlowString" IsAsync="True" />
            <Binding Path="FastString" Mode="OneWay" />
        </PriorityBinding>
    </TextBlock.Text>
</TextBlock>

在前面的示例中,我们在 TextBlock.Text 属性上设置 PriorityBinding,并在内部指定两个绑定。 第一个具有更高的优先级,并且具有我们要显示的实际属性值。 请注意,我们将 IsAsync 属性设置为 True,以指定此绑定需要一些时间来解析,并且它不应阻塞 UI 线程。

第二个绑定是使用单向绑定将数据绑定到普通属性,该绑定仅输出一条消息:

public string FastString
{
   
    get {
    return "The value is being calculated..."; }
}

通过使用 PriorityBinding 元素,该消息将立即输出,然后在准备就绪时使用 SlowString 属性的实际值进行更新。 现在让我们继续研究 Binding 类的另一种类型.

从控件模板中绑定

TemplateBinding 是一种特定类型的绑定,在 ControlTemplate 元素中使用,以便将数据绑定到正在模板化的类型的属性。 它与我们之前讨论的 RelativeSource.TemplatedParent 属性非常相似:

<ControlTemplate x:Key="ProgressBar" TargetType="{x:Type ProgressBar}">
    ...
    <TextBlock Text="{TemplateBinding Value}" />
    ...
</ControlTemplate>

在我们稍作编辑的之前的这个示例中,我们看到声明 TemplateBinding 比使用 RelativeSource.TemplatedParent 属性执行相同的绑定更直接且更简洁。 让我们提醒自己那是什么样子的:

<TextBlock Text="{Binding Value,RelativeSource={RelativeSource TemplatedParent}}" />

如果可能,通常最好使用 TemplateBinding 而不是relativeSource.TemplatedParent 属性,尽管它们在绑定中执行相同的连接,但它们之间存在一些差异。 例如,TemplateBinding 在编译时进行评估,这样可以更快地实例化控件模板,而 TemplatedParent 绑定直到运行时才进行评估。

此外,它是一种更简单的绑定形式,并且缺少许多 Binding 类属性,例如 StringFormat 和 Delay。 此外,它还对用户施加了额外的约束,即永久设置为 OneWay 绑定模式,并且绑定目标和绑定源都必须是依赖属性。 它被设计用于具有单一目的的单一位置,在这种情况下,它比同类产品能更好、更高效地完成其工作。

绑定源更改

有时,我们可能需要对绑定源进行更改并将这些更改传播到绑定目标控件。 我们可能想在新表单上设置默认值,清除旧表单值,甚至从视图模型中设置表单标签。 为此,我们的视图模型必须实现 INotifyPropertyChanged 接口,这就是我们将此实现构建到基本视图模型类中的原因。

当我们将绑定源数据绑定到 UI 中的控件时,事件处理程序将附加到源对象的 PropertyChanged 事件。 当收到绑定源属性路径指定的属性更改的通知时,控件将使用新值进行更新。

应该注意的是,如果没有专门附加处理程序并且其任何属性都没有数据绑定到 UI 控件,则绑定源的 PropertyChanged 事件将为 null。 正是由于这个原因,我们必须在引发此事件之前始终检查是否为空。

除 OneWayToSource 实例外,所有绑定模式都按照将源绑定到绑定目标的方向工作。 但是,只有此实例和 Binding.Mode 枚举的 TwoWay 实例将绑定目标方向的更改传播到绑定源。

当绑定在这两种模式中工作时,它会将处理程序附加到目标控件以侦听目标属性的更改。 当它收到目标属性更改的通知时,其行为由绑定的 UpdateSourceTrigger 属性的值确定。

该属性属于枚举类型UpdateSourceTrigger,有四个成员。 最常见的是 PropertyChanged 实例,它指定目标属性更改后应立即更新源属性。 这是大多数控件的默认值。

LostFocus 成员是下一个最常见的值,它指定当用户将焦点从数据绑定控件移开时,绑定应更新绑定源。 当我们希望在用户完成每个文本框中的输入(而不是在键入时)时触发验证时,此选项非常有用。

如果没有明确的指令,显式实例不会更新绑定源。 由于我们需要以编程方式调用内部 BindingExpression 对象的 UpdateSource 方法,以便将更改传播到绑定源,因此在我们的普通视图中通常不使用此选项。

相反,如果使用的话,我们会在 CustomControl 类中找到它。 请注意,如果绑定模式未设置为 OneWayToSource 或 TwoWay 实例之一,则调用 UpdateSource 方法不会执行任何操作。

如果我们有一个文本框实例,并且想要显式更新数据绑定到其 Text 属性的绑定源,则可以从 BindingOperations.GetBindingExpression 方法访问较低级别的 BindingExpression 对象并调用其 UpdateSource 方法:

BindingExpression bindingExpression = BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty);
bindingExpression.UpdateSource();

或者,如果我们的绑定目标控件类扩展了 FrameworkElement 类并且大多数都这样做,那么我们可以直接调用它的 GetBindingExpression 方法并传入我们想要更新绑定的依赖项属性键:

textBox.GetBindingExpression(TextBox.TextProperty);

UpdateSourceTrigger 枚举的最后一个成员是 Default 实例。 这与 Binding.Mode 枚举的 Default 实例类似,因为它使用每个目标依赖项属性指定的值,并且是 UpdateSourceTrigger 属性的默认值。 同样,我们将在本章后面了解如何设置依赖属性的元数据。

转换数据绑定值

在开发 WPF 应用程序时,很多时候我们需要将数据绑定属性值转换为不同的类型。 例如,我们可能希望在视图模型中使用 bool 属性来控制某些 UI 元素的可见性,这样我们就可以避免其中包含与 UI 相关的 Visibility 枚举实例。

我们可能希望将不同的枚举成员转换为不同的 Brush 对象,或将集合转换为所包含集合项的字符串表示形式。 我们已经看到了 IValueConverter 接口的许多示例,但现在让我们更彻底地了解一下:

public interface IValueConverter
{
   
    object Convert(object value, Type targetType, object parameter,CultureInfo culture);
    object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture);
}

正如我们已经看到的,类型对象的值输入参数是绑定的数据绑定值。 对象返回类型与我们要返回的转换后的值有关。 targetType 输入参数指定绑定目标属性的类型,通常用于验证输入值以确保转换器与预期的数据类型一起使用。

参数输入参数可选地用于将附加值传递到转换器。 如果使用,则可以使用 Binding.ConverterParameter 属性设置其值。 最后,文化输入参数为我们提供了一个 CultureInfo 对象,以便在文化敏感的应用程序

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0neKing2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值