WPF Binding

WPF裏分三種Binding:BindingPriorityBinding, 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提供了可以說是標準的方案,限於篇幅,我單獨開了一貼,點這裏查看。

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章