我们知道WPF最重要的一个特性是数据驱动UI,Binding就是实现这个特性的桥梁,这个类把数据和界面控件关联起来。而且它还支持双向通信。当数据改变时,界面显示会自动改变;当界面内容改变时,后台的数据也会自动改变。当然这个双向通信是可以设置的。
binding源和目标。刚才说binding是一座桥梁,那源和目标就是桥梁的两端。一般来说,源就是数据,目标就是UI,但也可以反过来,也可以两端都是UI控件。下面看一个最简单的例子:
public class Student:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int ID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
if(PropertyChanged!=null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
public int Age { get; set; }
}
我们定义了一个学生类,继承接口INotifyPropertyChanged,这个接口只定义了一个属性值改变的事件。
下面是界面代码:
下面我们在界面的构造函数中设置binding:
Student student = new Student();
public Window4()
{
InitializeComponent();
Binding binding = new Binding("Name") { Source = student };
txtName.SetBinding(TextBox.TextProperty, binding);
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
student.Name += "Name";
}
Binding binding = new Binding("Name") { Source = student };这条语句的含义就是:把binding的源设置为student对象实例,Name为binding的访问路径,意思就是说这个binding关注的是student对象的Name属性。
txtName.SetBinding(TextBox.TextProperty, binding);这条语句把binding对象和界面控件关联起来,意思是把txtName文本框的TextProperty和binding对象关联起来,因为binding对象已经关联了student对象了Name属性,那么这两条语句的意思就是:txtName文本框关联了student对象的Name属性值。
那文本框怎么知道Name的属性值变了呢,答案就是Name属性的set代码块中。PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));这条语句触发了Name属性的值改变事件,因为binding对象关联了Name属性值,所以当binding对象接受到Name属性值改变后,就把Name属性值赋值给了txtName文本框。
当我们点击按钮后,文本框就会显示当前Name属性的值。
path即对象的属性,关于path有很多种用法,下面我们逐一讲解:
1、索引器。看下面的例子:
我们把txtLength文本框的文本通过binding设置为txtName文本框文本的第二个字符,运行效果如下:
等效的c#代码:
Binding binding1 = new Binding("Text.[1]") { Source = txtName,Mode= BindingMode.OneWay };
txtLength.SetBinding(TextBox.TextProperty, binding1);
2、使用集合或者DataView作为Binding源时,如果我们想把它的默认元素当作path使用,则语法如下:
List stringList = new List() { "Tim", "Tom", "Blog" };
txt1.SetBinding(TextBox.TextProperty, new Binding("/") { Source = stringList });
txt2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList, Mode = BindingMode.OneWay });
txt3.SetBinding(TextBox.TextProperty, new Binding("/[1]") { Source = stringList, Mode = BindingMode.OneWay });
/斜杠即代表默认元素,集合的默认元素即第一个元素。
3、如果集合是嵌套的,我们想把子级集合中的元素作为path,则语法如下:
City c1 = new City() { Name = "黄冈" };
City c2 = new City() { Name = "武汉" };
Province p1 = new Province()
{
Name = "湖北",
CityList = new List() { c1, c2 }
};
City c3 = new City() { Name = "广州" };
City c4 = new City() { Name = "深圳" };
Province p2 = new Province()
{
Name = "广东",
CityList = new List() { c3, c4 }
};
Country country1 = new Country()
{
Name = "中国",
ProvinceList = new List() { p1, p2 }
};
List countries = new List();
countries.Add(country1);
txt1.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source= countries });
txt2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList.[1].Name") { Source = countries });
txt3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = countries });
txt4.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countries });
运行效果如下:
4、没有指定path的binding。如果path的值为"."或者直接没有指定path,则表明binding的源本身就是数据,比如:string,int类型的数据。看下面的例子:
菩提本无树,明镜亦非台。本来无一物,何处惹尘埃。
我们看到path=.,或者直接把path=.去掉都可以。
这条语句等效的c#代码:
string s = "菩提本无树,明镜亦非台。本来无一物,何处惹尘埃。";
lable1.SetBinding(TextBlock.TextProperty, new Binding(".") { Source = s });
这里的"."也可以省略
DataContext属性被定义在FrameworkElement类里,这个类是WPF控件的基类,这意味着所有的WPF控件都具备这个属性。我们知道WPF的UI布局是树形结构的,这棵树的每个节点都是控件,由此我们推出一个结论:在UI元素树每个节点都有DataContext。这一点很重要,因为当一个Binding只知道自己的path而不知道自己的source时,它就会沿着UI元素树一路向树的根部找过去,每路过一个节点都要看看这个节点的DataContext是否具备path指定的属性。如果有,那就把这个对象作为自己的source;如果没有,那就继续找下去;如果到了树的根部还没有找到,那这个binding就没有source,因此也不会得到数据。
我们看一个例子:
先定义一个Person类:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
在xaml里面定义DataContext和Binding:
我们把DataContext定义在Window标签内,给TextBox设置没有Source,只有Path的Binding。运行效果如下:
学习完前面的知识,理解这个其实很容易,我们看一个例子就明白了:
Hello DataContext!