数据绑定一直是 MvvmCross (Mvx) 框架的核心, 随着 Mvx 版本的版本更新, 绑定语法由 Json 变化到了 Swiss 语法, 并逐渐向 Tibet 过度。 由于基于 Json 的绑定语法在 Mvx 3.0 之后的版本已经标记为过时, 不再支持, 因此不做介绍, 本文详细介绍 Swiss 和 Tibet 语法。
Mvx 实现了跨平台的数据绑定, 概念与 WPF/Silverlight/WinPhone (Xaml) 的数据绑定一致, 可以在 Android 和 iOS 平台使用, 这也正是 Mvx 框架的魅力所在。
在 Xaml 平台下, 数据绑定技术是非常普遍的, 比如:
<TextBlock Text="{Binding Path=TweetText, Converter={StaticResource RemainingLength},
ConverterParameter=140}" />
与之对应的 Swiss 绑定为:
Text TweetText, Converter=RemainingLength, ConverterParameter=140
Swiss 绑定语法看起来比 Xaml 平台下的绑定语法要简洁一些, 接下来详细介绍。
先来看一个最基本的绑定, 将视图 View 的属性 $Target$ 绑定到数据模型 ViewModel 的属性 $SourcePath$ , 如下所示:
$Target$ $SourcePath$
通常情况下 $Target$
必须是直接是 View 的属性, 例如:
而 $SourcePath$
则可以 ViewModel 的属性, 也可以是 ViewModel上 C# 风格的属性路径 (PropertyPath) , 例如:
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 作为数据源;
如果需要使用 Converter , 则直接在后面添加:
, Converter=$ConverterName$
$ConverterName$
标识 ValueConverter 的名称, 通常是将类名去掉 ValueConverter 后缀, 例如: Length 对应的 ValueConverter 的类名是 LengthValueConverter
。
如果需要 ConverterParameter , 则在后面继续添加:
, ConverterParameter=$ParameterValue$
$ParameterValue$
允许的内容如下:
null
;long
;double
。如果需要 FallbackValue , 则继续添加:
, FallbackValue=$FallbackValue$
$FallbackValue$
允许的内容和 $ParameterValue$
一致, 再加上 Enum
枚举类型的 ToString()
的字符串形式, 这个在绑定类似 Visibility
之类的属性时非常有用。
如果需要特定的 BindMode , 则继续添加:
, Mode=$WhichMode$
$WhichMode$
允许的值如下:
如果需要 CommandParameter , 则可以继续添加:
, CommandParameter=$CPValue$
$CPValue$
允许的内容与 $ParameterValue$
相同。
如果需要多个绑定, 则用分号 ;
分割。
下面再看几个具体的例子:
Text Customer.FirstName
将 Text
绑定到 ViewModel 的 Customer.FirstName
属性;
Text Title, Converter=Length
将 Text
绑定到 ViewModel 的 Title
属性, 并使用名称为 Length 的 ValueConverter , 而这个 ValueConverter 是 LengthValueConverter 的默认实例;
Text Order.Amount, Converter=Trim, ConverterParameter='?'
将 Text
属性绑定到 ViewModel 的 Order.Amount
, 并应用 Trim
ValueConverter , Converter 的参数是字符串 '?'
;
Text Order.Amount, Converter=Trim, ConverterParameter='?', FallbackValue='N/A'
Bind the Text property to Order.Amount on the ViewModel, but apply the Trim value converter, passing it the string “?”. If no Order is available, or if the Order object doesn’t have an Amount value, then display “N/A”
将 Text
属性绑定到 ViewModel 的 Order.Amount
, 并应用 Trim
ValueConverter , Converter 的参数是字符串 '?'
, 如果不能成功获取 Order.Amount
的值, 则显示 "N/A"
。
Value Count, BindingMode=TwoWay
将 Value
属性绑定到 ViewModel 的 Count
属性, 并指明是双向绑定;
Click DayCommand, CommandParameter='Thursday'
Bind the Click event to the DayCommand property on the ViewModel (which should implement ICommand). When invoked, ensure that Execute is passeda parameter value of “Thursday” 将 Click
事件绑定到 ViewModel 的 DayCommand
属性 ( ICommand
的实现), 当事件被激发时, 传递 "Thursday"
参数。
Mvx 还为数据绑定提供了 Fluent API , 可以很方便的使用 C# 代码进行绑定, 通常使用 CreateBindingSet<TView, TViewModel>
扩展方法来完成, 包括:
Bind($ViewObject$)
其中 $ViewObject$
是要进行绑定的视图对象;
For(v => v.$ViewProperty$)
$ViewProperty$
是视图上的属性, 如果没有提供 For
, 将使用默认的属性, 例如: 对于 UILabel
默认的属性是 Text
;
To(vm => vm.$ViewModelPath$)
$ViewModelPath$
是 ViewModel 上的属性路径, 数据源;
OneWay()
TwoWay()
OneWayToSource()
OneTime()
指定绑定模式, OneWay, TwoWay, OneWayToSource 还是 OneTime ;
WithConversion($name$, $parameter$)
$name$
是 ValueConverter 的名称, $parameter$
是参数;
一些具体的绑定示例如下所示:
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();
除了上面的基于 lambda 表达式的 Fluent 绑定, 还可以使用基于字符串的 fluent 绑定, 在绑定视图的事件或者视图的属性没有被暴露成 c# 属性时非常有用。 比如, UIButton 并没有暴露 C# 的 Title 属性, 但是依然可以这样进行绑定:
set.Bind(okButton)
.For("Title")
.To(vm => vm.Caption);
注意: 当使用 fluent 进行绑定时, 别忘记在最后加上
.Apply()
, 否则整个绑定不会起作用。
Tibet 是 Swiss 的扩展, 经过精心的设计, 即保持了与现有的 Swiss 绑定的兼容行, 又添加了几个新的特性, 它们是:
多属性属性
如果一个 ViewModel 有两个属性 Firstname
和 Lastname
, 而需要在界面上显示完整的名称 Fullname
, 通常需要在 ViewModel 上再创建一个额外的属性, 比如:
private string _firstName, _lastName;
public string FirstName {
get { return _firstName; }
set {
_firstName = value;
RaisePropertyChanged(() => FirstName);
RaisePropertyChanged(() => FullName);
}
}
public string LastName {
get { return _lastName; }
set {
_lastName = value;
RaisePropertyChanged(() => LastName);
RaisePropertyChanged(() => FullName);
}
}
public string FullName { get { return _firstName + " " + _lastName; } }
在 Swiss 绑定中, 绑定的写法是:
Text Fullname
而在 Tibet 绑定中, 可以这样写:
Text Firstname + ' ' + Lastname
这样就不再需要创建那个额外的属性了。
属性合成
Tibet 提供了属性合成技术, 将数据源上的多个值合成为一个, 比如上面的多值绑定, 就使用了两个 Add
属性合成器将三个值合成为一个。 目前, tibet 只提供了为数不多的几个属性合成器, 它们是:
If(test, if_true, if_false)
类似于 C# 中的 ? :
算符;Format(format, args…)
类似于 string.Format
函数;Add(one,two)
将两个值相加, 可以在绑定中使用直接使用 +
代替;GreaterThan(one, two)
判断两个值的大小, 可以在绑定中使用 >
代替;重要提示: 属性合成还处于开发中, 只是基本可以工作的原型, 在未来的版本中随时都可能变化。
语义绑定
在多值绑定与属性合成中已经见到了, Tibet 支持语义绑定, 比如:
Value 100 * Ratio
将 Ratio
乘以 100
以转换成百分比, 再比如:
Value Format('Hello {1} - today is {0:ddd MMM yyyy}', TheDate, Name)
使用字符串将 Hello {1} - today is {0:ddd MMM yyyy}
对 TheData
和 Name
进行格式化。
绑定宏
绑定宏尚未实现, 准备支持的特性如下:
可能的想法是采用特定的字符前缀来实现, 例如: $
, #
或 @
等。
针对 ValueConverters 和 ValueCombiners 的函数式语法
使用 Tibet 绑定, 可以将在 Swiss 绑定:
Text TweetText, Converter=RemainingLength, ConverterParameter=140
用下面的方式重写:
Text RemainingLength(TweetText,140)
注意: ValueCombiner 和 ValueConverter 的名字是共享的, 在运行时 Mvx 会首先查找 Combiner 找不到再查找 Converter 。
嵌套转换
Tibet 还支持嵌套, 比如可以将上面的 Trim 和 Length 一起使用, 如下所示:
Text Length(Trim(FirstName + ' ' + LastName))