数据绑定允许将两个对象的属性链接起来,使得一个对象的变化引起另一个对象的变化。 这是一个非常有价值的工具,尽管数据绑定可以完全用代码来定义,但是XAML提供了快捷方式和便利。 因此,Xamarin.Forms中最重要的标记扩展之一是Binding。
数据绑定
数据绑定连接两个对象的属性,称为源和目标。在代码中,需要两个步骤:必须将目标对象的BindingContext属性设置为源对象,并且必须在目标对象上调用SetBinding方法(通常与Binding类一起使用)以绑定该对象的属性对象到源对象的属性。
目标属性必须是可绑定属性,这意味着目标对象必须从BindableObject派生。在线Xamarin.Forms文档指出哪些属性是可绑定的属性。 Text等属性与TextProperty的可绑定属性相关联。
在标记中,除了绑定标记扩展取代了SetBinding调用和Binding类以外,还必须执行代码中所需的相同的两个步骤。
但是,当您在XAML中定义数据绑定时,有多种方法来设置目标对象的BindingContext。有时它是从代码隐藏文件中设置的,有时使用StaticResource或者x:Static标记扩展,有时也作为BindingContext属性元素标记的内容。
如第5部分所讨论的,绑定通常用于将程序的可视化与基础数据模型连接起来,通常在MVVM(Model-View-ViewModel)应用程序体系结构的实现中。从数据绑定到MVVM,但是其他场景是可能的。
视图到视图的绑定
您可以定义数据绑定来链接同一页面上两个视图的属性。 在这种情况下,可以使用x:Reference标记扩展来设置目标对象的BindingContext。
这是一个包含Slider和两个Label视图的XAML文件,其中一个由Slider值旋转,另一个则显示Slider值:
-
<ContentPage xmlns=""
-
xmlns:x=""
-
x:Class="XamlSamples.SliderBindingsPage"
-
Title="Slider Bindings Page">
-
-
<StackLayout>
-
<Label Text="ROTATION"
-
BindingContext="{x:Reference Name=slider}"
-
Rotation="{Binding Path=Value}"
-
FontAttributes="Bold"
-
FontSize="Large"
-
HorizontalOptions="Center"
-
VerticalOptions="CenterAndExpand" />
-
-
<Slider x:Name="slider"
-
Maximum="360"
-
VerticalOptions="CenterAndExpand" />
-
-
<Label BindingContext="{x:Reference slider}"
-
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
-
FontAttributes="Bold"
-
FontSize="Large"
-
HorizontalOptions="Center"
-
VerticalOptions="CenterAndExpand" />
-
</StackLayout>
-
</ContentPage>
Slider包含一个由两个Label视图使用x:Reference标记扩展名引用的x:Name属性。
x:Reference绑定扩展定义了一个名为Name的属性,将其设置为引用元素的名称,在这种情况下为滑块。 但是,定义x:Reference标记扩展的ReferenceExtension类还为Name定义了ContentProperty属性,这意味着它不是明确需要的。 为了多样化,第一个x:Reference包含“Name =”,但第二个不包含:
-
BindingContext="{x:Reference Name=slider}"
-
…
-
BindingContext="{x:Reference slider}"
绑定标记扩展本身可以有几个属性,就像BindingBase和Binding类一样。 Binding的ContentProperty是Path,但是如果路径是绑定标记扩展中的第一项,则标记扩展的“Path =”部分可以省略。 第一个例子有“Path =”,但第二个例子省略了它:
-
Rotation="{Binding Path=Value}"
-
…
-
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
属性可以全部在一行或分成多行:
-
Text="{Binding Value,
-
StringFormat='The angle is {0:F0} degrees'}"
做任何方便的事情。
注意第二个绑定标记扩展中的StringFormat属性。 在Xamarin.Forms中,绑定不执行任何隐式类型转换,如果需要将非字符串对象显示为字符串,则必须提供类型转换器或使用StringFormat。 在幕后,静态的String.Format方法被用来实现StringFormat。 这可能是一个问题,因为.NET格式规范涉及大括号,也用于分隔标记扩展。 这会造成混淆XAML分析器的风险。 为了避免这种情况,请将整个格式化字符串放在单引号中:
-
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
这是正在运行的程序:
绑定模式
单个视图可以在其几个属性上具有数据绑定。但是,每个视图只能有一个BindingContext,因此该视图上的多个数据绑定必须全部引用同一个对象的属性。
这个和其他问题的解决方案涉及Mode属性,它被设置为BindingMode枚举的成员:
-
默认
-
OneWay — 值从源传输到目标
-
OneWayToSource — 将值从目标传输到源
-
TwoWay — 值在源和目标之间传输
以下程序演示了OneWayToSource和TwoWay绑定模式的一个常见用法。四个滑块视图旨在控制标签的缩放,旋转,旋转X和旋转Y属性。起初,标签的这四个属性似乎应该是数据绑定目标,因为每个属性都由一个Slider设置。然而,Label的BindingContext只能是一个对象,并且有四个不同的滑块。
出于这个原因,所有的绑定都是以看似倒退的方式设置的:四个滑块中的每一个的BindingContext被设置为Label,并且绑定被设置在滑块的Value属性上。通过使用OneWayToSource和TwoWay模式,这些Value属性可以设置源属性,即Label的Scale,Rotate,RotateX和RotateY属性:
-
<ContentPage xmlns=""
-
xmlns:x=""
-
x:Class="XamlSamples.SliderTransformsPage"
-
Padding="5"
-
Title="Slider Transforms Page">
-
<Grid>
-
<Grid.RowDefinitions>
-
<RowDefinition Height="*" />
-
<RowDefinition Height="Auto" />
-
<RowDefinition Height="Auto" />
-
<RowDefinition Height="Auto" />
-
<RowDefinition Height="Auto" />
-
</Grid.RowDefinitions>
-
-
<Grid.ColumnDefinitions>
-
<ColumnDefinition Width="*" />
-
<ColumnDefinition Width="Auto" />
-
</Grid.ColumnDefinitions>
-
-
<!-- Scaled and rotated Label -->
-
<Label x:Name="label"
-
Text="TEXT"
-
HorizontalOptions="Center"
-
VerticalOptions="CenterAndExpand" />
-
-
<!-- Slider and identifying Label for Scale -->
-
<Slider x:Name="scaleSlider"
-
BindingContext="{x:Reference label}"
-
Grid.Row="1" Grid.Column="0"
-
Maximum="10"
-
Value="{Binding Scale, Mode=TwoWay}" />
-
-
<Label BindingContext="{x:Reference scaleSlider}"
-
Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
-
Grid.Row="1" Grid.Column="1"
-
VerticalTextAlignment="Center" />
-
-
<!-- Slider and identifying Label for Rotation -->
-
<Slider x:Name="rotationSlider"
-
BindingContext="{x:Reference label}"
-
Grid.Row="2" Grid.Column="0"
-
Maximum="360"
-
Value="{Binding Rotation, Mode=OneWayToSource}" />
-
-
<Label BindingContext="{x:Reference rotationSlider}"
-
Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
-
Grid.Row="2" Grid.Column="1"
-
VerticalTextAlignment="Center" />
-
-
<!-- Slider and identifying Label for RotationX -->
-
<Slider x:Name="rotationXSlider"
-
BindingContext="{x:Reference label}"
-
Grid.Row="3" Grid.Column="0"
-
Maximum="360"
-
Value="{Binding RotationX, Mode=OneWayToSource}" />
-
-
<Label BindingContext="{x:Reference rotationXSlider}"
-
Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
-
Grid.Row="3" Grid.Column="1"
-
VerticalTextAlignment="Center" />
-
-
<!-- Slider and identifying Label for RotationY -->
-
<Slider x:Name="rotationYSlider"
-
BindingContext="{x:Reference label}"
-
Grid.Row="4" Grid.Column="0"
-
Maximum="360"
-
Value="{Binding RotationY, Mode=OneWayToSource}" />
-
-
<Label BindingContext="{x:Reference rotationYSlider}"
-
Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
-
Grid.Row="4" Grid.Column="1"
-
VerticalTextAlignment="Center" />
-
</Grid>
-
</ContentPage>
三个Slider视图上的绑定是OneWayToSource,这意味着Slider值会导致其BindingContext(标签名为label)的属性发生更改。 这三个Slider视图会更改标签的Rotate,RotateX和RotateY属性。
但是,Scale属性的绑定是TwoWay。 这是因为Scale属性的默认值为1,而使用TwoWay绑定会导致Slider初始值设置为1而不是0.如果该绑定是OneWayToSource,Scale属性最初将从Slider设置为0 默认值。 该标签将不可见,这可能会导致用户混淆。
绑定和集合
没有任何事情比模板化的ListView更能说明XAML和数据绑定的强大功能。
ListView定义了IEnumerable类型的ItemsSource属性,并显示该集合中的项目。这些项目可以是任何类型的对象。默认情况下,ListView使用每个项目的ToString方法来显示该项目。有时这只是你想要的,但是在很多情况下,ToString只返回对象的完全限定的类名。
但是,ListView集合中的项目可以通过使用模板来以任何方式显示,该模板涉及从Cell派生的类。为ListView中的每个项目克隆模板,并将在模板上设置的数据绑定传输到单个克隆。
很多时候,您需要使用ViewCell类为这些项目创建自定义单元格。这个过程在代码中有些杂乱,但是在XAML中它变得非常简单。
包含在XamlSamples项目中的是一个名为NamedColor的类。每个NamedColor对象都具有字符串类型的Name和FriendlyName属性,以及Color类型的Color属性。另外,NamedColor还有141个类型为Color的静态只读字段,与Xamarin.Forms Color类中定义的颜色相对应。使用x:Static标记扩展将静态NamedColor.All属性设置为ListView的ItemsSource很容易:
-
<ContentPage xmlns=""
-
xmlns:x=""
-
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
-
x:Class="XamlSamples.ListViewDemoPage"
-
Title="ListView Demo Page">
-
-
<ListView ItemsSource="{x:Static local:NamedColor.All}" />
-
-
</ContentPage>
由此产生的显示确定项目是真正的类型XamlSamples.NamedColor:
它没有太多的信息,但ListView是可滚动和可选的。
要定义项目的模板,您需要将ItemTemplate属性分解为一个属性元素,并将其设置为一个DataTemplate,然后引用一个ViewCell。 在ViewCell的View属性中,您可以定义一个或多个视图的布局来显示每个项目。 这是一个简单的例子:
-
<ListView ItemsSource="{x:Static local:NamedColor.All}">
-
<ListView.ItemTemplate>
-
<DataTemplate>
-
<ViewCell>
-
<ViewCell.View>
-
<Label Text="{Binding FriendlyName}" />
-
</ViewCell.View>
-
</ViewCell>
-
</DataTemplate>
-
</ListView.ItemTemplate>
-
</ListView>
Label元素被设置为ViewCell的View属性。 (因为View属性是ViewCell的内容属性,所以不需要ViewCell.View标记。)这个标记显示每个NamedColor对象的FriendlyName属性:
好多了。 现在所有需要的是用更多的信息和实际的颜色来修饰项目模板。 为了支持这个模板,在页面的资源字典中定义了一些值和对象:
-
<ContentPage xmlns=""
-
xmlns:x=""
-
xmlns:local="clr-namespace:XamlSamples"
-
x:Class="XamlSamples.ListViewDemoPage"
-
Title="ListView Demo Page">
-
-
<ContentPage.Resources>
-
<ResourceDictionary>
-
<OnPlatform x:Key="boxSize"
-
x:TypeArguments="x:Double">
-
<On Platform="iOS, Android, UWP" Value="50" />
-
</OnPlatform>
-
-
<OnPlatform x:Key="rowHeight"
-
x:TypeArguments="x:Int32">
-
<On Platform="iOS, Android, UWP" Value="60" />
-
</OnPlatform>
-
-
<local:DoubleToIntConverter x:Key="intConverter" />
-
-
</ResourceDictionary>
-
</ContentPage.Resources>
-
-
<ListView ItemsSource="{x:Static local:NamedColor.All}">
-
<ListView.ItemTemplate>
-
<DataTemplate>
-
<ViewCell>
-
<StackLayout Padding="5, 5, 0, 5"
-
Orientation="Horizontal"
-
Spacing="15">
-
-
<BoxView WidthRequest="{StaticResource boxSize}"
-
HeightRequest="{StaticResource boxSize}"
-
Color="{Binding Color}" />
-
-
<StackLayout Padding="5, 0, 0, 0"
-
VerticalOptions="Center">
-
-
<Label Text="{Binding FriendlyName}"
-
FontAttributes="Bold"
-
FontSize="Medium" />
-
-
<StackLayout Orientation="Horizontal"
-
Spacing="0">
-
<Label Text="{Binding Color.R,
-
Converter={StaticResource intConverter},
-
ConverterParameter=255,
-
StringFormat='R={0:X2}'}" />
-
-
<Label Text="{Binding Color.G,
-
Converter={StaticResource intConverter},
-
ConverterParameter=255,
-
StringFormat=', G={0:X2}'}" />
-
-
<Label Text="{Binding Color.B,
-
Converter={StaticResource intConverter},
-
ConverterParameter=255,
-
StringFormat=', B={0:X2}'}" />
-
</StackLayout>
-
</StackLayout>
-
</StackLayout>
-
</ViewCell>
-
</DataTemplate>
-
</ListView.ItemTemplate>
-
</ListView>
-
</ContentPage>
注意使用OnPlatform来定义BoxView的大小和ListView行的高度。 尽管所有三个平台的值都是相同的,但是标记可以很容易地适用于其他值来微调显示。
绑定值转换器
以前的ListView Demo XAML文件显示Xamarin.Forms Color结构的各个R,G和B属性。 这些属性的类型为double,范围从0到1.如果要显示十六进制值,则不能简单地使用带有“X2”格式规范的StringFormat。 这只适用于整数,此外,双值需要乘以255。
这个小问题是通过一个价值转换器解决的,也叫做转换器。 这是一个实现IValueConverter接口的类,这意味着它有两个方法Convert和ConvertBack。 当一个值从源传输到目标时,Convert方法被调用; 在OneWayToSource或双向绑定中调用ConvertBack方法以实现从目标到源的传输:
-
using System;
-
using System.Globalization;
-
using Xamarin.Forms;
-
-
namespace XamlSamples
-
{
-
class DoubleToIntConverter : IValueConverter
-
{
-
public object Convert(object value, Type targetType,
-
object parameter, CultureInfo culture)
-
{
-
double multiplier;
-
-
if (!Double.TryParse(parameter as string, out multiplier))
-
multiplier = 1;
-
-
return (int)Math.Round(multiplier * (double)value);
-
}
-
-
public object ConvertBack(object value, Type targetType,
-
object parameter, CultureInfo culture)
-
{
-
double divider;
-
-
if (!Double.TryParse(parameter as string, out divider))
-
divider = 1;
-
-
return ((double)(int)value) / divider;
-
}
-
}
-
}
ConvertBack方法在这个程序中不起作用,因为绑定只是从源到目标的一种方式。
绑定使用Converter属性引用绑定转换器。 绑定转换器也可以接受使用ConverterParameter属性指定的参数。 对于一些多功能性,这是如何指定乘数。 绑定转换器检查转换器参数是否有有效的double值。
转换器在资源字典中实例化,因此它可以在多个绑定中共享:
-
<local:DoubleToIntConverter x:Key="intConverter" />
三个数据绑定引用这个单一实例。 请注意,绑定标记扩展包含嵌入的StaticResource标记扩展:
-
<Label Text="{Binding Color.R,
-
Converter={StaticResource intConverter},
-
ConverterParameter=255,
-
StringFormat='R={0:X2}'}" />
结果如下:
ListView在处理基础数据中可能动态发生的更改方面非常复杂,但只有在采取某些步骤时才是如此。 如果分配给ListView的ItemsSource属性的项目集合在运行时发生更改(即可以将项目添加到集合中或从集合中删除项目),则可以使用这些项目的ObservableCollection类。 ObservableCollection实现INotifyCollectionChanged接口,ListView将为CollectionChanged事件安装一个处理程序。
如果项目的属性本身在运行时发生变化,那么集合中的项目应该实现INotifyPropertyChanged接口,并使用PropertyChanged事件发信号更改属性值。 这在本系列的下一部分(第5部分)中进行了演示。从数据绑定到MVVM。
概要
数据绑定为链接页面中两个对象之间或者可视对象与底层数据之间的属性提供了强大的机制。 但是当应用程序开始使用数据源时,流行的应用程序体系结构模式开始成为一种有用的范例。 这在第5部分中有介绍。从数据绑定到MVVM。