寫在前面:這仍然是一些沒有經過嚴格審閱的文字。雖然我的確執行了初稿、複稿以及審閱等一系列用以保證文章品質的方法,但是仍然擔心其中是否有錯誤。希望您能協助指出,以在下一次我在版本更新時進行修正。所有的錯誤,包括別字、概念不清(表述錯誤等)、邊緣情況沒有覆蓋等,您認為有必要提及的各個方面,都可以是我們深入討論的話題。
在前面對綁定資料來源進行介紹的過程中,本文都是使用Binding類的Source屬性指定資料來源的。使用該屬性訪問繫結來源具有一些限制:軟體開發人員無法引用XAML中定義的元素或依某種規律尋找與繫結來源相關的元素。因此除了Source屬性之外,WPF還提供了ElementName、RelativeSource等方法以輔助完成對繫結來源的指定。
ElementName用來引用(甚至是後向引用)在同一NameScope中顯式指定名稱的介面組成。綁定將沿其所在的元素沿邏輯樹狀結構向上尋找,直到遇到第一個具有NameScope的元素並嘗試在該NameScope中尋找該名稱。(並不精確,卻會是您所接觸的大多數情況)
ElementName屬性與Source屬性互斥,即在同時設定這兩個屬性的時候,程式會在運行時拋出異常。在正確引用了所需要指定的介面元素以後,當前綁定的繫結來源將會是該介面元素,而具體需要綁定的屬性則由Path屬性所指定。
RelativeSource則用來指定與當前介面元素相關的繫結來源。使用它進行尋找的方式分為幾種。尋找自身時,使用Self模式:
1 {Binding RelativeSource={RelativeSource Self}}
尋找祖先的特定類型時,使用FindAncestor模式:
1 {Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollContentPresenter}}
綁定應用模板的資料項目時,使用TemplatedParent模式:
1 {Binding RelativeSource={RelativeSource Mode=TemplatedParent}}
綁定到資料項目集合中當前項之前的資料項目時,使用PreviousData模式。在網路上使用PreviousData綁定的樣本較少,因此在這裡,我給出一個較為完整的樣本:
1 <Window …
2 xmlns:local="clr-namespace:Binding_PreviousData"
3 x:Name="MainWindow">
4 <Window.Resources>
5 <local:HistoryConverter x:Key="historyConverter"/>
6
7 <DataTemplate x:Key="historyTemplate">
8 <TextBlock>
9 <TextBlock.Text>
10 <MultiBinding Converter="{StaticResource historyConverter}">
11 <Binding RelativeSource="{RelativeSource PreviousData}"/>
12 <Binding/>
13 </MultiBinding>
14 </TextBlock.Text>
15 </TextBlock>
16 </DataTemplate>
17 </Window.Resources>
18 <StackPanel>
19 <DockPanel>
20 <Button DockPanel.Dock="Right" Content="Submit" Click="OnButtonClicked"/>
21 <TextBox x:Name="mInput"/>
22 </DockPanel>
23 <ListBox ItemTemplate="{StaticResource historyTemplate}" ItemsSource="{Binding ElementName=MainWindow, Path=History}"/>
24 </StackPanel>
25 </Window>
1 public partial class Window1 : Window
2 {
3 …
4 public ObservableCollection<string> History…
5 }
通過Source、ElementName、RelativeSource等屬性提供繫結來源的方法彼此擁有一定的互補性。一般說,Source用來引用資料層中的資料,ElementName引用的是UI邏輯樹狀結構中的組成,而RelativeSource則常常用來從視覺樹中尋找繫結來源,甚至可以穿越當前XAML檔案根項目。因此在決定繫結來源時,軟體開發人員需要根據實際尋找方式決定需要使用的方式。
在綁定中,指定了綁定的源並不足夠。請考慮指定繫結來源的各種方法:ElementName用來引用邏輯樹狀結構中所給出的元素;RelativeSource則用來指定與當前元素相關聯的其它元素;而Source則較為通用,只是其所接受的各種運算式,如StaticResource,常常用來引用具有特定性質的執行個體。而綁定所操作的,常常是這些源所具有的各個屬性,甚至是子屬性。軟體開發人員需要一種方法指定參與綁定的繫結來源的屬性。這也便是綁定提供Path屬性及XPath屬性的原因。
一般情況下,綁定的Path所指定的各級屬性都需要提供屬性更改通知的支援,至少軟體開發人員應能保證在需要綁定執行時屬性更改通知能及時發出。例如在INotifyPropertyChanged介面的實現中,軟體開發人員需要顯式地發送PropertyChanged事件。而就DependencyProperty而言,其更改通知的發送則由WPF屬性系統輔助解決。另外,綁定的Path屬性提供了對結構化資料的支援,如對於Point類型的屬性Location,軟體開發人員可以設定Path為Location.X。這樣做的好處在於,提供結構化資料的支援可以擁有更好的語義特徵。另一個優點則在於,更新該結構化資料可以避免其內的各個屬性在更新時擁有先後順序,從而使眾多依賴於這些資料的綁定在錯誤狀態中執行綁定邏輯。
另一個常見的疑惑則是有關綁定的更新:如果在綁定中指明路徑為X.Y,並且X發出Y更改的訊息,那麼綁定是否會執行。答案是會。
如果綁定的源是XML資料而不是CLR對象,那麼軟體開發人員需要使用XPath屬性指定要使用的繫結來源,而不是Path。
在講解完繫結資料源後,請讀者來看看綁定的目標。WPF規定綁定的目標屬性必須是依賴項屬性,而不能是欄位等其它組成。您可能心中有疑問:為什麼綁定的目標屬性必須是依賴項屬性?原因很簡單:除了對綁定的支援之外,依賴項屬性的更改還可能導致布局等功能的變化。如TextBlock的Text屬性變化可能會導致TextBlock所需要的空間變大。此時屬性更改不僅僅要參與綁定功能的執行,更需要以非常高效的方式參與布局系統以及繪製系統等等各WPF子系統。該高效參與各子系統的方式就是依賴項屬性。
由於一般綁定都使用在XAML中,因此對繫結目標的使用也常常是水到渠成的事情:綁定的目標屬性常常是DependencyProperty,而被綁定的屬性所處於的執行個體便自然是DependencyObject。
如果僅僅提供從源屬性到目標屬性的聯動,那麼綁定的作用可能並不那麼大。考慮這樣一個情況:如果軟體開發人員希望將源屬性綁定到TextBox的Text屬性,那麼在使用者更改TextBox所顯示的文字時,源屬性將與目標屬性不再匹配。這樣的例子有很多,而WPF所提供的解決方案就是綁定的Mode屬性。其主要分為四種模式:Twoway、Oneway、OnewayToSource以及OneTime。Twoway模式所提供的功能最為強大。一旦繫結來源中參與綁定的屬性發生變化,或是綁定的目標屬性發生變化,那麼綁定都將執行。該模式下的綁定不僅僅會將繫結來源屬性的變化傳遞到目標屬性,更可以在目標屬性發生變化時將變化傳遞到源屬性。這種綁定常常使用在繫結目標屬性可以從使用者介面更改的情況下,如TextBox的Text屬性。另一種模式Oneway可以說是最常用的繫結模式。如果繫結來源中參與綁定的屬性是一個唯讀屬性,或者綁定的目標屬性不會由於其它外界因素所更改,那麼Oneway綁定是綁定的最佳選擇。與Oneway模式類似的是OnewayToSource模式。該模式可以用來繞過綁定對繫結目標屬性的限制:綁定要求其目標屬性必須是DependencyProperty,而在某些情況下,軟體開發人員需要一個不是DependencyProperty的屬性根據一個DependencyProperty屬性的變化而變化。最後,正如其名稱所表現的一樣,OneTime模式僅僅運行一次。
從效能上來講,OneWay模式綁定的開銷較TwoWay模式的開銷小。而OneTime則是較OneWay模式更為輕量級的繫結模式。
與繫結模式相關的一個知識點則是:在建立一個DependencyProperty作為繫結來源時,依賴項屬性的中繼資料中的BindsTwoWayByDefault屬性用來控制一個依賴項屬性在綁定時是否預設為雙向繫結。
接下來要講解的則是綁定更新通知。綁定提供了兩個附加事件SourceUpdated、TargetUpdated。如果需要啟用這兩個附加事件,軟體開發人員需要將相應的屬性NotifyOnSource(Target)Updated設定為True。通過該附加事件,軟體開發人員可以將事件處理邏輯與其它介面元素進行互動,從而擴充了綁定執行邏輯的靈活性。另外,一個屬性的更新常常與其所處於的更新模式相關。軟體開發人員可以通過UpdateSourceTrigger屬性確定觸發源更新的原因,如TextBox的更新條件。
除此之外,WPF中的綁定還擁有另外一個模式:非同步模式。與該模式相關的屬性則為IsAsync。在將該屬性的值設定為True的情況下,Binding將會從線程池中取出一個線程處理該綁定,以避免在綁定求值時阻塞UI。在屬性的訪問符沒有返回的時候,綁定會暫時使用FallbackValue作為綁定的值。在沒有設定FallbackValue的時候,綁定結果將為繫結目標屬性的預設值。該繫結模式在源屬性值需要較長時間才能獲得的情況下非常有用。
XmlDataProvider.IsAsynchronous以及ObjectDataProvider.IsAsynchronous屬性同樣提供了該功能。
另外一個與綁定相關的重要概念就是主從模式:如果在綁定的執行個體中將屬性Selector.IsSynchronizedWithCurrentItem設定為true,那麼當前選定項將與之相關的CollectionView的CurrentItem屬性同步。那麼其它不接受集合類型資料,只接受單一資料的屬性直接綁定到該集合資料項目屬性的時候實際上是綁定到了該關聯CollectionView的CurrentItem屬性上。
另一個問題是:當綁定的目標與繫結來源中的屬性不一致時該怎麼處理?實際上,這就是綁定的轉換器所提供的功能。在通過Converter屬性標示了綁定所使用的轉換器後,綁定的每次運行都會調用該轉換器,以將源屬性的值轉換為所需要的目標屬性的值。在使用轉換器的時候,軟體開發人員還可以通過ConverterParameter屬性為轉換器標明參數,以允許轉換器在轉換目標屬性的過程中使用該參數。
實現一個綁定的轉換器非常簡單:軟體開發人員只需要實現IValueConverter或IMultiValueConverter即可:
1 internal class IntToVisibilityConverter : IValueConverter
2 {
3 public Object Convert(object value, Type typeTarget, object param, CultureInfo culture)
4 {
5 return (int)value == 0 ? Visibility.Collapsed : Visibility.Visible;
6 }
7
8 public Object ConvertBack(object value, Type typeTarget, object param, CultureInfo culture)
9 {
10 throw new NotSupportedException();
11 }
12 }
在實現一個轉換器時,軟體開發人員最好使用ValueConversion特性修飾此實現,以向開發工具指示轉換所涉及的資料類型。
既然提到了IMultiValueConverter,那麼就不得不提到另一種綁定:MultiBinding。該綁定以多個綁定作為子項目,並在每個子綁定的繫結來源發生變化後被執行。每個綁定啟動並執行結果都會通過IMultiValueConverter所實現的轉化邏輯將來源資料轉化為目標屬性所需要的值。也正是由於這種一個綁定發生變化時MultiBinding就會被執行的特性,軟體開發人員需要在使用MultiBinding時不得不考慮一些特殊情況。一個較為嚴重的情況則是兩個相互關聯資料所參與的MultiBinding。在其中一個資料發生改變的時候,MultiBinding將被執行以反映資料的更新。但是此時與該資料相關聯的其它資料並非處於正確的狀態,從而導致錯誤的結果,更嚴重地,程式崩潰。其中一個不適合使用MultiBinding的情況就是求子串。如果使用MultiBinding傳入需要操作的字串以及子串的起始位置,那麼在任意一個資料來源發生改變的時候另一個資料可能是非法的,如在字串發生變化的時候,子串的起始位置將可能大於字串的長度。解決該問題的方法則是為這些相關聯的資訊提供一個結構化的資料,並以其作為綁定的源屬性。每次字串發生變化的時候,軟體開發人員建立一個新的該結構化資料,並使其正確記錄字串以及子串的起始位置。在對該結構化資料進行更新時,其內部所記錄的資料將是統一的,從而保證綁定的正確執行。
另一個需要提及的知識點則是子綁定對MultiBinding類屬性的繼承。MultiBinding類的Mode屬性以及UpdateSourceTrigger屬性的值將被其各個子綁定繼承,除非其中的子綁定重寫該屬性。
MultiBinding當前只支援Binding類型的對象,而不支援MultiBinding或PriorityBinding類型的對象。這是因為Converter本身已經支援對這兩種對象的類比。
除了MultiBinding外,另一個較為特殊的綁定則是PriorityBinding。其同樣接受一組Binding作為子項目,並為這些子綁定依次賦予由高到低的優先順序。在運行時,PriorityBinding將返回當前成功執行的具有最高優先順序的綁定的值。而在所有Binding都沒有成功執行或沒有返回的情況下,FallbackValue所標示的值將被使用。一般情況下,該綁定用來處理繫結來源屬性需要較長時間才能成功返回的情況。其與非同步模式綁定具有一定的相似性。但不同的是,首先,非同步模式綁定所標示的FallbackValue只能在XAML中標明,如果軟體開發人員希望FallbackValue會根據資料層狀態而改變,那麼他需要選擇PriorityBinding,並為該PriorityBinding賦予一個具有最低優先順序的綁定。同時具有最低優先順序的綁定常常是一個同步綁定,以在PriorityBinding中作為FallbackValue使用。其次,非同步模式綁定並不支援多個綁定。在需要執行大量耗時操作,卻希望給使用者一個粗略計算結果的情況下,在PriorityBinding中使用多個Binding並同時執行這兩種計算常常是一個較為合適的解決方案。最後,PriorityBinding所提供的非同步特性實際上是通過其各個子綁定的非同步特性所提供的。
綜上所述,PriorityBinding的這種運行方式決定了各子綁定的運行方式:除了最後一個子綁定外,其它的各個綁定的IsAsync屬性需要被設定為True,以令這些耗時功能的執行以非同步方式完成,並在完成後對PriorityBinding的運行結果進行適當更新。而對於最後一個子綁定而言,將IsAsync設定為True則不再是一個強制要求。如果IsAsync屬性並沒有被設定為True,那麼最後一個綁定將作為一個更靈活的FallbackValue;如果IsAsync屬性並沒有被設定為True,那麼所有的綁定都將非同步執行,綁定所最初顯示的則是由PriorityBinding的FallbackValue所指定的資料。
另外,對PriorityBinding中的各個非同步子綁定提供FallbackValue會影響到PriorityBinding的執行。如果您有興趣,可以自行實驗一下。就個人經驗而談,軟體開發人員不應設定非同步子綁定的FallbackValue。
現在,您腦中可能有這樣一個疑問:看起來,在提供了合適的轉換器的情況下,MultiBinding同樣可以達成PriorityBinding的效果。對於這個問題,我想我的答案是肯定的。下面就是我為這個問題所提供的簡單實現:
1 <Window …
2 xmlns:local="clr-namespace:SimulatedPriorityBinding">
3 <Window.Resources>
4 <local:PrioritizedConverter x:Key="prioritizedConverter"/>
5 </Window.Resources>
6 ……
7 <MultiBinding Converter="{StaticResource prioritizedConverter}">
8 <Binding … Path="MoreSlowProperty" IsAsync="True"/>
9 <Binding … Path="SlowProperty" IsAsync="True"/>
10 <Binding … Path="QuickProperty"/>
11 </MultiBinding>
12 </Window>
1 public class PrioritizedConverter : IMultiValueConverter
2 {
3 public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
4 {
5 foreach (object value in values)
6 if (value != DependencyProperty.UnsetValue)
7 return value;
8 return DependencyProperty.UnsetValue;
9 }
10
11 public object[] ConvertBack…
12 }
其實這種功能的重疊,甚至PriorityBinding只提供MultiBinding所提供功能的子集這一行為並不足以為奇。WPF常常會為一些常用的文法結構提供了簡化版本並可能藉此提高效能,像TemplateBinding之於使用TemplatedParent模式的Binding。這和C#語言所具有的嚴格特徵略有不同(即C#的前幾個版本的設計原則為:一件事情,盡量不提供兩種方法去做。舉例來說,靜態成員函數的調用只能通過類型進行,而C++既可通過類型又可以通過類型執行個體完成)。
另一類比較特殊的綁定則是TemplateBinding。其實際上類似於使用了TemplatedParent的Binding,但較Binding所提供的功能更少,也因為更輕量而具有更高的效率。這是因為在TemplateBindingExpression類中,GetValue的實現僅僅是獲得特定屬性的值,而不是繁瑣的計算。由於其固定地綁定到使用該模板的資料執行個體上,因此該類僅僅提供了Property屬性以指定需要綁定到的屬性。
(未完待續)
源碼下載:http://download.csdn.net/detail/silverfox715/3907934
轉載請註明原文地址:http://www.cnblogs.com/loveis715/archive/2011/12/15/2288272.html
商業轉載請事先與我聯絡:silverfox715@sina.com