“模板數(shù)據(jù)綁定”從XAML到代碼CODE
數(shù)據(jù)綁定(data binding)指的是將控件和element連接到數(shù)據(jù)的一種機制。數(shù)據(jù)綁定可以很簡單,例如將CheckBox控件連接到一個Boolean(布爾)變數(shù);也可以很復(fù)雜,將整個數(shù)據(jù)庫連接到一個數(shù)據(jù)面板(panel)。
在GUI上面呈現(xiàn)控件,一般而言有兩種目的,一方面是向使用者顯示數(shù)據(jù),另一方面是允許使用者改變數(shù)據(jù)。然而,在現(xiàn)代的API中,許多數(shù)據(jù)和控件之間的例行連接,都已經(jīng)被自動化了。在過去,編程員必須寫程序代碼,用布爾變數(shù)的值來初始化一個CheckBox,且將使用者操作過后的CheckBox值寫到布爾變量中。在今天的現(xiàn)代化編程環(huán)境,程序員只需要定義CheckBox和此變量之間的綁定,就可以了,由數(shù)據(jù)綁定自動進行這兩件工作。數(shù)據(jù)綁定已經(jīng)變成一種相當普遍的做法,不只WPF,連Flex/Apollo也采用一樣的做法,甚至連語法都很類似。
數(shù)據(jù)綁定常常用來取代事件處理器(event handler),這么做可以幫助程序代碼更簡潔,特別是,如果在XAML中使用數(shù)據(jù)綁定取代事件處理器,可以使得我們不需要在code-behind文件中編寫事件處理器。在某些例子中,甚至因此可以完全不用code-behind文件。(當然,事件處理器還是存在,只是被藏了起來,我們目前看不見。)
來源與目標
每個數(shù)據(jù)綁定都具有來源(source)和目標(target)。一般來說,來源是某種數(shù)據(jù),目標則是一個控件,但是實際上,你會發(fā)現(xiàn)來源和目標的區(qū)別有時候很模糊,有時候似乎會角色錯亂,竟然由目標提供數(shù)據(jù)給來源。雖然來源與目標這種方便的稱呼方式,無法明確描述數(shù)據(jù)的流向,但是來源與目標的區(qū)別還是相當重要的。
最簡單的綁定,就是兩個控件之間的綁定。比方說,假設(shè)你想要使用一個Label來觀看某ScrollBar的Value property。你可以為ScrollBar安裝一個ValueChanged事件處理器,或者你可以用更簡單的方式,直接定義一個數(shù)據(jù)綁定,如下面的XAML片段所展示的這樣:
<ScrollBar Name="scroll"
Orientation="Horizontal" Margin="24"
Maximum="100" LargeChange="10" SmallChange="1" />
<Label HorizontalAlignment="Center"
Content="{Binding ElementName=scroll, Path=Value}" />
綁定本身一定是在綁定的目標上做設(shè)定。在此XAML片段中,綁定是設(shè)定在Label的Content property上,語法如下:
Content="{Binding ElementName=scroll, Path=Value}"
Binding也是一種markup extension。大括號出現(xiàn)在Binding定義的周圍。ElementName與Path都是Binding類別的property,都可以出現(xiàn)在此定義中。在此Binding定義中,ElementName被設(shè)定成scroll,這是ScrollBar的名字(指定在Name attribute中);此Binding 的Path property被設(shè)定為Value,在這里就是參考到ScrollBar的Value property。此Label的Content property然后會被綁定到此ScrollBar的Value property。當你操作ScrollBar時,Label就會顯示出目前的值。
盡管是XAML老手,你還是有可能會不小心在Binding的定義內(nèi)使用引號。ElementName和Path看起來都像是XML attribute,所以你的手指頭會不小心鍵入這樣:
Content="{Binding ElementName="scroll" Path="Value"}"
這是不對的!不只不可以在大括號內(nèi)使用引號,而且一定要在ElementName和Path項目之間使用逗號區(qū)隔。
另一方面,如果你真的無法控制自己,就是會不小心地在綁定定義內(nèi)鍵入引號,那么或許你應(yīng)該改用property element的語法。
<ScrollBar Name="scroll"
Orientation="Horizontal" Margin="24"
Maximum="100" LargeChange="10" SmallChange="1" />
<Label HorizontalAlignment="Center">
<Label.Content>
<Binding ElementName="scroll" Path="Value" />
</Label.Content>
</Label>
綁定的目標不是隨便什么都可以,綁定必須建立在有支持dependency property的property上,因為控件和element都是設(shè)計成將改變反映在它們的 dependency property。綁定的目標必須衍生自DependencyObject。綁定所設(shè)定的property必須有支持dependency property。因此,在這個例子中,Label需要有一個字段元,類型為DependencyProperty,名稱為ContentProperty。
但是對于一個綁定來源來說,要求就寬松許多了。來源中被綁定的property,不需要是dependency property。在理想的例子中,property應(yīng)該和事件有關(guān)連,此事件會指出何時此property改變了,但是有些綁定甚至可以在沒有通知事件的情況下運作。
模式變化
雖然來源和目標的稱呼方式隱含的意義是:來源element(本例是指ScrollBar)會促使目標(Label)的改變,但其實這只是四種可能的綁定模式(mode)之一。你可以利用Mode property和BindingMode列舉的成員,指定你想要的模式。預(yù)定是這樣的:
Content="{Binding ElementName=scroll, Path=Value, Mode=OneWay}"
請注意,Mode property的設(shè)定是和Path property的設(shè)定分開的,中間有一個逗號。在BindingMode列舉成員OneWay的前后,并沒有使用引號。如果你比較喜歡property element語法,你可以改用下面的寫法:
<Label.Content>
<Binding ElementName="scroll" Path="Value" Mode="OneWay" />
</Label.Content>
你也可以設(shè)定模式為TwoWay:
Content="{Binding ElementName=scroll, Path=Value, Mode=TwoWay}"
此程序的作用其實和OneWay時一樣,但是理論上,Label的Content property如果有改變,也會反映在ScrollBar的Value property。下面是另一種可能性:
Content="{Binding ElementName=scroll, Path=Value, Mode=OneTime}"
OneTime模式的意思是目標會從來源取得數(shù)據(jù),進行初始化,但是不會持續(xù)追蹤改變。在此程序中,Label顯示0,因為ScrollBar的初始Value property就是0。如果你操作ScrollBar,你會看到Label的值一直不受影響。(你可以把ScrollBar element的Value初始設(shè)定為50,這么一來,你會看到Label顯示50。)
最后的選項是:
Content="{Binding ElementName=scroll, Path=Value, Mode=OneWayToSource}"
初次看到此模式會讓人遲疑,因為此模式指示來源要依據(jù)目標來更新,這等于是將來源和目標的角色對調(diào)。在這個例子中,目標(Label)應(yīng)該要更新來源(ScrollBar),但是Label沒有數(shù)字數(shù)據(jù)可以提供給ScrollBar。此Label是空白的,且會當你移動ScrollBar時,會維持空白。
雖然,OneWayToSource模式似乎反常,但是當你想要建立某種綁定,而目標的property不支持dependency property,且來源的property有支持dependency property時,你就會發(fā)現(xiàn)這個模式的妙用了:將綁定放在來源,將模式設(shè)定為OneWayToSource。
綁定的預(yù)定(default)Mode是由定義此綁定的此property所控制。例如:ScrollBar的Value property預(yù)定的綁定Mode是TwoWay。Mode property是綁定最重要的一部份,我們不需要去猜測(或查詢)預(yù)定的綁定模式為何,而是應(yīng)該好好考慮,決定該使用什么Mode會比較恰當,然后明確地做設(shè)定。
DataContext
DataContext property是另一種表達綁定來源對象的方式,請看下面的例子:
<ScrollBar Name="scroll"
Orientation="Horizontal" Margin="24"
Maximum="100" LargeChange="10" SmallChange="1" />
<Label HorizontalAlignment="Center"
DataContext="{Binding ElementName=scroll}"
Content="{Binding Path=Value}" />
Label的DataContext與Content,都被設(shè)定到一個Binding的定義中,此定義被分成兩部分。第一部份的Binding定義指示ElementName,且第二個部分具有Path。
在此范例中使用DataContext property,沒有好處,但是在某些其它的例子,DataContext property可能相當有價值。DataContext是可以在element tree中被沿襲,所以如果你為一個element設(shè)定DataContext,則該element所有的孩子都會受到影響。
如果一個面板內(nèi),許多控件都綁定到一個特定對象的各種property上,這種狀況下,只要將此DataContext設(shè)定成該型態(tài)的不同對象,所有的控件都會反映此新對象。
dependency property的好處
綁定的來源不需要是dependency property。到目前為止,所有的數(shù)據(jù)綁定范例的目標和來源,都同時具備后端的dependency property,但是本章稍后會有不一樣的例子,綁定來源是傳統(tǒng)的(沒有dependency property)的.NET property。字在一般的例子中,OneWay綁定牽涉到從來源到目標的信息連續(xù)傳送。單向的綁定想成功,來源必須實現(xiàn)某種機制,以使得來源一有變動就會讓目標被通知。
前面所謂的某種機制,當然可能是指事件,但是還有別的可能。dependency property之所以被發(fā)明,其中一個原因是為了數(shù)據(jù)綁定,且dependency property系統(tǒng)具有內(nèi)建的通知(notification)支持。綁定來源不需要是dependency property,但是如果是的話,會有幫助的。只要定義DependencyProperty,就可以免費得到數(shù)據(jù)綁定通知。
兩個metadata標志影響數(shù)據(jù)綁定,如果你包含F(xiàn)rameworkPropertyMetadataOptions.NotDataBindable標志,其它的元素flag,仍然可以綁定到dependency property,但是你無法在一個dependency property本身定義一個數(shù)據(jù)綁定。(換句話說,將具有此flag的dependency property,不會是數(shù)據(jù)綁定的目標)。FrameworkPropertyMetadataOptions.BindsTwoWayByDefault標志只會影響dependency property為目標的綁定。
前面看過,你將Binding的Path property設(shè)定成這些來源對象的property。那么,為何要叫做Path?為何不叫做Property?
之所以稱為Path,因為它可以不只一個property??梢允且贿B串的property(可能附有索引)利用句號結(jié)合在一起,看起來像是C#程序代碼,但是卻沒有strong typing的麻煩。例如:
Path=Content.Children[1].SelectedItem.Content.Length
雖然這可能看起來像是C#程序代碼的一連串套迭property,但是這只是XMAL內(nèi)的字符串,且會被當作字符串進行解析(parse)。解析器使用refelction來決定這些項目是否有意義,如果沒有意義,解析的步驟會整個放棄,不產(chǎn)生任何結(jié)果或任何例外。
數(shù)據(jù)轉(zhuǎn)換與多重綁定
當數(shù)據(jù)從綁定來源送到目的地時(且有時候是反方向的),數(shù)據(jù)可能需要轉(zhuǎn)換類型。Binding類包含一個property,名為Converter,讓你可以指定轉(zhuǎn)換器類。此類必須包含兩個方法:Convert與ConvertBack,以進行轉(zhuǎn)換。
進行轉(zhuǎn)換的類必須實現(xiàn)IValueConverter接口,看起來像這樣:
public class MyConverter: IValueConverter
{
public object Convert(object value,
Type typeTarget,object param,
CultureInfo culture)
{
...
}
Public object ConvertBack(object value,
Type typeTarget,object param,
CultureInfo culture)
{
...
}
}
這里的value參數(shù)是要被轉(zhuǎn)換的對象,而typeTarget是要被轉(zhuǎn)換成的型態(tài),也就是此方法傳出值的型態(tài)。如果它無法轉(zhuǎn)換成該指定型態(tài),此方法應(yīng)該要傳出null。第三個參數(shù)是Binding的ConvertParameter property會指定的對象,最后一個參數(shù)用來指示轉(zhuǎn)換時要注意到的地域文化(culture)。
如果你是在C#中建立Binding,你可以將Convert property設(shè)定成一個有實現(xiàn)IValueConverter接口的對象:
Binding bind = new Binding();
bind.Convert = new MyConverter();
你也可以將Binding的ConvertParameter property設(shè)定成一個對象,被當作param參數(shù)傳遞進入Convert與ConvertBack,以控制轉(zhuǎn)換過程。
有一種綁定一定會需要轉(zhuǎn)換類別,那就是多重綁定(multi-binding)。多重綁定從多個來源聯(lián)合多個對象,進入一個單一的目標(例如,將紅、綠、藍三原色合并成為單一個Color對象)。多重綁定轉(zhuǎn)換器,必須實現(xiàn)此IMultiValueConverter接口。
延遲更新
我們目前為止所看過的binding,都從來源source element立即更新目標。有時候這不一定是我們想要的。當你在TextBox鍵入文字,改變底下數(shù)據(jù)庫的某個字段,你希望每按下一次按鍵就改變數(shù)據(jù)庫一次嗎?其中還包括你打錯字并按下退格鍵做修改的部分。當然不!只有當你完成文字的輸入,你才希望來源被更新,而判別這個時機最簡單的方式,就是當你此TextBox失去輸入焦點的時候。
藉由設(shè)定此綁定的UpdateSourceTrigger property,你可以改變更新數(shù)據(jù)的行為。你可以將它設(shè)定為UpdateSourceTrigger列舉的一個成員,LostFocus(這是TextBox的Text property預(yù)定值)、PropertyChanged(對于大多數(shù)的property來說最常見)、或Explicit(這需要程序做出特別的動作,來反映在來源上)。
例如,將綁定改成這樣:
Text="{Binding ElementName=txtbox1, Path=Text,
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
現(xiàn)在每次對TextBox按下按鍵,來源的TextBox會跟著改變。
UpdateSourceTrigger.Explicit選項需要做更多事才能運作。使用此選項的程序,也必須準備對定義此綁定的element引用其GetBindingExpression方法(這是FrameworkElement所定義的方法),自變量是此綁定所牽涉到的DependencyProperty:
BindingExpression bindexp =
txtboxSource.GetBindingExpression(TextBox.TextProperty);
當你想要從目標更新來源時(或許當按下標示Update的按鈕時),就引用:
bindexp.UpdateSource();
此引用無法推翻綁定模式。綁定模式必須是TwoWay或OneWayToSource,否則此引用會被忽略。
Source property
如果你想要開始從數(shù)據(jù)庫以及其它外部類別和對象的角度,思考關(guān)于數(shù)據(jù)綁定的一切,那么需要停用ElementName property,改用Source property。Source property參照到一個對象,而Path繼續(xù)指到該對象的一個property(或者一連串property)。
對于Source來說,一個可能性是x:Static markup extension。x:Static讓XAML文件可以參照到一個類別內(nèi)的一個靜態(tài)字段或property。在某些例子(例如用到Environment類別的靜態(tài)字段),你可以使用x:Static本身取出那些property。然而,有可能你真正需要的是被此靜態(tài)property所參考到的對象的某個property。這種狀況下,你需要透過Source property來使用綁定。