一、普通属性
首先我们来探讨下.NET里面的属性,我们平时写一个类的时候我们需要定义一些字段来保存一些值。
public class Person { public string nam; public int age; }
但是我们会发现,这样子定义的话不能满足我们的需求,比如说我们需要实现一些对字段值得验证限制什么的,这时候我们就需要属性来为我们实现了。因为我们能在给属性设置的时候编写我们的验证逻辑。
public class Person { private string name; private int age; public int Age { get{return age;} set{ if(value>100&&value<0) age=100; else age=value; } } }
这里顺带说一下普通属性的原理。普通属性只是字段的一层糖衣。他本身是不可以保存值的。它在VS编译后会被编译成对应的方法(因为方法不管是静态还是非静态在内存中都只会有一份拷贝,所以即使这样当我们创建多个实例的时候,我们的内存不会有所增加)。
二、依赖属性
依赖属性是WPF中特有的一种属性,他是普通属性的一种升级版本。因为他的值依赖与其他对象,它自身可以没有值,这样子就可以大大的节省实例的内存开销。大家想想WPF中的每一个元素都有几十个属性,如果我们再创建每一个元素的时候都要初始化属性,并且给属性赋值,这样子的话就会大大的增加我们系统的内存。
在WPF中允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间),只保留在需要用到数据的时候能够获得默认值、借用其他对象数据或实时分配空间的能力——这种对象就称为依赖对象(Dependency Object)而他这种实时获取数据的能力则依靠依赖属性(Dependency Property)来实现。——《深入浅出WPF》
1、创建依赖属性
public class Student : DependencyObject { public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student)); }
a、依赖属性所属的类必须继承至DependencyObject类,
b、依赖属性的声明开头默认 public static readonly,
c、依赖属性的命名以Property结尾,
d、使用DependencyProperty.Register静态方法注册。
e、Register方法的三个参数(1、依赖属性包装器的名字,2、依赖属性保存值得类型,3、指明依赖属性的宿主类型)
其实这样子我们已经创建了一个简单的依赖属性,这样子我们就可以使用了。
Student stu = new Student(); //通过DependencyObject类的SetValue给依赖属性赋值 stu.SetValue(NameProperty, tb1.Text); //通过DependencyObject类的GetValue获得依赖属性赋值 tb2.Text = stu.GetValue(NameProperty).ToString();
我们是通过DependencyObject的SetValue给依赖属性赋值,GetValue获得依赖属性的值。大家在这里会发现依赖属性是可以保存值的,而我们上面讲的普通属性(他的值还是保存在字段中)。
2、添加属性包装器
在WPF中为了使我们使用起来方便我们会使用一个属性包装器将我们的依赖属性包装起来。
public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty, value); } }
这样子我们的依赖属性就通过一个属性包装器暴露给外界了,我们就可以像使用普通属性的时候使用依赖属性。
3、强制回调和验证回调
因为在WPF中我们可以直接通过SetValue给依赖属性赋值,这样子我们就不能像上面普通属性那样在给他赋值的时候进行相关的验证,但WPF提供了验证回调和强制回调这两个委托给我们使用。
验证回调(ValidateValueCallback):该回调函数可以接受或拒绝新值。他作为Dependency.Register()的一个参数提供回调函数,这个函数必须只指向一个接受一个Object参数并返回一个Boolean值得方法。返回true表示接受,false表示不接受。
强制回调(CoerceValueCallback):该回调函数可将新值修改为更能接受的值。这个回调一般用于处理为相同对象设置的依赖属性值相互冲突的问题(例如:Scrollbar的Maximum,Minimum和Value的值设置的时候。)
PropertyMetadata metadata= new PropertyMetadata(); //设置最大年龄时强制回调,因为最大年龄要大于最小年龄 private static object CoerceMaxAge(DependencyObject d, object value)//这里包含两个参数一个是该数值将要应用到的对象和准备使用的值 { Student stu = d as Student; if ((int)value < stu.MinAge) return stu.MinAge; else return value; } //设置年龄的时候强制回调,因为他的值必须在最大年龄和最小年龄之间 private static object CoerceAge(DependencyObject d, object value) { Student stu = d as Student; if ((int)value > stu.MaxAge) return stu.MaxAge; else if ((int)value < stu.MinAge) return stu.MinAge; else return value; } //验证回调,如果设置的值不满足要求 会抛出异常 private static bool IsRangeable(object value) { int i = Convert.ToInt32(value); if (i >= 0 && i < 150) return true; else return false; } public static readonly DependencyProperty AgeProperty = DependencyProperty.Register("Age", typeof(int), typeof(Student), new PropertyMetadata() { DefaultValue = 0, CoerceValueCallback = new CoerceValueCallback(CoerceAge) },new ValidateValueCallback(Student.IsRangeable)); public int Age { get { return (int)GetValue(AgeProperty); } set { SetValue(AgeProperty, value); } } public int MaxAge { get { return (int)GetValue(MaxAgeProperty); } set { SetValue(MaxAgeProperty, value); } } public static readonly DependencyProperty MaxAgeProperty = DependencyProperty.Register("MaxAge", typeof(int), typeof(Student), new PropertyMetadata(0) { CoerceValueCallback = new CoerceValueCallback(CoerceMaxAge), PropertyChangedCallback = new PropertyChangedCallback((d, e) => { Student stu = d as Student; stu.CoerceValue(Student.AgeProperty); }) }); public int MinAge { get { return (int)GetValue(MinAgeProperty); } set { SetValue(MinAgeProperty, value); } } public static readonly DependencyProperty MinAgeProperty = DependencyProperty.Register("MinAge", typeof(int), typeof(Student),new PropertyMetadata(new PropertyChangedCallback( (d,e)=>{ Student stu=d as Student; //当最小年龄的值改变的时候强制回调最大年龄的年龄的值 stu.CoerceValue(Student.MaxAgeProperty); stu.CoerceValue(Student.AgeProperty); })));
在这段代码中我模拟了一个ScrollBar设置三个值情景——设置年龄的最大值和最小值和年龄。
在设置年龄的时候我使用了一个验证回调,这样子当我们输入一个不合法的年龄的时候就会产生异常。在设置最大年龄的时候使用了强制回调,因为这样可以确保最大年龄大于等于最小年龄,在设置年龄的时候我也使用了强制回调,这样子就可以确保年龄在最大值和最小值之间。
书上说强制回调会执行在验证回调的前面,但是我调试的时候却发现结果正好相反不知道为什么。
当我们在给依赖属性赋值的时候事件执行顺序是CoerceValueCallback——ValidateValueCallback——PropertyChangedCallbacK。
三、附加属性
附加属性就是说一个属性本来不属于某个对象,但是由于某种需求而被后来附加上。例如我们在给元素布局的时候使用的Grid.Row和Canvas.Left等。他的注册和使用几乎和依赖属性差不多。