很多时候我们需要将某个View1 值的改变显示在另外一个View2 上,对View1的对应事件编码可实现我们想要的效果,如果只是想处理值的改变,可以通过连接两个View的对应属性即可,称为Data Binding
。Data Binding在Model-View-ViewModel (MVVM)设计模式中起着重要作用。
Data Binding中设计两个概念Source
和 Target
。当Source的值发生改变时Data Binding会自动将这个新的值更新到Target。对Target和Source有特殊要求,Target必须继承BindableProperty
类(VisualElement通过继承Element继承了BindableObject,所以Xamarin.Forms中视图的大部分属性都是BindableProperty类型),Source必须实现INotifyPropertyChanged
接口提供一种通知机制监听Source值的改变(BindableObject实现了INotifyPropertyChanged接口)。
简单的Data Binding使用
本示例以Slider的Value属性作Source,Label的Opacity属性作Target,实现拖动滑块影响Label透明度的效果。
代码方式设置Data Binding:
核心代码设置Target对象的BindingContext
属性(BindableObject类型)。再调用Target 对象的SetBinding
方法设置绑定属性关系,第一个参数targetProperty为BindableProperty类型,表示目标属性。第二个参数string类型,表示BindingContext的哪个属性为Source。本例调用的是5个参数的方法,后三个参数为默认值。
代码运行效果:
XAML方式设置Data Binding:
<StackLayout>
<Label Text="Opacity Binding Demo"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
BindingContext="{x:Reference Name=slider}"
Opacity="{Binding Path=Value}" />
<Slider x:Name="slider" VerticalOptions="CenterAndExpand" />
</StackLayout>
查看Label定义,BindingContext属性通过x:Reference
指定,Opacity为目标属性通过Binding
扩展标记的Path设置。Path不仅可以是Property也可以是SubProperty或 Indexer.如Content.Children[4].Value
.
在整个视图树中子View是会继承父布局的BindingContext属性。如子View没有单独设置BindingContext属性,会查找上级视图若发现BindingContext赋值会直接继承,如果上级视图同样没有BindingContext赋值且存在上级视图会继续搜索上级视图BindingContext的赋值。本例修改XAML布局代码将Label的BindingContext删除添加到StackLayout中,同样会实现我们想要的效果。
<StackLayout BindingContext="{x:Reference Name=slider}">
<Label Text="Opacity Binding Demo"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Opacity="{Binding Path=Value}" />
<Slider x:Name="slider" VerticalOptions="CenterAndExpand" />
</StackLayout>
同样可以使用属性节点定义方式设置Data Binding相关属性
<Label Text="Opacity Binding Demo"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center">
<Label.BindingContext>
<x:Reference Name="slider" />
</Label.BindingContext>
<Label.Opacity>
<Binding Path="Value" />
</Label.Opacity>
</Label>
Reference对应的C#类为ReferenceExtension
,Binding对应的类为BindingExtension
。两个类的定义都指定了Content Property,分别为Name和Path,所以可以简化代码:
BindingContext="{x:Reference slider}"
Opacity="{Binding Value}"
前面是通过BindingContext指定Data Binding的Source,还可以通过Binding指定Source。对应的C#代码为SetBinding两个参数的方法:
BindingBase
为abstract类,Forms提供了Binding
类该类继承了BindingBase。
通过Binding指定Source,再将Binding对象作为参数传入SetBinding方法。
Binding 提供了重载的构造函数和静态方法Create<TSource>来创建Binding对象,不作介绍。
同样XAML定义方式为,删除BindingContext属性赋值,修改Binding扩展标记。
根据内容属性简化XAML代码:
Opacity="{Binding Value , Source={x:Reference slider}}"
扩展标记定义在一对大括号内且大括号内不应出现双引号,指定多个属性值时通过逗号分隔。
关于内容属性定义的简化写法《Creating Mobile Apps with Xamarin.Forms》中有提到“Even though BindingExtension defines Path as its content property, the argument name can be eliminated only when that argument is the first among multiple arguments.”大概意思是要省略内容属性的参数名称必须将其放在第一个参数,但是测试发现Opacity="{Binding Source={x:Reference slider} , Value}"
这种写法同样可以。
那么问题来了,如果我们给BindingContext赋值的同时也为Binding的Source赋值,应该将哪个属性对应的对象作为数据源。符合就近原则Source的优先级高于BindingContext,即指定Source时不在考虑BindingContext。且Source使用更加灵活,如一个对象的多个属性使用不同对象作为数据源只能通过Source方式指定。
Binding Mode 介绍
现在要通过Data Binding实现两个Slider的Value相互影响。滑动一个Slider的同时另一个有相同变化。
愚蠢的办法是分别将两个Slider作为另一个的Source,即同时为两个Slider设置Data Binding。上一个Slider和Label的示例可以理解为Source影响Target,最简单的办法就是可以使Source和Target相互影响。BindingMode
枚举可以帮助我们定义target 和 source之间的绑定模式。
BindingMode有四个枚举值:
• Default
• OneWay — Source 的改变影响Target的值(通常是这种情况).
• OneWayToSource — Target的改变影响Source的值.
• TwoWay — Source和Target值改变会相互影响.
对于可读写的BindableProperty对象默认BindingMode为OneWay,只读的BindableProperty对象默认BindingMode为OneWayToSource。
大多数BindableProperty对象BindingMode默认值为OneWay,以下控件的Property的BindingMode方式默认是TwoWay:
由于Slider的Value默认BindingMode为TwoWay,所以实现两个Slider连动XAMlL定义为:
<StackLayout>
<Slider x:Name="slider" VerticalOptions="CenterAndExpand" />
<Slider BindingContext="{x:Reference Name=slider}" Value="{Binding Path=Value}" VerticalOptions="CenterAndExpand" />
</StackLayout>
通过XAML明确指定BindingMode的值Value="{Binding Path=Value Mode=TwoWay}"
。通过C#代码指定BindingMode的值slider.SetBinding(Slider.ValueProperty,"Value",BindingMode.TwoWay);
。
Binding StringFormat 介绍
再次把Slider作为Source,Label作为Target。将Slider的Value值绑定到Label的Text。Value值ToString后直接显示到Label上可能不是我们期望的,Binding类提供了StringFormat
属性表示.NET格式化字符串。
XAML中StringFormat使用,因为StringFormat本身会包含一对大括号,所以StringFormat赋值时要包含一对单引号:
C#代码设置StringFormat:
label.SetBinding(Label.TextProperty, "Value", stringFormat: "Slider Value Is {0:F3}");
Binding IValueConvert 介绍
目前示例Target需要的数据为string,默认转换或StringFormat可以实现效果。但是Data Binding的Target接受数据类型为一个对象时如何处理?我们可以通过value converter
类完成Source到Target的类型转换,需要实现IValueConverter
接口,接口有Convert
和ConvertBack
两个方法。
当数据由Source转换到Target时调用Convert方法。Convert方法中value表示Source传递的值,你可以通过GetType来确定它的类型,也可以默认一种类型来处理。targetType表示Target需要的数据类型,Convert方法返回的类型应与targetType相同。parameter在Binding 中会用到,culture为CultureInfo类型需要和地域文化相关的转换时会用到。
ConvertBack方法会在数据由Target转换到Source 时调用,只有Binding Mode为TwoWay 或者 OneWayToSource时才有必要实现该方法,否则直接返回null即可。value参数表示target传递的值,targetType表示source的Type类型。
示例实现效果,一个Entry 和一个Button,当Entry中内容为空时Button不可用,点击Button清空Entry。
定义IntToBoolConverter实现IValueConverter,本示例中Entry为Source,Entry的Text.Length为Path,Button为Target,IsEnabled为绑定的属性。所以自定义的Converter应有Int转换为bool的能力(Convert方法,本例中ConvertBack可直接返回null)。
XAML中使用Converter要先在Resources
字典中定义IntToBoolConverter对象,指定key值,在Binding时通过StaticResource
赋值。其中Resources相关内容在Style中介绍。
如果Converter只使用一次,不必在Resources中定义,直接在Binding中通过属性节点定义即可。
《Creating Mobile Apps with Xamarin.Forms》中提供了一个bool转泛型的Converter类,可以将bool值转换为我们想要的值。定义如下:
在XAML定义时,通过 x:TypeArguments
指定我们需要的类型,并设置TrueObject和FalseObject属性。