如果把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>