Xamarin.Forms 数据绑定连接两个对象之间的一对属性,其中至少有一个通常是用户界面对象。 这两个对象称为“目标”和“源”:
- “目标”是设置数据要绑定的对象(和属性)。
- “源”是数据绑定引用的对象(和属性)。
这种区别有时可能会令人困惑:在最简单的情况下,数据从源流到目标,这意味着目标属性值是从源属性的值设置的。 但是,在某些情况下,数据也可以从目标流向源,或者双向流动。 为了避免混淆,请记住,即使目标提供数据而非接收数据,也始终是设置数据绑定的对象。
使用绑定上下文绑定
虽然数据绑定通常完全在 XAML 中指定,但在代码中查看数据绑定也是有指导意义的。 “基本代码绑定”页包含具有 Label 和 Slider 的 XAML 文件:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BasicCodeBindingPage"
             Title="Basic Code Binding">
    <StackLayout Padding="10, 0">
        <Label x:Name="label"
               Text="TEXT"
               FontSize="48"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />
        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
Slider 在 0 到 360 范围中进行设置。 此计划的目的是通过操作 Slider 来旋转 Label。
如果没有数据绑定,你可以将 Slider 的 ValueChanged 事件设置为访问 Slider 的 Value 属性的事件处理程序,并将该值设置为 Label 的 Rotation 属性。 数据绑定使该作业自动化;不再需要其中的事件处理程序和代码。
可以在派生自 BindableObject 的任何类的实例上设置绑定,这些类包括 Element、VisualElement、View 和 View 派生物。  绑定始终在目标对象上进行设置。 绑定引用源对象。 要设置数据绑定,请使用目标类的以下两个成员:
- BindingContext属性指定源对象。
- SetBinding方法指定目标属性和源属性。
在本例中,Label 是绑定目标,Slider 是绑定源。 Slider 源中的更改会影响 Label 目标的旋转。 数据从源流向目标。
BindableObject 定义的 SetBinding 方法的参数类型为 BindingBase,Binding 类从该类派生而来,但 BindableObjectExtensions 类还定义了其他 SetBinding 方法。 “基本代码绑定”示例中的代码隐藏文件使用此类中更简单的 SetBinding 扩展方法。
public partial class BasicCodeBindingPage : ContentPage
{
    public BasicCodeBindingPage()
    {
        InitializeComponent();
        label.BindingContext = slider;
        label.SetBinding(Label.RotationProperty, "Value");
    }
}
Label 对象是绑定目标,因此它是设置该属性和调用该方法的对象。 BindingContext 属性指示绑定源 Slider。
SetBinding 方法在绑定目标上进行调用,但需同时指定目标属性和源属性。 目标属性指定为 BindableProperty 对象:Label.RotationProperty。 源属性指定为字符串,并指示 Slider 的 Value 属性。
SetBinding 方法揭示了数据绑定最重要的规则之一:
目标属性必须由可绑定属性支持。
此规则意味着目标对象必须是从 BindableObject 派生的类的实例。 有关可绑定对象和绑定属性的概述,请参阅“可绑定属性”一文。
源属性没有此类规则,该属性指定为字符串。 在内部,反射用于访问实际属性。 然而,在这种特殊情况下,Value 属性还受可绑定属性支持。
代码可以稍微简化:RotationProperty 可绑定属性由 VisualElement 定义,并由 Label 和 ContentPage 继承,因此在 SetBinding 调用中不需要类名:
label.SetBinding(RotationProperty, "Value");
但是,包含类名可以很好地提醒目标对象。
操做 Slider 时,Label 会相应地旋转:
“基本 Xaml 绑定”页等同于“基本代码绑定”,不同之处在于它定义了 XAML 中的整个数据绑定:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BasicXamlBindingPage"
             Title="Basic XAML Binding">
    <StackLayout Padding="10, 0">
        <Label Text="TEXT"
               FontSize="80"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               BindingContext="{x:Reference Name=slider}"
               Rotation="{Binding Path=Value}" />
        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
