Xamarin.Forms 基本绑定

Xamarin.Forms 数据绑定连接两个对象之间的一对属性,其中至少有一个通常是用户界面对象。 这两个对象称为“目标”和“源”

  • “目标”是设置数据要绑定的对象(和属性)
  • “源”是数据绑定引用的对象(和属性)

这种区别有时可能会令人困惑:在最简单的情况下,数据从源流到目标,这意味着目标属性值是从源属性的值设置的。 但是,在某些情况下,数据也可以从目标流向源,或者双向流动。 为了避免混淆,请记住,即使目标提供数据而非接收数据,也始终是设置数据绑定的对象。

使用绑定上下文绑定

虽然数据绑定通常完全在 XAML 中指定,但在代码中查看数据绑定也是有指导意义的。 “基本代码绑定”页包含具有 LabelSlider 的 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

如果没有数据绑定,你可以将 SliderValueChanged 事件设置为访问 SliderValue 属性的事件处理程序,并将该值设置为 LabelRotation 属性。 数据绑定使该作业自动化;不再需要其中的事件处理程序和代码。

可以在派生自 BindableObject 的任何类的实例上设置绑定,这些类包括 ElementVisualElementViewView 派生物。 绑定始终在目标对象上进行设置。 绑定引用源对象。 要设置数据绑定,请使用目标类的以下两个成员:

在本例中,Label 是绑定目标,Slider 是绑定源。 Slider 源中的更改会影响 Label 目标的旋转。 数据从源流向目标。

BindableObject 定义的 SetBinding 方法的参数类型为 BindingBaseBinding 类从该类派生而来,但 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。 源属性指定为字符串,并指示 SliderValue 属性。

SetBinding 方法揭示了数据绑定最重要的规则之一:

目标属性必须由可绑定属性支持

此规则意味着目标对象必须是从 BindableObject 派生的类的实例。 有关可绑定对象和绑定属性的概述,请参阅“可绑定属性”一文

源属性没有此类规则,该属性指定为字符串。 在内部,反射用于访问实际属性。 然而,在这种特殊情况下,Value 属性还受可绑定属性支持。

代码可以稍微简化:RotationProperty 可绑定属性由 VisualElement 定义,并由 LabelContentPage 继承,因此在 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 标记扩展。 这些可立即通过大括号分隔符识别:

  • 引用源对象(命名为 sliderSlider)需要 x:Reference 标记扩展。
  • Binding 标记扩展将 LabelRotation 属性链接到 SliderValue 属性。

有关 XAML 标记扩展的详细信息,请参阅文章 XAML 标记扩展x:Reference 标记扩展由 ReferenceExtension 类支持;BindingBindingExtension 类支持。 正如 XML 命名空间前缀所示,x:Reference 是 XAML 2009 规范的一部分,而 Binding 是 Xamarin.Forms 的一部分。 请注意,在大括号中没有出现引号。

在设置 BindingContext 时,很容易忘记 x:Reference 标记扩展。 错误地将属性直接设置为绑定源的名称很常见,如下所示:

BindingContext="slider"

但这不正确。 该标记将 BindingContext 属性设置为 string 对象,该对象的字符拼写为“slider”!

请注意,源属性是由 BindingExtensionPath 属性指定的,它对应于 Binding 类的 Path 属性。

可以简化“基本 XAML 绑定”页上显示的标记:XAML 标记扩展名(如 x:ReferenceBinding)可以定义“内容属性”属性,对于 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 是为了控制 LabelScale 属性。 因此,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。 这将导致 LabelScale 属性也设置为 0,覆盖其默认值 1。 这会导致 Label 在最初是不可见的。 正如 Android 的屏幕截图所示,你可以操作 Slider,使 Label 再次出现,但它最初的消失让人不安。

你将在下一篇文章中发现如何通过从 Scale 属性的默认值初始化 Slider 来避免此问题。

注意

VisualElement 类还定义了 ScaleXScaleY 属性,这些属性可以在水平和垂直方向上以不同方式缩放 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 标记扩展有两个属性集,SourcePath,用逗号分隔。 如果你愿意,也可在同一行上显示这两个属性集:

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>

现在 SourcePath 属性是常规的 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>

这种语法并不常见,但当涉及到复杂对象时,有时它也是必要的。

到目前为止显示的示例将 BindingBindingContext 属性和 Source 属性设置为 x:Reference 标记扩展,以引用页面上的另一个视图。 这两个属性的类型是 Object,可以将它们设置为包含适合绑定源的属性的任何对象。

在前面的文章中,你会发现可以将 BindingContextSource 属性设置为 x:Static 标记扩展,以引用静态属性或字段的值,或将 StaticResource 标记扩展设置为引用存储在资源字典中的对象,或直接指向对象,该对象通常是(但不总是)视图模型的实例。

还可以将 BindingContext 属性设置为 Binding 对象,以便 BindingSourcePath 属性定义绑定上下文。

绑定上下文继承

在本文中,你已经看到可以使用 BindingContext 属性或 Binding 对象的 Source 属性指定源对象。 如果两者都已设置,则 BindingSource 属性优先于 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>

StackLayoutBindingContext 属性设置为 slider 对象。 该绑定上下文由 LabelBoxView 继承,它们的 Rotation 属性都设置为 SliderValue 属性:

绑定上下文继承

下一篇文章中,你将看到“绑定模式”如何更改目标对象和源对象之间的数据流

第 9 频道YouTube 上查找更多 Xamarin 视频。