前面讲了,数据目标="{Binding 数据源}",绑定的数据源,这里我们讲得数据目标。
一、属性(Property)的来龙去脉。
1、变量、函数
数据(变量)+算法(函数)=程序
2、字段、方法
类的作用:把散落的程序中的变量和函数进行归档封装,并控制它的访问。被封装在类里的变量称为字段(Field),被封装在类里的函数称为方法(Method)。
我们使用private、public等修饰符来控制字段或方法的可访问性。使用static决定字段或方法是对类有意义,还是对类的实例有意义。
如:Age对类的实例有意义,Amount(总量)对类有意义。
c#语言规定:对类有意义的字段或方法使用static关键字修饰,称为静态成员,通过类名加访问操作符(即“.”操作符)可以访问它们。
对类的实例有意义的字段和方法不加static关键字,称为非静态成员或实例成员。
静态成员、非静态成员在内存中的结构?
静态字段在内存中只有一个拷贝,非静态字段则每个实例拥有一个拷贝。无论方法是否为静态的,内存只有一个拷贝。
3、属性是如何演变出来的?
CLR属性是对字段进行封装,控制字段的可访问性,有效性。不会增加内存负担,仅仅是个语法糖衣(Syntax Sugar)。
class Human { private int age; public int Age { //可访问性(只读) get { return age; } private set { //有效性 if (value >= 0 && value <= 100) { age = value; } else { throw new OverflowException("Age不在范围"); } } } }@H_403_99@
二、依赖属性(Dependency Property)
依赖属性就是一种可以自己没有值,并能通过使用Binding 从数据源获得值(依赖在别人身上)的属性。拥有依赖属性的对象被称为“依赖对象”。与传统的CLR属性和面向对象思想相比依赖属性有很多新颖之处,
其中包括:
节省实例对内存的开销。属性值可以通过Binding依赖在其他对象上。
具有内置的更改通知的支持(当源对象中改变依赖项属性的值时,会立即更新目标对象中的绑定属性)。
1、依赖属性对内存的使用方式
传统.NET开发中,一个对象所占用的内存空间在调用new操作符进行实例化的时候就已经决定。
WPF允许对象在被创建的时候,并不包含字段所占用的空间,只保留需要用字段能够获得默认值、借用其它对象字段或实时分配空间的能力。这种对象称为依赖对象(Dependency Object),而它实时获取数据的能力则依靠依赖属性(Dependency Property)来实现。
DependencyObject 依赖对象是WPF系统中相当底层的一个基类。WPF所有UI控件都是依赖对象,节省内存开销。
DependencyObject 继承树如下图结构:
public class DependencyObject : System.Windows.Threading.DispatcherObject { //DependencyObject具有GetValue或SetValue方法,以DependencyProperty作为参数 //GetValue通过DependencyProperty对象获取数据 public object GetValue(DependencyProperty dp) { } //SetValue通过DependencyProperty对象存储数据 public void SetValue(DependencyProperty dp,object value) { } }@H_403_99@
实例一:依赖属性
定义依赖属性,必须以DependencyObject为宿主、借助它的SetValue和GetValue方法,写入或读取。
(1)、想定义依赖属性,类必须继承DependencyObject。
(2)、 变量由 public static readonly 三个修饰符修饰。
(3)、使用DependencyProperty.Register方法创建依赖属性的实例Name+Property,并对它进行注册。
//定义依赖属性,类必须继承 DependencyObject
public class Student : DependencyObject
{
//定义依赖属性必须是 public static readonly DependencyProperty Name+Property为后缀
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name",typeof(string),typeof(Student));
//CLR属性包装器名称(属性名称)、依赖属性类型(属性类型)、依赖属性关联到哪个类型(宿主类型)public static readonly DependencyProperty AgeProperty=
DependencyProperty.Register("Age",typeof(int),typeof(Student),new PropertyMetadata(20));
//new PropertyMetadata(20) 数据默认元素据20(属性默认值)
}
按钮调用
Student s = new Student();//创建Student实例 s.SetValue(Student.NameProperty,"111"); //SetValue方法将111,存储进依赖属性 TextBox1.SetValue(TextBox.TextProperty,s.GetValue(Student.NameProperty)); //GetValue 获取依赖属性的值@H_403_99@
实例二:CLR属性包装器:以“实例属性”的形式向外界暴露依赖属性。
为依赖属性 添加一个 CLR属性包装器,有个这个包装,就相当于为依赖对象准备了用于暴露数据的 Binding Path。也就是说,现在的依赖对象已经具备了扮演数据源和数据目标双重角色的能力。值得注意的是,尽管Student类没有实现INotifyPropertyChanged 接口,当属性的值发生变化时与之关联的Binding对象依然可以得到通知。
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty,value); }
}
Student s=new Student();
s.Name="111";
TextBox1.text=s.Name;
实例三:借用FrameWorkElement类的 SetBinding方法,使Student类有绑定方法。(推荐)
// 摘要: // 创建 System.Windows.Data.BindingExpressionBase 的新实例,并将其与指定的绑定目标属性关联。 // // 参数: // target: // 绑定的绑定目标。 // // dp: // 绑定的目标属性。 // // binding: // 描述绑定的 System.Windows.Data.BindingBase 对象。 // // 返回结果: // 为指定的属性创建并与之相关联的 System.Windows.Data.BindingExpressionBase 的实例。System.Windows.Data.BindingExpressionBase // 类是 System.Windows.Data.BindingExpression、System.Windows.Data.MultiBindingExpression // 和 System.Windows.Data.PriorityBindingExpression 的基类。 // // 异常: // System.ArgumentNullException: // target 参数不能为 null。 // // System.ArgumentNullException: // dp 参数不能为 null。 // // System.ArgumentNullException: // binding 参数不能为 null。 public static BindingExpressionBase SetBinding(DependencyObject target,DependencyProperty dp,BindingBase binding)@H_403_99@
BindingOperations.SetBinding(this,dp,binding)
//数据源 public class Student : DependencyObject { //小技巧:输入 propdp,按Tab键。可以快速建依赖属性 //CLR属性包装器 public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty,value); } } //依赖属性 public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name",typeof(Student)); //借用 SetBinding包装 public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding) { return BindingOperations.SetBinding(this,binding); } }@H_403_99@
Student stu; public Window1() { InitializeComponent(); stu = new Student(); //Student的Name属性 关联 textBox1的Text属性 stu.SetBinding(Student.NameProperty,new Binding("Text") { Source=textBox1}); //textBox2的Text属性 关联 Student的Name属性 textBox2.SetBinding(TextBox.TextProperty,new Binding("Name") { Source = stu }); }@H_403_99@
三、依赖属性存取的秘密
1、DependencyProperty.Register 源码分析
(1)、创建一个DependencyProperty 实例,并用CLR属性包装器名和宿主类型进行异或生成键(key),把DependencyProperty 实例存到Hashtable(PropertyFromName) 中
private static HashtablePropertyFromName=newHashtable();
DependencyProperty dp=newDependencyProperty(name,PropertyType,ownerType,defaultMetadata,validateValueCallback);
PropertyFromName[key]=dp;
(2)、最后返回DependencyProperty 实例
return dp;
2、DependencyObject的GetValue、SetValue
@H_404_335@GetValue源码分析:
//dp.GlobalIndex 是:CLR属性包装器名和宿主类型进行异或生成键(key)
EntryIndex entryIndex=LookupEntry(dp.GlobalIndex);
//每个依赖对象实例都自带一个EffectiveValueEntry 类型数组(存取值),当某个依赖属性的值要被读取时,算法会从这个数组中检索值
//如果数组中没有包含这个值,算法会返回依赖属性的默认值(这个值由依赖属性的DefaultMetadata来提供的)
//static关键字所修饰的依赖属性对象,用来检索数据。
//为了保障GlobalIndex稳定性,使用readonly关键字修饰。
EffectiveValueEntry valueEntry=GetValueEntry(entryIndex,null,RequestFlags.FullyResolved)
return valueEntry.Value;
依赖属性读取是优先级控制的,由先到后依次是: