- <
Grid> -
< TextBox Height="23" Margin="10,10,9,0" Name="textBox1" VerticalAlignment="Top" Text="{Binding ElementName=slider1, Path=Value}"/> -
< TextBox Height="23" Margin="10,41,9,0" Name="textBox2" VerticalAlignment="Top" /> -
< Slider Height="21" Margin="10,73,9,0" Name="slider1" VerticalAlignment="Top" Maximum="100" /> - </
Grid>
细心的你,一定一眼就看到只多了这样一句话:Text="{Binding ElementName=slider1, Path=Value}"
这句话的意思是说
非常简单是不是?请注意,这里面可蕴含了“数据驱动界面”的模型哦!在这里,我们始终把slider1的Value当成是数据源(Data Source),而textBox1则是用来显示和修改数据的窗口(Data Presenter)——slider1是核心,它的Value属性值将驱动textBox1的Text进行改变;人为改变textBox1的Text属性值,也会被送回到slider1的Value属性值上去。
是时候让我们了解Data Binding的几个关键概念了——
- 数据源(Data Source,简称Source):顾名思义,它是保有数据的实体、是数据的来源、源头。把谁当作数据源完全由程序员来决定——只要你想把它当做数据核心来使用。它可以是一个UI元素、某个类的实例,也可以是一个集合(关于对集合的绑定,非常重要,专门用一篇文章来讨论之)。
- 路径(Path):数据源作为一个实体可能保有着很多数据,你具体关注它的哪个数值呢?这个数值就是Path。就上面的例子而言,slider1是Source,它拥有很多数据——除了Value之外,还有Width、Height等,但都不是我们所关心的——所以,我们把Path设为Value。
- 目标(Target):数据将传送到哪里去?这就是数据的目标了。上面这个例子中,textBox1是数据的Target。有一点需要格外注意:Target一定是数据的接收者、被驱动者,但它不一定是数据的显示者——也许它只是数据联动中的一环——后面我们给出了例子。
- 关联(Binding):数据源与目标之间的通道。正是这个通道,使Source与Target之间关联了起来、使数据能够(直接或间接地)驱动界面!
- 设定关联(Set Binding):为Target指定Binding,并将Binding指向Target的一个属性,完成数据的“端对端”传输。
在C#代码中设置Binding
XAML代码是如此简单,简单就那么一句话。这可不是吾等C#程序员、刨根问底之徒可以善罢甘休的!
形象地讲,Binding就像一个盒子,盒子里装了一些机关用于过滤和控制数据,盒子两端各接着一根管子,管子是由管壳和管芯构成的,看上去就像下面的图:
当脑子里有了这样一个形象之后,遵循下面的步骤就OK了:
- Source:确定哪个对象作为数据源
- Target:确定哪个对象作为目标
- Binding:声明一个Binding实例
- 把一根管子接到Source上并把管芯插在Source的Path上
- 把另一根管子接到Target上并把管芯插在Target的联动属性上
如果有必要,可以在3与4之间设置Binding的“关卡”们。其实,第3步之后的顺序不是固定的,只是这个步骤比较好记——一概向右连接。所得结果看上去是这样:
我猜你可能会问:“那个D.P.是什么呀?”
D.P.的全称是“Dependency Property”,直译过来就是“依赖式属性”,意思是说它自己本身是没有值的,它的值是“依赖”在其它对象的属性值上、通过Binding的传递和转换而得来的。表现在例子里,它就是Target上的被数据所驱动的联动属性了!
这里是等价的C#代码
- public
Window1() - {
-
InitializeComponent(); -
-
// 1. 我打算用slider1作为Source -
// 2. 我打算用textBox1作为Target -
Binding binding = new Binding(); -
binding.Source = this.slider1; -
binding.Path = new PropertyPath("Value"); -
this.textBox1.SetBinding(TextBox.TextProperty, binding); - }
- public
Window1() - {
-
InitializeComponent(); -
-
// 1. 我打算用slider1作为Source -
// 2. 我打算用textBox1作为Target -
Binding binding = new Binding("Value"); -
binding.Source = this.slider1; -
this.textBox1.SetBinding(TextBox.TextProperty, binding); - }
- public
Window1() - {
-
InitializeComponent(); -
-
-
// 1. 我打算用slider1作为Source -
// 2. 我打算用textBox1作为Target -
Binding binding = new Binding("Value"); -
binding.Source = this.slider1; -
binding.Mode = BindingMode.TwoWay; -
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; -
this.textBox1.SetBinding(TextBox.TextProperty, binding); - }
自定义数据源:
在我们项目组日常的工作中,经常需要自己写一个类,并且拿它的实例当作数据源。怎样才能让一个类成为“合格的”数据源呢?
要诀就是:
-
为这个类定义一些Property,相当于为Binding提供Path - 让这个类实现INotifyPropertyChanged接口。实现这个接口的目的是当Source的属性值改变后通知Binding(不然人家怎么知道源头的数据变了并进行联动协同呢?),好让Binding把数据传输给Target——本质上还是使用事件机制来做,只是掩盖在底层、不用程序员去写event handler了。
- class="csharp"
name="code">// 第一步:声明一个类,准备必要的属性 -
- public
class Student - {
-
private int id; -
public int Id -
{ -
get { return id; } -
set { id = value; } -
} -
-
private string name; -
public string Name -
{ -
get { return name; } -
set { name = value; } -
} -
-
private int age; -
public int Age -
{ -
get { return age; } -
set { age = value; } -
} -
- }
- //
第二步:实现INotifyPropertyChanged接口 -
- public
class Student : INotifyPropertyChanged - {
-
public event PropertyChangedEventHand ler PropertyChanged; // 这个接口仅包含一个事件而已 -
-
private int id; -
public int Id -
{ -
get { return id; } -
set -
{ -
id = value; -
if (this.PropertyChanged != null) -
{ -
-
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs ("Id")); // 通知Binding是“Id”这个属性的值改变了 -
} -
} -
} -
-
-
private string name; -
public string Name -
{ -
get { return name; } -
set -
{ -
name = value; -
if (this.PropertyChanged != null) -
{ -
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs ("Name")); // 通知Binding是“Name”这个属性的值改变了 -
} -
} -
} -
-
private int age; -
public int Age -
{ -
get { return age; } -
set { age = value; } // Age的值改变时不进行通知 -
}