很多時(shí)候我們需要將某個(gè)View1 值的改變顯示在另外一個(gè)View2 上,對(duì)View1的對(duì)應(yīng)事件編碼可實(shí)現(xiàn)我們想要的效果,如果只是想處理值的改變,可以通過(guò)連接兩個(gè)View的對(duì)應(yīng)屬性即可,稱為Data Binding
。Data Binding在Model-View-ViewModel (MVVM)設(shè)計(jì)模式中起著重要作用。
Data Binding中設(shè)計(jì)兩個(gè)概念Source
和 Target
。當(dāng)Source的值發(fā)生改變時(shí)Data Binding會(huì)自動(dòng)將這個(gè)新的值更新到Target。對(duì)Target和Source有特殊要求,Target必須繼承BindableProperty
類(VisualElement通過(guò)繼承Element繼承了BindableObject,所以Xamarin.Forms中視圖的大部分屬性都是BindableProperty類型),Source必須實(shí)現(xiàn)INotifyPropertyChanged
接口提供一種通知機(jī)制監(jiān)聽(tīng)Source值的改變(BindableObject實(shí)現(xiàn)了INotifyPropertyChanged接口)。
簡(jiǎn)單的Data Binding使用
本示例以Slider的Value屬性作Source,Label的Opacity屬性作Target,實(shí)現(xiàn)拖動(dòng)滑塊影響Label透明度的效果。
代碼方式設(shè)置Data Binding:
核心代碼設(shè)置Target對(duì)象的BindingContext
屬性(BindableObject類型)。再調(diào)用Target 對(duì)象的SetBinding
方法設(shè)置綁定屬性關(guān)系,第一個(gè)參數(shù)targetProperty為BindableProperty類型,表示目標(biāo)屬性。第二個(gè)參數(shù)string類型,表示BindingContext的哪個(gè)屬性為Source。本例調(diào)用的是5個(gè)參數(shù)的方法,后三個(gè)參數(shù)為默認(rèn)值。
代碼運(yùn)行效果:
XAML方式設(shè)置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屬性通過(guò)x:Reference
指定,Opacity為目標(biāo)屬性通過(guò)Binding
擴(kuò)展標(biāo)記的Path設(shè)置。Path不僅可以是Property也可以是SubProperty或 Indexer.如Content.Children[4].Value
.
在整個(gè)視圖樹(shù)中子View是會(huì)繼承父布局的BindingContext屬性。如子View沒(méi)有單獨(dú)設(shè)置BindingContext屬性,會(huì)查找上級(jí)視圖若發(fā)現(xiàn)BindingContext賦值會(huì)直接繼承,如果上級(jí)視圖同樣沒(méi)有BindingContext賦值且存在上級(jí)視圖會(huì)繼續(xù)搜索上級(jí)視圖BindingContext的賦值。本例修改XAML布局代碼將Label的BindingContext刪除添加到StackLayout中,同樣會(huì)實(shí)現(xiàn)我們想要的效果。
<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>
同樣可以使用屬性節(jié)點(diǎn)定義方式設(shè)置Data Binding相關(guān)屬性
<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對(duì)應(yīng)的C#類為ReferenceExtension
,Binding對(duì)應(yīng)的類為BindingExtension
。兩個(gè)類的定義都指定了Content Property,分別為Name和Path,所以可以簡(jiǎn)化代碼:
BindingContext="{x:Reference slider}"
Opacity="{Binding Value}"
前面是通過(guò)BindingContext指定Data Binding的Source,還可以通過(guò)Binding指定Source。對(duì)應(yīng)的C#代碼為SetBinding兩個(gè)參數(shù)的方法:
BindingBase
為abstract類,F(xiàn)orms提供了Binding
類該類繼承了BindingBase。
通過(guò)Binding指定Source,再將Binding對(duì)象作為參數(shù)傳入SetBinding方法。
Binding 提供了重載的構(gòu)造函數(shù)和靜態(tài)方法Create<TSource>來(lái)創(chuàng)建Binding對(duì)象,不作介紹。
構(gòu)造函數(shù)示例
Create<TSource>示例
同樣X(jué)AML定義方式為,刪除BindingContext屬性賦值,修改Binding擴(kuò)展標(biāo)記。
根據(jù)內(nèi)容屬性簡(jiǎn)化XAML代碼:
Opacity="{Binding Value , Source={x:Reference slider}}"
擴(kuò)展標(biāo)記定義在一對(duì)大括號(hào)內(nèi)且大括號(hào)內(nèi)不應(yīng)出現(xiàn)雙引號(hào),指定多個(gè)屬性值時(shí)通過(guò)逗號(hào)分隔。
關(guān)于內(nèi)容屬性定義的簡(jiǎn)化寫(xiě)法《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.”大概意思是要省略內(nèi)容屬性的參數(shù)名稱必須將其放在第一個(gè)參數(shù),但是測(cè)試發(fā)現(xiàn)Opacity="{Binding Source={x:Reference slider} , Value}"
這種寫(xiě)法同樣可以。
那么問(wèn)題來(lái)了,如果我們給BindingContext賦值的同時(shí)也為Binding的Source賦值,應(yīng)該將哪個(gè)屬性對(duì)應(yīng)的對(duì)象作為數(shù)據(jù)源。符合就近原則Source的優(yōu)先級(jí)高于BindingContext,即指定Source時(shí)不在考慮BindingContext。且Source使用更加靈活,如一個(gè)對(duì)象的多個(gè)屬性使用不同對(duì)象作為數(shù)據(jù)源只能通過(guò)Source方式指定。
Binding Mode 介紹
現(xiàn)在要通過(guò)Data Binding實(shí)現(xiàn)兩個(gè)Slider的Value相互影響。滑動(dòng)一個(gè)Slider的同時(shí)另一個(gè)有相同變化。
愚蠢的辦法是分別將兩個(gè)Slider作為另一個(gè)的Source,即同時(shí)為兩個(gè)Slider設(shè)置Data Binding。上一個(gè)Slider和Label的示例可以理解為Source影響Target,最簡(jiǎn)單的辦法就是可以使Source和Target相互影響。BindingMode
枚舉可以幫助我們定義target 和 source之間的綁定模式。
BindingMode有四個(gè)枚舉值:
? Default
? OneWay — Source 的改變影響Target的值(通常是這種情況).
? OneWayToSource — Target的改變影響Source的值.
? TwoWay — Source和Target值改變會(huì)相互影響.
對(duì)于可讀寫(xiě)的BindableProperty對(duì)象默認(rèn)BindingMode為OneWay,只讀的BindableProperty對(duì)象默認(rèn)BindingMode為OneWayToSource。
大多數(shù)BindableProperty對(duì)象BindingMode默認(rèn)值為OneWay,以下控件的Property的BindingMode方式默認(rèn)是TwoWay:
由于Slider的Value默認(rèn)BindingMode為T(mén)woWay,所以實(shí)現(xiàn)兩個(gè)Slider連動(dòng)XAMlL定義為:
<StackLayout>
<Slider x:Name="slider" VerticalOptions="CenterAndExpand" />
<Slider BindingContext="{x:Reference Name=slider}" Value="{Binding Path=Value}" VerticalOptions="CenterAndExpand" />
</StackLayout>
通過(guò)XAML明確指定BindingMode的值Value="{Binding Path=Value Mode=TwoWay}"
。通過(guò)C#代碼指定BindingMode的值slider.SetBinding(Slider.ValueProperty,"Value",BindingMode.TwoWay);
。
Binding StringFormat 介紹
再次把Slider作為Source,Label作為T(mén)arget。將Slider的Value值綁定到Label的Text。Value值ToString后直接顯示到Label上可能不是我們期望的,Binding類提供了StringFormat
屬性表示.NET格式化字符串。
XAML中StringFormat使用,因?yàn)镾tringFormat本身會(huì)包含一對(duì)大括號(hào),所以StringFormat賦值時(shí)要包含一對(duì)單引號(hào):
C#代碼設(shè)置StringFormat:
label.SetBinding(Label.TextProperty, "Value", stringFormat: "Slider Value Is {0:F3}");
Binding IValueConvert 介紹
目前示例Target需要的數(shù)據(jù)為string,默認(rèn)轉(zhuǎn)換或StringFormat可以實(shí)現(xiàn)效果。但是Data Binding的Target接受數(shù)據(jù)類型為一個(gè)對(duì)象時(shí)如何處理?我們可以通過(guò)value converter
類完成Source到Target的類型轉(zhuǎn)換,需要實(shí)現(xiàn)IValueConverter
接口,接口有Convert
和ConvertBack
兩個(gè)方法。
當(dāng)數(shù)據(jù)由Source轉(zhuǎn)換到Target時(shí)調(diào)用Convert方法。Convert方法中value表示Source傳遞的值,你可以通過(guò)GetType來(lái)確定它的類型,也可以默認(rèn)一種類型來(lái)處理。targetType表示Target需要的數(shù)據(jù)類型,Convert方法返回的類型應(yīng)與targetType相同。parameter在Binding 中會(huì)用到,culture為CultureInfo類型需要和地域文化相關(guān)的轉(zhuǎn)換時(shí)會(huì)用到。
ConvertBack方法會(huì)在數(shù)據(jù)由Target轉(zhuǎn)換到Source 時(shí)調(diào)用,只有Binding Mode為T(mén)woWay 或者 OneWayToSource時(shí)才有必要實(shí)現(xiàn)該方法,否則直接返回null即可。value參數(shù)表示target傳遞的值,targetType表示source的Type類型。
示例實(shí)現(xiàn)效果,一個(gè)Entry 和一個(gè)Button,當(dāng)Entry中內(nèi)容為空時(shí)Button不可用,點(diǎn)擊Button清空Entry。
定義IntToBoolConverter實(shí)現(xiàn)IValueConverter,本示例中Entry為Source,Entry的Text.Length為Path,Button為T(mén)arget,IsEnabled為綁定的屬性。所以自定義的Converter應(yīng)有Int轉(zhuǎn)換為bool的能力(Convert方法,本例中ConvertBack可直接返回null)。
XAML中使用Converter要先在Resources
字典中定義IntToBoolConverter對(duì)象,指定key值,在Binding時(shí)通過(guò)StaticResource
賦值。其中Resources相關(guān)內(nèi)容在Style中介紹。
如果Converter只使用一次,不必在Resources中定義,直接在Binding中通過(guò)屬性節(jié)點(diǎn)定義即可。
《Creating Mobile Apps with Xamarin.Forms》中提供了一個(gè)bool轉(zhuǎn)泛型的Converter類,可以將bool值轉(zhuǎn)換為我們想要的值。定義如下:
在XAML定義時(shí),通過(guò) x:TypeArguments
指定我們需要的類型,并設(shè)置TrueObject和FalseObject屬性。