WPF裏分三種Binding:Binding, PriorityBinding, MultiBinding,這三種Binding的基類都是BindingBase,而BindingBase又繼承於MarkupExtension
Binding
提供對綁定定義的高級別訪問,綁定將綁定目標對象(通常爲 WPF 元素)的屬性與任何數據源(例如數據庫、XML 文件或包含數據的任何對象)連接起來。
常見的使用Binding的代碼:
C#
Binding binding = new Binding(); // Set source object binding.Source = treeView; // Set source property binding.Path = new PropertyPath("SelectedItem.Header"); // Attach to target property currentFolder.SetBinding(TextBlock.TextProperty, binding);
XAML:
<TextBlock x:Name=”currentFolder” DockPanel.Dock=”Top” Text=”{Binding ElementName=treeView, Path=SelectedItem.Header}” Background=”AliceBlue” FontSize=”16”/>
所有FrameworkElement都包含SetBinding方法:SetBinding(DependencyProperty dp, String path), SetBinding(DependencyProperty dp, BindingBase binding),可以看出,FrameworkElement中的SetBinding只對DependencyProperty有效。
另一種設置Binding的方法是:BindingOperations.SetBinding(currentFolder, TextBlock.TextProperty, binding);
BindingOperations.SetBinding的原型是
public static BindingExpressionBase SetBinding( DependencyObject target, DependencyProperty dp, BindingBase binding )
第一個參數是DependencyObject,所以我們可以對自定義DependencyObject或者繼承自DependencyObject的類進行綁定。當然第二個參數還是DependencyProperty。
清除Binding:
BindingOperations.ClearBinding(currentFolder, TextBlock.TextProperty); //刪除currentFolder上的TextBlock.TextProperty綁定
BindingOperations.ClearAllBindings(currentFolder); //刪除currentFolder上的所有綁定
直接對dependency property賦值也可以解除binding, 不過只對單向binding有效。
Bingding的源:
有三個屬性用來設置源:ElementName(string)、Source(Object) 和 RelativeSource(RelativeSource)。注:這三個只能指定一個,否則異常。
ElementName: 源爲一個元素(Element),這裏用的是此元素中設置的Name屬性。
Source:以object作爲源。<TextBlock Text="{Binding Source={StaticResource myDataSource}, Path=PersonName}"/>
RelativeSource: 源相對於綁定目標的位置。
源是元素本身:{Binding RelativeSource={RelativeSource Self}}
源是Tempalte中元素的Parent:{Binding RelativeSource={RelativeSource TemplatedParent}}
源是綁定以collection形式的前一個數據:{Binding RelativeSource={RelativeSource PreviousData}},MSDN上關於PreviousData的說明並不多,這裏有一篇文章可以參考
以上三項爲RelativeSource中的Static值,使用這些值可以減少內存開銷
源是Ancestor(可能比parent還高):{Binding RelativeSource={RelativeSource FindAncestor,
AncestorLevel=n, AncestorType={x:Type desiredType}}}
Path:
Binding中的Path是 PropertyPath對象。
在最簡單的情況下,Path 屬性值是要用於綁定的源對象的屬性名稱,如 Path=PropertyName。
通過類似於 C# 中使用的語法,可以指定屬性的子屬性。例如,子句 Path=ShoppingCart.Order 將綁定設置爲對象的子屬性 Order 或屬性 ShoppingCart。
若要綁定到附加屬性,請將附加屬性用括號括起。例如,若要綁定到附加屬性 DockPanel.Dock,則語法爲 Path=(DockPanel.Dock)。
在應用了索引器的屬性名稱之後的方括號內,可以指定屬性的索引器。例如,子句 Path=ShoppingCart[0] 將綁定設置爲與屬性的內部索引處理文本字符串“0”的方式對應的索引。此外,還支持多個索引器。
在 Path 子句中可以同時使用索引器和子屬性,例如,Path=ShoppingCart.ShippingInfo[MailingAddress,Street]。
在索引器內部,可以有多個由逗號 (,) 分隔的索引器參數。可以使用圓括號指定每個參數的類型。例如,可以使用 Path="[(sys:Int32)42,(sys:Int32)24]",其中 sys 映射到 System 命名空間。
如果源爲集合視圖,則可以用斜槓 (/) 指定當前項。例如,子句 Path=/ 設置到視圖中當前項的綁定。如果源爲集合,則此語法指定默認集合視圖的當前項。
可以結合使用屬性名和斜槓來遍歷作爲集合的屬性。例如,Path=/Offices/ManagerName 指定源集合的當前項,該源集合包含同樣是集合的 Offices 屬性。其當前項是包含 ManagerName 屬性的對象。
也可以使用句點 (.)路徑綁定到當前源。例如,Text=”{Binding}” 等效於 Text=”{Binding Path=.}”。
BindingExpression
Binding 類是高級別類。BindingExpression 類是基礎對象,用於保持綁定源與綁定目標之間的連接。Binding 中包含可在多個 BindingExpression 對象之間共享的所有信息。也就是說,可以把一個Binding對象綁定對n個元素上,而針對這n個元素,分別有相應的n個BindingExpresion對象。
Binding可以直接綁定普通的.net實例,比如int值。但是如果後臺改變int值了,前臺不能顯示改變後的值,這時可以調用UpdateTarget()方法更新綁定。如下:BindingExpression be = button.GetBindingExpression(Button.ContentProperty);
be.UpdateTarget();
還有UpdateSource方法用來更新源。
綁定到.net屬性/對象:
上面提到Binding綁到普通的.net屬性,如果source變化了,UI上是不會顯示的,除了用BindingExpression每次顯式更新Target外,還可以使用如下技術:
綁定到單個對象需實現INotifyPropertyChanged接口,這個接口只有一個成員:
event PropertyChangedEventHandler PropertyChanged
實現INotifyPropertyChanged的示例如下:
using System.ComponentModel; namespace SDKSample { // This class implements INotifyPropertyChanged // to support one-way and two-way bindings // (such that the UI element updates when the source // has been changed dynamically) public class Person : INotifyPropertyChanged { private string name; // Declare the event public event PropertyChangedEventHandler PropertyChanged; public Person() { } public Person(string value) { this.name = value; } public string PersonName { get { return name; } set { name = value; // Call OnPropertyChanged whenever the property is updated OnPropertyChanged("PersonName"); } } // Create the OnPropertyChanged method to raise the event protected void OnPropertyChanged(string name) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(name)); } } } } 或者顯式實現INotifyPropertyChanged:
#region INotifyPropertyChanged Members event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { this.PropertyChanged = (PropertyChangedEventHandler)Delegate.Combine(this.PropertyChanged, value); } remove { this.PropertyChanged = (PropertyChangedEventHandler)Delegate.Remove(this.PropertyChanged, value); } } #endregion
看了上面代碼着實沒看出source值改變了,前臺是通過什麼機制反映的,正常的情況下公開了一個事件,必須有一個對此事件的實現體,而上面代碼並沒有實現PropertyChanged的方法。
我猜想是Binding內部獲取了這個接口並對PropertyChanged進行了賦值,因爲在debug時,這個事件確實被賦值的,而賦值前的Stack是External Code調用的。
綁定到集合需實現INotifyCollectionChanged,但是推薦使用ObservableCollection<T>,這個類實現了INotifyCollectionChanged和INotifyPropertyChanged。
附:當綁定到普通的.net屬性時,WPF使用反射取得source的值,當對象實現ICustomTypeDescriptor時,WPF使用這個接口取得值,性能上會有所提升。
DataContext:
DataContext在共享資源時最有用。
<StackPanel x:Name="parent" DataContext="{StaticResource photos}"> <Label x:Name="numItemsLabel" Content="{Binding Path=Count}" DockPanel.Dock="Bottom"/>
也可以在代碼這麼寫parent.DataContext = photos;
Value Converters:
IValueConverter可以在綁定時加入自己的邏輯,很好。
public class RawCountToDescriptionConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // Let Parse throw an exception if the input is bad int num = int.Parse(value.ToString()); return num + (num == 1 ? " item" : " items"); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
IValueConverter就兩個方法需要自己實現,Convert和ConvertBack,一個轉過來,一個轉過去。
XAML代碼如下
<Label Background="{Binding Path=Count, Converter={StaticResource myConverter}, Source={StaticResource photos}}"/>
這裏的myConverter是個resource,需要在xaml中預先定義:
<Window.Resources> <local:CountToBackgroundConverter x:Key="myConverter"/> </Window.Resources>
Path對應的Count值會作爲第一個參數value傳給Convert方法。
注意,返回的值一定要是綁定時對應的值,比如綁定時需要綁到Geometry類上,那麼Convert返回的也必須是Geometry類。
Convert方法還帶有一個parameter參數,可以在xaml中這麼使用
<Label Background="{Binding Path=Count, Converter={StaticResource myConverter}, ConverterParameter=Yellow, Source={StaticResource photos}}"/>
ConverterParameter是object類型。
C#代碼中就可以得到parameter的值了。
TIP:
可以用Binding.DoNothing作返回值,以指示綁定引擎不要執行任何操作。
可用使用[ValueConversion(typeof(DateTime), typeof(String))]來標識Converter要轉化和返回的值類型,第一個參數是soure,第二個參數是target。這樣在編譯時,如果類型不匹配的話,編譯器會拋出異常:error CS0592: Attribute 'ValueConversion' is not valid on this declaration type. It is only valid on 'class' declarations.
.net自帶一些converter,比如常用的BooleanToVisibilityConverter,可以根據checkbox是否勾上來隱藏其他控件。
在collection中使用converter:使用DateTemplate,在其中使用Converter。(也可以使用Converter對整個collection進行轉化,但是可能效率不好)
Binding.Mode
指示源和目標間數據流的方向。
OneWay 源更新時,目標也更新
TwoWay 源更新時目標也更新,或者目標更新時同時更新源
OneTime 僅當應用程序啓動時或 DataContext 進行更改時更新目標屬性。綁一次就不更維護更新,目標相當於源的一次性鏡像
OneWayToSource 目標更新時更新源,和OneWay相反
大部分WPF自帶的控件的dependency property默認的是OneWay,像TextBox.Text默認的是TwoWay。
值得注意的事,只讀屬性只能設置成OneWay,不能是TwoWay,否則運行時異常。
對於 OneWay 或 TwoWay 綁定,對源的動態更改不會自動傳播到目標。必須在源對象上實現 INotifyPropertyChanged 接口。
對於 TwoWay 綁定,對目標的更改不會自動傳播到源,除非綁定目標是 Text 屬性。在這種情況下,更新僅在 TextBox 失去焦點時發生。
對於 OneTime 和 OneWay 綁定,對 SetValue 的調用會自動更改目標值並刪除綁定。
再次提醒,源要實現了INotifyPropertyChanged 接口才能把改變反映到目標上。
OneWayToSource 用於多個目標更改一個源的情況,可以想像成多人錄入。或者用來實現源和目標倒置的情況。
Binding.UpdateSourceTrigger
指示使用TwoWay或OneWayToSource時,目標在什麼情況下更新源。有三個枚舉值
PropertyChanged:目標屬性改變時更新源
LostFocus:失去焦點時更新源
Explicit:只有在顯式調用BindingExpression.UpdateSource方法時才更新源。BindingExpression可以通過BindingOperations.GetBindingExpression或FrameworkElement.GetBindingExpression方法獲得
Binding類中提供了SourceUpdated和TargetUpdated事件,可以用來記些log,不過必須相應的NotifyOnSourceUpdated或NotifyOnTargetUpdated設置成true纔會激發事件。
Binding的驗證
我們當然可以自己寫代碼驗證數據的正確性,不過WPF提供了可以說是標準的方案,限於篇幅,我單獨開了一貼,點這裏查看。