如果把Binding比作數據的橋梁,那么它的兩端分別是Binding的源和目標。數據從哪里來就是源,Binding是架在中間的橋梁,Binding目標是數據要往哪兒去。一般情況下,Binding源是邏輯層的對象,Binding目標是UI層的控件對象,這樣,數據就會源源不斷通過Binding送達UI層,被UI層展現,也就完成了數據驅動UI的過程。
數據源是一個對象,一個對象上可能有很多數據,這些數據又通過屬性暴露給外界。那么,其中哪個數據是你想通過Binding送達UI的元素呢?換句話說,UI上的元素關心的是哪個屬性值的變化,這個屬性就稱為Binding的路徑(Path)。但光有屬性還不行----Binding是一種自動機制,當值變化后屬性要有能力通知Binding,讓Binding把變化傳遞給UI元素。怎樣才能讓一個屬性具備這種通知Binding值已經變化的能力呢?方法就是在set語句中激發一個PropertyChanged事件。這個事件不需要我們自己聲明,我們要做的是讓作為數據源的類實現INotifyPropertyChanged接口。當Binding設置了數據源之后,Binding就會自動偵聽來自這個接口的PropertyChanged事件。
<StackPanel> <TextBox x:Name="txtName" BorderBrush="Black" Margin="5"></TextBox> <Button Content="AddAge" Margin="5" Click="Button_Click"></Button> </StackPanel>
class Student : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string name; public string Name { get { return name; } set { name = value; if (this.PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } } }
public partial class MainWindow : Window { Student stu; public MainWindow() { InitializeComponent(); stu = new Student(); //准備Binding Binding binding = new Binding(); binding.Source = stu; binding.Path = new PropertyPath("Name"); //使用Binding,連接數據源和Binding目標 BindingOperations.SetBinding(this.txtName, TextBox.TextProperty, binding); } private void Button_Click(object sender, RoutedEventArgs e) { stu.Name += "Name"; } }
這樣,當點擊button的時候,改變了stu對象的Name屬性,該數據會自動更新到UI,就是TextBox
原理大致如此,寫法卻有很多,比如BindingOperations.SetBinding,繼承自FrameworkElement的類都封裝了該方法
public BindingExpression SetBinding(DependencyProperty dp, string path);
所以還可以這樣寫
InitializeComponent(); stu = new Student(); //准備Binding Binding binding = new Binding(); binding.Source = stu; binding.Path = new PropertyPath("Name"); //使用Binding,連接數據源和Binding目標 //BindingOperations.SetBinding(this.txtName, TextBox.TextProperty, binding); txtName.SetBinding(TextBox.TextProperty, binding);
既然Binding作為源和目標間數據的橋梁,那么數據也是有方向的,控制Binding數據流向的屬性是Mode
public enum BindingMode { // 摘要: // 導致對源屬性或目標屬性的更改可自動更新對方。此綁定類型適用於可編輯窗體或其他完全交互式 UI 方案。 TwoWay = 0, // // 摘要: // 當綁定源(源)更改時,更新綁定目標(目標)屬性。如果要綁定的控件為隱式只讀控件,則適用此綁定類型。例如,可以綁定到如股市代號之類的源。或者,可能目標屬性沒有用於進行更改(例如表的數據綁定背景色)的控件接口。如果不需要監視目標屬性的更改,則使用 // System.Windows.Data.BindingMode.OneWay 綁定模式可避免 System.Windows.Data.BindingMode.TwoWay // 綁定模式的系統開銷。 OneWay = 1, // // 摘要: // 當應用程序啟動或數據上下文更改時,更新綁定目標。此綁定類型適用於以下情況:使用當前狀態的快照適合使用的或數據狀態實際為靜態的數據。如果要從源屬性初始化具有某個值的目標屬性,並且事先不知道數據上下文,則也可以使用此綁定類型。實質上,這是 // System.Windows.Data.BindingMode.OneWay 綁定的較簡單的形式,它在不更改源值的情況下可提供更好的性能。 OneTime = 2, // // 摘要: // 當目標屬性更改時更新源屬性。 OneWayToSource = 3, // // 摘要: // 使用綁定目標的默認 System.Windows.Data.Binding.Mode 值。每個依賴項屬性的默認值都不同。一般情況下,用戶可編輯控件屬性(例如文本框和復選框的屬性)默認為雙向綁定,而多數其他屬性默認為單向綁定。確定依賴項屬性綁定在默認情況下是單向還是雙向的編程方法是:使用 // System.Windows.DependencyProperty.GetMetadata(System.Type) 來獲取屬性的屬性元數據,然后檢查 // System.Windows.FrameworkPropertyMetadata.BindsTwoWayByDefault 屬性的布爾值。 Default = 4, }
Binding還有另外一個屬性來控制何時更新數據,它是UpdateSourceTrigger
public enum UpdateSourceTrigger { // 摘要: // 綁定目標屬性的默認 System.Windows.Data.UpdateSourceTrigger 值。多數依賴項屬性的默認值為 System.Windows.Data.UpdateSourceTrigger.PropertyChanged,而 // System.Windows.Controls.TextBox.Text 屬性的默認值為 System.Windows.Data.UpdateSourceTrigger.LostFocus。 Default = 0, // // 摘要: // 當綁定目標屬性更改時,立即更新綁定源。 PropertyChanged = 1, // // 摘要: // 當綁定目標元素失去焦點時,更新綁定源。 LostFocus = 2, // // 摘要: // 僅在調用 System.Windows.Data.BindingExpression.UpdateSource() 方法時更新綁定源。 Explicit = 3, }
順便提一句,Binding還有具有NotifyOnSourceUpdate和NotifyOnTargetUpdate兩個bool類型的屬性,如果設置為true,則當源或目標被更新后Binding會被激發相應的SourceUpdated事件和TargetUpdated事件。實際工作中,我們可以通過監聽兩個事件來找出有哪些數據或控件被更新了。
沒有Source的Binding——使用DataContext作為Binding的源
首先創建一個數據源類
public class Student { public string Id { get; set; } public string Name { get; set; } public string Age { get; set; } }
<Window x:Class="BindingSample2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:BindingSample2" Title="MainWindow" Height="350" Width="525"> <StackPanel> <StackPanel.DataContext> <local:Student Age="22" Name="HuangTao" Id="Hello"></local:Student> </StackPanel.DataContext> <Grid> <StackPanel> <TextBox Text="{Binding Path=Id}" Margin="5"></TextBox> <TextBox Text="{Binding Path=Name}" Margin="5"></TextBox> <TextBox Text="{Binding Path=Age}" Margin="5"></TextBox> </StackPanel> </Grid> </StackPanel> </Window>
這3個TextBox的Binding就會自動向UI元素樹的上層去尋找可用的DataContext對象。
沒有Source沒有Path的Binding,但源本身就是數據的時候
<Window x:Class="BindingSample2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:BindingSample2" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="MainWindow" Height="350" Width="525"> <StackPanel> <StackPanel.DataContext> <!--<local:Student Age="22" Name="HuangTao" Id="Hello"></local:Student>--> <sys:String>Hello!</sys:String> </StackPanel.DataContext> <Grid> <StackPanel> <!--<TextBox Text="{Binding Path=Id}" Margin="5"></TextBox> <TextBox Text="{Binding Path=Name}" Margin="5"></TextBox> <TextBox Text="{Binding Path=Age}" Margin="5"></TextBox>--> <TextBlock Text="{Binding}" Margin="5"></TextBlock> </StackPanel> </Grid> </StackPanel> </Window>