来自《深入浅出WPF》(刘铁猛)读书笔记
程序的本质是数据加算法。数据会在存储,逻辑和展示三个层流通,所以站在数据的角度上来说,这三个层都很重要。但算法在程序中的分布就不均匀了,对于一个三层结构的程序来说,算法分布在这几处:
A)数据库内部;
B)读取和写回数据;
C)业务逻辑;(程序的核心)
D)数据展示;
E)界面与逻辑的交互;
WPF作为一种专门的展示层技术,华丽的外观和动画只是它的表层现象,更重要的是它在深层次上帮助程序员把思维的重心固定在逻辑层,让展示层永远处于逻辑层的从属地位。WPF具有这种能力的关键是它引入了Data Binding概念以及与之配套的Dependency Property系统和DataTemplate。
从传统的Windows Form迁移到WPF之后,对于一个三层程序而言,数据存储层由数据库和文件系统来构建,数据传输和处理仍然使用.Net Framework的ADO.Net等基本类(与Windows Form等开发一样),展示层则使用WPF类库来实现,而展示层与逻辑层的沟通就使用Data Binding来实现。
如果把Binding比作数据的桥梁,那么它的两端分别是Binding的源(Source)和目标(Target)。
UI上的元素关心的是哪个属性值的变化,这个属性就称谓Binding的路径(Path)。Binding是一种自动机制,当值变化后属性要有能力通知Binding,让Binding把变化传递给UI元素。
怎样才能让一个属性具备这种通知Binding值已经变化的能力?方法是在属性的set语句中激发一个PropertyChanged事件。这个事件不需要我们自己声明,我们要做的是让作为数据源的类实现System.CompoenentModel名称空间中的INotifyPropertyChanged接口。当为Binding设置了数据源后,Binding就会自动侦听来自这个接口的PropertyChanged事件。
class Student
{
private string name;
public string Name
{
get {return name; }
set {name=value; }
}
}
实现INotifyPropertyChanged接口的Student类:
class Student
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get {return name; }
set {
name=value;
//invoke event
if(this.PropertyChanged != null)
{
this.PropertyChanged.INvoke(this,new PropertyChangedEventArgs("Name"));
}
}
}
}
接下来,我们将进入最重要的一步--使用Binding把数据源和UI元素连接起来。
public partial class Window1: Window
{
Student stu;
public Window1()
{
InitializeComponent();
//prepare data source
stu=new Student();
//prepare binding
Binding binding=new Binding();
binding.source=stu;
binding.Path=new PropertyPath("Name");
//connect data source & target
Binding.Operations.SetBinding(this.textBoxName,TextBox.TextProperty,binding);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
st.Name += "Name";
}
}
SetBinding(...)有3个参数:
1)用于指定Binding的目标,本例中是this.textBoxName;
2)用于为Binding指明把数据送达目标的哪个属性;
3)使用哪个Binding实例将数据源与目标关联起来。
Binding对源的要求并不苛刻--只要它是一个对象,并且通过属性Property公开自己的数据,它就能作为Binding的源。
A)把控件作为Binding源与Binding标记扩展
在C#代码中可以访问XAML代码中申明的变量但在XAML代码中却无法访问C#代码中申明的变量。
在C#代码中我们可以直接访问控件对象,所以一般不会使用Binding的ElementName属性,而是直接把对象赋值给Binding的Source属性。
this.textBox1.SetBinding(TextBox.TextProperty,new Binding("Value"){ElementName="slider1"});
可以简写为:
<TextBox x:Name="textBox1" Text="{Binding Value, ElementName=slider1}" BorderBrush="Black" Margin="5"/>
B)控制Binding的方向及数据更新
默认情况下数据既能通过Binding送达目标,也能从目标返回源。
控制Binding数据流向的属性是Mode,它的类型是BindingMode枚举,取值TwoWay,OneWay,OneTime,OneWayToSource,Default.这里Default是指Binding模式会根据目标的实际情况来确定,比如可编辑(TextBox.Text),Default就采用双向模式,若是只读(TextBlock.Text),则采用单项模式。
Binding的另外一个属性--UpdateSourceTrigger,它的类型是UpdateSourceTrigger枚举,可取值为PropertyChanged,LostFocus,Explicit,Default.
C)Binding的路径(Path)
实例textBox与slider关联:
<TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider1}">
等效的C#代码:
Binding binding=new Binding() {Path=new PropertyPath("Value"), Source=this.slider1};
this.textBox1.SetBinding(TextBox.TextProperty,binding);
或者使用Binding的构造器简写为:
Binding binding=new Binding("Value") {Source=this.slider1};
this.textBox1.SetBinding(TextBox.TextProperty,binding);
可以使用索引器作为Path:让一个TextBox显示另一个textBox文本的第四个字符:
<StackPanel>
<TextBox x:Name="textBox1" BorderBrush="Black" Margin="5" />
(TextBox x:Name="textBox2" Text="{Binding Path=Text.[3],ElementName=textBox1,Mode=OneWay}" BorderBrush="Black" Margin="5" />
</StackPanel>
当使用一个集合或者DataView作为Binding源,如想把他的默认元素当做path使用,则:new Binding("/");
如果集合元素的属性仍然是一个集合,想把子集合中的元素当做path,则可以使用多级斜线的语法:
new Binding("/Name")
new Binding("ProvinceList.Name")
new Binding("Province/CityList.Name")