正如在代码中一样,数据绑定是在目标对象(即 Label)上设置的。 涉及两个 XAML 标记扩展。 这些可立即通过大括号分隔符识别:
- 引用源对象(命名为 slider的Slider)需要x:Reference标记扩展。
- Binding标记扩展将- Label的- Rotation属性链接到- Slider的- Value属性。
有关 XAML 标记扩展的详细信息,请参阅文章 XAML 标记扩展。 x:Reference 标记扩展由 ReferenceExtension 类支持;Binding 由 BindingExtension 类支持。 正如 XML 命名空间前缀所示,x:Reference 是 XAML 2009 规范的一部分,而 Binding 是 Xamarin.Forms 的一部分。 请注意,在大括号中没有出现引号。
在设置 BindingContext 时,很容易忘记 x:Reference 标记扩展。 错误地将属性直接设置为绑定源的名称很常见,如下所示:
BindingContext="slider"
但这不正确。 该标记将 BindingContext 属性设置为 string 对象,该对象的字符拼写为“slider”!
请注意,源属性是由 BindingExtension 的 Path 属性指定的,它对应于 Binding 类的 Path 属性。
可以简化“基本 XAML 绑定”页上显示的标记:XAML 标记扩展名(如 x:Reference 和 Binding)可以定义“内容属性”属性,对于 XAML 标记扩展,这意味着不需要出现属性名称。 Name 属性是 x:Reference 的内容属性,Path 属性是 Binding 的内容属性,这意味着可以从表达式中删除它们:
<Label Text="TEXT"
       FontSize="80"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       BindingContext="{x:Reference slider}"
       Rotation="{Binding Value}" />
不使用绑定上下文绑定
BindingContext 属性是数据绑定的重要组件,但并不总是必要的。 源对象可以在 SetBinding 调用或 Binding 标记扩展中指定。
“可选代码绑定”示例演示了这一点。 XAML 文件类似于“基本代码绑定”示例,但定义 Slider 是为了控制 Label 的 Scale 属性。 因此,Slider 在 -2 到 2 范围进行设置:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.AlternativeCodeBindingPage"
             Title="Alternative Code Binding">
    <StackLayout Padding="10, 0">
        <Label x:Name="label"
               Text="TEXT"
               FontSize="40"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />
        <Slider x:Name="slider"
                Minimum="-2"
                Maximum="2"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
代码隐藏文件设置与 BindableObject 定义的 SetBinding 方法的绑定。 该参数是 Binding 类的构造函数:
public partial class AlternativeCodeBindingPage : ContentPage
{
    public AlternativeCodeBindingPage()
    {
        InitializeComponent();
        label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider));
    }
}
Binding 构造函数有 6 个参数,因此 source 参数是用命名参数指定的。 该参数是 slider 对象。
运行此程序可能会感到惊讶:
左侧的 iOS 屏幕显示了页面首次出现时的屏幕外观。 Label 在哪里?
问题在于 Slider 的初始值为 0。 这将导致 Label 的 Scale 属性也设置为 0,覆盖其默认值 1。 这会导致 Label 在最初是不可见的。 正如 Android 的屏幕截图所示,你可以操作 Slider,使 Label 再次出现,但它最初的消失让人不安。
你将在下一篇文章中发现如何通过从 Scale 属性的默认值初始化 Slider 来避免此问题。
注意
VisualElement 类还定义了 ScaleX 和 ScaleY 属性,这些属性可以在水平和垂直方向上以不同方式缩放 VisualElement。
“替代 XAML 绑定”页完全显示出在 XAML 中的等效绑定:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.AlternativeXamlBindingPage"
             Title="Alternative XAML Binding">
    <StackLayout Padding="10, 0">
        <Label Text="TEXT"
               FontSize="40"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               Scale="{Binding Source={x:Reference slider},
                               Path=Value}" />
        <Slider x:Name="slider"
                Minimum="-2"
                Maximum="2"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
