【xamarin + MvvmCross 从零开始】四、MvvmCross 详解 (2)
前言
上篇我们说明了几个在MvvmCross中常用的对象,这次我们讲一下MvvmCross的数据绑定。
什么是数据绑定
数据绑定是MvvmCross的至关重要的特性,通过代码或XAML方式建立View与ViewModel之间的关联,以达到数据呈现、交互的目的。
那什么是数据绑定呢?
- 建立View和ViewModel中属性之间的关联
- 可以指定BindingMode来控制数据的流向
- 可以指定ValueConvert实现数据的双向转换,在使用ValueConvert时可以通过参数实现定制转换
- 可以指定FallbackValue,当数据绑定失败时作为默认结果使用
ViewModel定义
我们来看一个典型的ViewModel是如何定义的
using System.Windows.Input; using MvvmCross.Core.ViewModels; namespace XamarinSample.ViewModels { public class UserViewModel: MvxViewModel { private string _name; private int _age; private string _password; /// <summary> /// 用户名 /// </summary> public string Name { get { return _name; } set { _name = value; // 触发属性 Name 变更 RaisePropertyChanged(() => Name); } } /// <summary> /// 年龄 /// </summary> public int Age { get { return _age; } set { _age = value; RaisePropertyChanged(() => Age); } } /// <summary> /// 密码 /// </summary> public string Password { get { return _password; } set { _password = value; RaisePropertyChanged(() => Password); } } private ICommand _saveCommand; /// <summary> /// 保存用户信息Command,供绑定保存操作使用 /// </summary> public ICommand SaveCommand => _saveCommand ?? (_saveCommand = new MvxCommand(Save)); private void Save() { // 保存User数据 } } }在MvvmCross中所有的ViewModel需要实现IMvxViewModel接口,MvvmCross也实现了基本的ViewModel,所以只需要从MvxViewModel继承就可以。
所有可以绑定数据必须是属性,并且如果要实现数据变更通知,需要在数据变化时调用RaisePropertyChanged 方法。当给Name赋值时,将触发INotityPropertyChange接口中的PropertyChanged事件,View层通过对PropertyChanged事件的接收而实现View层的响应。
Mvvm 不支持直接对方法进行绑定,若要实现对方法的绑定,需要将方法转换为ICommand类型的属性,再进行绑定。这里我们将Save方法封装为SaveCommand后就可以将SaveCommand方法绑定到View 中。
绑定模式
One-Way
- 单向绑定,由ViewModel向View单向的数据绑定。
- 当ViewModel的数据发生变时,View显示的数据也会同时发生变化。
- 一般应用于数据展示,比如天气预报数据显示,ViewModel由服务器更新数据后,呈现界面自动更新。
- 单向绑定在Xaml绑定为默认的绑定模式。
One-Way-To-Source
- 由界面流向数据源的单向绑定。
- 比如用户填表,只需要采集用户数据,不需要向用户进行呈现。
- 这种绑定的模式不经常使用。
Two-Way
- 双向数据绑定,当ViewModel发生变化时,View也相应发生变化;当用户在界面更新数据或都输入时,ViewModel数据也发生变化。
- 一般应用于数据编辑等,如用户信息修改,用户在View修改信息时,ViewModel也相应的发生变化。
One-Time
- 一次性绑定。数据的方向为由ViewModel向View绑定。
- 一般只在界面第一次初始化绑定时获取数据,以后不管ViewModel如何变化,View的呈现都不发生变化。
- 这种模式不经常使用。一般在显示固定内容时使用,如固定的标签、不会发生变化的背景图片等。
数据转换(ValueConverter)
数据转换是一个实现了IValueConverter接口的一个对象。我们来看一下接口的定义:
接口主要包括两个方法:
Convert:实现将ViewModel中的数据转换为支持或方便View呈现的数据。如数据的格式化、布尔型数据转换为是/否等可方便识别的内容等。
ConvertBack:与Convert相反,将View中的数据转换为方便持久化的数据。
通过接口中可以看到,转换支持一个object类型的参数对象。表示在使用ValueConvert时,可以传入一个自定义的参数,极大的提高了转换的灵活性。如在将float转换为字符串时,可以传入一个整形的参数,用来标识转换结果需要保存的小数位数。
接口可以根据需要进行实现,如一个One-Way绑定使用的转换,就没有必要实现ConvertBack部分。
数据转换在MvvmCross中一个通用的特性。这就表示只用在PCL程序集中实现一次数据绑定接口,就可以在Android、iOS、WP等多个平台上使用。
备选值(Fallback Values)
有时ViewModel中的值不是一个可用的值,那这时就需要使用备选值。如显示用户所属的部门数据时,正常情况下需要将关联的部门显示为部门的名称,但当用户并没有设置关联的部门信息时,显示为空显示不太友好,这时可以使用备选值,友好的显示为[未设置部门信息]。
数据绑定(Data Binding)
数据绑定是MvvmCross的核心内容。随着MvvmCross的不断更新,绑定的方式也有很多种。
JSON 绑定
这种绑定方式做为较早的绑定方式,目前已经不推荐使用,这里我们就不做说明了。
Swiss 绑定
Swiss绑定是Json绑定的替代方式,就绑定的语法而言,较JSON绑定方式有极大的简化。
Swiss绑定的语法:
$Target$ $SourcePath$
Target必须是View某个对象的直接属性,比如:
Text,控件的Text属性
IsChecked,可选控件的IsChecked属性
Value,控件的值
……
SourcePath为ViewModel中某个属性或都子对象的某个属性,也就是说可以根据需要指定多级关联属性,比如:
- UserId
- RememberMe
- Password
- Customer.FirstName
- Customer.Address.City
- Customer.Orders[0].Date
- Customer.Orders[0].Total
- Customer.Cards["Primary"].Expiry
- Customer.Cards["Primary"].Number
- …
SourcePath除了以上语法以外,还有一些特殊的用法
- 如果直接使用 . (点)的话,则是说明绑定的对象为当前的整个ViewModel
- 如果需要对绑定的ViewModel属性进行转换,可以添加一个Converter,语法如下:
, Converter=$ConverterName$
- 在使用Converter时,可根据需要使用ConverterParameter:
, ConverterParameter=$ParameterValue$
ConverterName值可以是以下的一种:
- 使用单引号或双引号限定,表示字符串
- null代表C#中的null值
- 整型值,MvvmCross将解释为 长整型long
- 浮点数,MvvmCross将解释为 双精度 数值 double
- 可以根据需要添加FallbackValue:
, FallbackValue=$FallbackValue$
FallbackValue的赋值语法基本和ParameterValue一样,再加上枚举值的ToString()方法。
- 如果需要改变BindMode,可以添加BindMode:
,Mode=$BindMode$
OneWay
OneWayToSource
TwoWay
OneTime
Default
如果需要在同一个对象上绑定多个值,需要要每个不同的绑定值之间用分号(;)进行分隔。
如果绑定的值是一个ICommand类型的属性,可根据需要添加CommandParameter:
, CommandParameter=$CPValue$
在这里CPValue的取值与ParameterValue一样
下面我们看几个Swiss绑定的例子:
Text Customer.FirstName
将View中某个对象的Text属性绑定到ViewModel中的Customer.FirstName属性
Text Title, Converter=Length
将View中某个对象的Text属性绑定以ViewModel中的Title属性,并使用转换器 Length 进行数据转换
Text Order.Amount, Converter=Trim, ConverterParameter='£'
将View中某个对象的Text属性绑定到ViewModel中的Order.Amount属性,并使用转换器 Trim,转换器参数为字符串
'£'
Value Count, Mode=TwoWay
将View中某个对象的Value属性绑定到ViewModel中的Count属性,设置绑定的模式为双向绑定
Click DayCommand, CommandParameter='Thursday'
将View中的某个对象的Click事件绑定到ViewModel中的dayCommand 属性,并使用命令参数
'Thursday'
Fluent 绑定
Fluent绑定做为一种新的绑定方式,可以通过C#代码进行数据的绑定。由于在iOS或MacOS等平台上不方便使用Xaml方式的绑定,数据的绑定就使用Fluent绑定。
Fluent绑定方式在编码时可以充分利用VS的智能提示功能,并且能写出干净整洁的代码,方便阅读。
Fluent绑定通常情况下是通过 CreateBindingSet<TView, TViewModel> 创建视图与数据源的绑定关系。
绑定的语法包括:
- Binding($BindObject$)
指定绑定对象,$BindObject$是指View中的某个对象。
- For(v => v.$ViewProperty$)
$ViewProperty$指定绑定对象的属性,这里只能是关联对象的直接属性。部分对象可以不指定For语句,则$ViewProperty$将使用默认属性。如UIButton的默认属性是Text属性。
To(vm => vm.$ViewModelPath$)
$ViewModelPath$是ViewModel中的属性路径,可以是多级属性,如User.FirstName
绑定模式
OneWay() TwoWay() OneWayToSource() OneTime()
WithConversion($name$, $parameter$)
指定数据转换器。$name$是指定转换器的名称,$parameter$是指当前转换器的参数
- 使用Fluent绑定,在绑定结束后必须调用Apply()方法,否则绑定不会起作用。
我们看几个例子:
var set = this.CreateBindingSet<MyView, MyViewModel>(); set.Bind(nameLabel) .For(v => v.Text) .To(vm => vm.Customer.FirstName); set.Bind(creditLabel) .For(v => v.Text) .To(vm => vm.Customer.Total) .WithConversion("CurrencyFormat", "$"); set.Bind(cardLabel) .For(v => v.Text) .To(vm => vm.Customer.Cards["Primary"].Number) .WithConversion("LastFour") .OneWay() .FallbackValue("N/A"); set.Bind(warningView) .For(v => v.Hidden) .To(vm => vm.Customer.Alert) .WithConversion("Not") .FallbackValue(true); set.Apply();
Tibet 绑定
Tibet绑定与Swiss绑定完全兼容,并且在Swiss绑定上进行了扩展,主要扩展以下几个部分:
- multi-binding
- value combiners
- literal-binding
- binding macros
- functional syntax for ValueConverters and ValueCombiners
- nested value conversion
多值绑定 (multi-binding)
在Swiss绑定中,每次只能绑定一个ViewModel属性,当需要绑定多个属性时,就只能通过在ViewModel中添加属性来实现,这样很不友好。
private string _firstName; public string FirstName { get { return _firstName; } set { _firstName = value; RaisePropertyChanged(() => FirstName); RaisePropertyChanged(() => FullName); } } private string _lastName; public string LastName { get { return _lastName; } set { _lastName = value; RaisePropertyChanged(() => LastName); RaisePropertyChanged(() => FullName); } } public string FullName { get { return _firstName + "" + _lastName; } }在示例代码中,如果我们想要在View上显示FullName,就需要在ViewModel中添加一个属性FullName,这样才能绑定到View中,在Tibet绑定中,我们可以这样写:
Text FirstName + ' ' + LastName
这样,MvvmCross实际将这个绑定解析为三个绑定值 FirstName、 ' ' 、LastName, 当FirstName 或 LastName 发生变化时,相应的绑定对象的值都会发生变化。
值连接 (value combiners)
类似的值连接Tibet还实现了一些:
If(test, if_true, if_false)
- 接受三个输入值,类似于三元运算
- 一个bool型的表达式
- 当表达式为true时所返回的值
- 当表达式为false时所返回的值
Format(format, args…)
- 字符串格式化,同string.Format用法一致
- 字符串格式化模板
- 0或多个字符串格式化值
Add(one,two)
- 接受两个参数。当两个参数类型为字符串时,返回两个字符串的连接值;当两个值为数值时,返回两个数值相加的结果。GreaterThan(one, two)
- 接受两个参数进行大于运算,参数类型可以字符串、浮点型或是整型数据。由于绑定字符串不需要编译,而在支行时通过MvvmCross进行解释执行,所以某些绑定不是支持所有平台。而且某些复杂的表达式在解释时可能与所期望的结果不同。
语义绑定 (literal-binding)
绑定对很多简单的表达式是可以执行的
- Value 100 * Ratio
绑定常量值或是运算表达式
- Value ‘True’
bool 型的属性可以通 True 或 False 绑定常量值
- Value
Format('Hello {1} - today is {0:ddd MMM yyyy}', TheDate, Name)
绑定的Format表达式
Rio 绑定及其它
关于数据绑定,MvvmCross还会逐步增强,包括语法的简化和功能增强,后期会增加宏绑定等内容。
小结
本篇内容主要讲解了MvvmCross中数据绑定的方式。在实际的开发过程中主要使用Swiss和Fluent绑定。习惯通过XAML绑定的同学可以使用Swiss绑定,在不能使用Swiss或是习惯通过c#代码进行数据绑定的同学可以使用Fluent绑定。
下篇我们讲下ViewModel对象的生命周期以及如何通过ViewModel进行导航。