现在,Binding 标记扩展有两个属性集,Source 和 Path,用逗号分隔。 如果你愿意,也可在同一行上显示这两个属性集:
Scale="{Binding Source={x:Reference slider}, Path=Value}" />
Source 属性设置为嵌入的 x:Reference 标记扩展,否则具有与设置 BindingContext 相同的语法。 请注意,大括号中没有出现引号,这两个属性必须用逗号分隔。
Binding 标记扩展的内容属性是 Path,但是标记扩展的 Path= 部分只有在它是表达式中的第一个属性时才能被删除。 若要消除 Path= 部分,需要交换这两个属性:
Scale="{Binding Value, Source={x:Reference slider}}" />
尽管 XAML 标记扩展通常由大括号分隔,但它们也可以表示为对象元素:
<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand">
    <Label.Scale>
        <Binding Source="{x:Reference slider}"
                 Path="Value" />
    </Label.Scale>
</Label>
现在 Source 和 Path 属性是常规的 XAML 属性:值出现在引号中,属性之间没有逗号分隔。 x:Reference 标记扩展也可以成为对象元素:
<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand">
    <Label.Scale>
        <Binding Path="Value">
            <Binding.Source>
                <x:Reference Name="slider" />
            </Binding.Source>
        </Binding>
    </Label.Scale>
</Label>
这种语法并不常见,但当涉及到复杂对象时,有时它也是必要的。
到目前为止显示的示例将 Binding 的 BindingContext 属性和 Source 属性设置为 x:Reference 标记扩展,以引用页面上的另一个视图。 这两个属性的类型是 Object,可以将它们设置为包含适合绑定源的属性的任何对象。
在前面的文章中,你会发现可以将 BindingContext 或 Source 属性设置为 x:Static 标记扩展,以引用静态属性或字段的值,或将 StaticResource 标记扩展设置为引用存储在资源字典中的对象,或直接指向对象,该对象通常是(但不总是)视图模型的实例。
还可以将 BindingContext 属性设置为 Binding 对象,以便 Binding 的 Source 和 Path 属性定义绑定上下文。
绑定上下文继承
在本文中,你已经看到可以使用 BindingContext 属性或 Binding 对象的 Source 属性指定源对象。 如果两者都已设置,则 Binding 的 Source 属性优先于 BindingContext。
BindingContext 属性具有一个极其重要的特性:
BindingContext 属性的设置是通过可视化树继承的。
正如你所看到的,这对于简化绑定表达式非常方便,在某些情况下,特别是在模型-视图-视图模型 (MVVM) 场景中,这是非常重要的。
“绑定上下文继承”示例是绑定上下文继承的简单演示:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BindingContextInheritancePage"
             Title="BindingContext Inheritance">
    <StackLayout Padding="10">
        <StackLayout VerticalOptions="FillAndExpand"
                     BindingContext="{x:Reference slider}">
            <Label Text="TEXT"
                   FontSize="80"
                   HorizontalOptions="Center"
                   VerticalOptions="EndAndExpand"
                   Rotation="{Binding Value}" />
            <BoxView Color="#800000FF"
                     WidthRequest="180"
                     HeightRequest="40"
                     HorizontalOptions="Center"
                     VerticalOptions="StartAndExpand"
                     Rotation="{Binding Value}" />
        </StackLayout>
        <Slider x:Name="slider"
                Maximum="360" />
    </StackLayout>
</ContentPage>
StackLayout 的 BindingContext 属性设置为 slider 对象。 该绑定上下文由 Label 和 BoxView 继承,它们的 Rotation 属性都设置为 Slider 的 Value 属性:
在下一篇文章中,你将看到“绑定模式”如何更改目标对象和源对象之间的数据流。


