1、属性的来龙去脉 程序的本质就是:数据+算法=>以算法来处理数据以期待得到的输出的结果 然后到了面向对象的时代,类这一数据结构出现了,它把散落在程序中的变量跟函数归档封装,被封装的变量成为Field,函数成为Method 我们还可以控制它的可访问性,如private,public, 是否使用static关键字决定了字段或方法对类有意义还是对类的实例有意义 对类的实例有意义:比如Human类,Weight字段则对但个个体(实例)有意义 对类有意义:Amount总量,对但整个人类有意义 静态字段在内存中只有一个拷贝,非静态字段每个实例都有一个拷贝 而无论方法是否是静态的,在内存中只有一个拷贝 而直接给字段赋值容易把错误的值写入字段,所以每次都需要判断一下,这样会增加代码冗余和违反高内聚的原则 该功能只能交给对象自己来处理,所以属性出现了 属性一般是这样的 Class Human{ private Int32 age; public Int32 Age{ get{ return age;} set { if(value>=0||value<=100) { this.age=value; } else{ throw new OverFlowException("Age overflow"); } } } } 如果实例化多个Human Human h1=new Human(); Human h2=new Human(); Human h3=new Human(); 这样age字段在内存中会有3个拷贝,会消耗一定内存,而对于age的包装器(CLR属性),通过IL看出会生成对应的两个方法get_Age:Int32(),set_Age:void(Int32), 再多的实例方法也只有一个拷贝,所以CLR属性不会增加内存的负担 2、依赖属性(Dependency Property) 依赖属性主要解决的是内存消耗的问题 想想啊一个TextBox对象包含着138个属性,每个属性都包装着一个4个字节的字段,如果一个页面上有10*1000个TextBox,那么则会消耗内存138*4*10*1000≈5.26MB内存 而我们最常用的是其Text属性,意味着大多数内存被浪费掉了 依赖属性可以解决这个问题,依赖属性就是一个可以没有值,对象创建时可以不包含用于存储数据的空间(即字段占用的空间), 通过Binding从数据源获取值(依赖在别人身上的属性),拥有依赖属性的对象成为依赖对象 依赖属性的特点: 节省实例对内存的开销 属性值可以通过Binding依赖在其他对象上 依赖对象(DependencyObject)通过依赖属性(DependencyProperty)来获取数据 依赖对象被DependencyObject所实现 public class DependencyObject:DispatcherObject{ public object GetValue(DependencyProperty dp){ //.... } public void SetValue(DependencyProperty dp,object value){ //...} } WPF所有UI控件都是依赖对象,UI控件的大多数属性都已经依赖化了。 举例: 建立一个依赖对象 public class Student:DependencyObject{ //建立一个依赖属性 /** *1.DependencyProperty一定存在于DependencyObject中,所以Student继承DependencyObject *2.public static readonly 来修饰,NamePropert成员变量名字后加Property表明他是一个依赖属性 *3.非new而是通过DependencyProperty.Register来注册 * 参数1:将来使用哪个CLR属性作为这个依赖属性的包装器 * 参数2:依赖属性用来存储什么类型的值 * 参数3:该依赖属性的宿主是什么,即他依赖对象的类型 */ public static readonly DependencyProperty NameProperty= DependencyProperty.Register("Name",typeof(string),typeof(Student)); } 注意: 1.依赖属性就是那个有public static readonly修饰的DependencyProperty的实例,而依赖属性的包装器(Wrapper)是个CLR属性,不要把包装器误认为是依赖属性, 没有包装器这个依赖属性一样存在 2.包装器的作用是以“实例属性”的形式向外界暴露依赖属性,这样,一个依赖属性才能成为数据源的一个Path 3.上面第二个参数,工作中习惯性称其为“依赖属性的类型”,其实应该称为“依赖属性的注册类型”,因为依赖属性的类型是DependencyProperty。 string str1=“从我这可以获取到值”; Student s=new Student(); s.SetValue(Student.NamedProperty,str); string str2=s.GetValue(Student.NamedProperty).ToString(); Console.WriteLine(str2);//从我这可以获取到值 依赖属性即使没有CLR属性作为其外包装器,也能很好的工作。 因为在GetValue时我们要进行一次数据转换object->string,为了体现高内聚原则所以我们在给依赖属性加上一个外包装器 public string Name { get{ return (string)GetValue(NameProperty);} set{ SetValue(NameProperty,value);} } 有了这个包装,就相当于为依赖对象准备了用于暴漏数据的Binding Path 这样Student对象既可以扮演数据源和数据目标的双重角色,虽然没有实现INotifyPropertyChanged接口,当属性值发生改变时与之Binding的对象依然能得到通知 哇塞,天生的就是合格的数据源。 对于WPF的UI控件都有一个SetBinding的方法 Binding binding=new Binding("Text"){Source=txtBox1}; txtBox2.SetBinding(TextBox.TextProperty,binding); 对于Setbinding方法Student类中没有,DependencyObject中也没有,其实这个方法在FrameworkElement类中, public class FrameworkElement{ public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding) { //尼玛仅仅对BindingOperations.SetBinding做了个简单的封装。。。。。 return BindingOperations.SetBinding(this,dp,binding); } } OK 我们也对Student 简单的封装一个SetBinding方法 public class Student :DependencyObject{ //.. public BindingExpressionBase SetBinding(DependencyProperty dp ,BindingBase biding) { return BindingOperations.SetBinding(this,dp,binding); } } 小技巧:propdp按tab键就可以修改依赖属性的各个参数 其实在注册DependencyProperty实例的时候,DependencyProperty.Register()方法还有第四个参数的重载,类型为PropertyMetadata 作用是给依赖属性的DefaultMetadata属性赋值,意思是说DefaultMetadata是向依赖属性的调用者提供一下信息: 1.CoerceValueCallback:依赖属性值被强制改变时此委托会被调用,此委托可关联一个可影响的函数。 2.DefaultValue:依赖属性未被显示赋值,若读取之则获取次默认值,不设置此值会抛异常。 3.IsSealed:控制PropertyMetadata的属性值是否可以改变,默认值为true。 4.PropertyChangedCallback:依赖属性值改变后悔调用此委托,此委托可关联一个可影响的函数。 需要注意的是:依赖属性的DefaultMetadata只能通过Register方法的第四个参数进行赋值,一旦赋值不能改变(DefaultMetadata是个只读属性), 如果想用新的DefaultMetadata来替换掉默认的metadata,则需要用到DependencyProperty.OverrideMetadata()方法。 深入探讨一下依赖属性的值是如何存取的。。。 我们知道DependencyObject中有个SetValue方法 DependencyProperty中?No。因为它是一个static对象,如果成百上千的实例难道都存在一个对象里?No 在DependencyProperty这个类中会有这样一个成员 private static Hashtable PropertyFromName=new Hashtable(); 这个就是用来注册DependencyProperty实例的地方。。 所有DependencyProperty.Register()方法的重载都归根于对DependencyProperty.RegisterCommon方法的调用 private static DependencyProperty RegisterCommon( string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback ){ //1.参数为依赖属性名称和依赖属性所属的依赖对象 //两个参数进行异或运算 FromNameKey key=new FromNameKey(name,ownerType); //... //2.检查该依赖属性是否被注册过 if(PropertyFromValue.Contains(key)) { //如果你尝试同一个属性名称和宿主类型进行注册则会抛出异常 throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered,name,ownerType.name)); } //3.检查PropertyMetadat是否提供,如果没有则准备一个默认的 //... //4.都准备妥当DenpendencyProperty被创建出来了 DependencyProperty dp=new DependencyProperty(name,propertyType,ownerType,defaultMetadata,validateValueCallback); //5.注册进Hashtable中去,key会自动调用其重写的GethashCode() PropertyFromName[key]=dp; } 前四个参数与Register中的都一样,那到底在FromNameKey中做些什么呢,下面是FromNameKey对象的构造器 public FromNameKey(string name,Type ownerType){ _name=name; _ownerType=ownerType; //依赖属性名称与依赖对象的异或运算 _hashCode=_name.GetHashCode()^_ownerType.GetHashCode(); } 并且Override中有其GetHashCode方法的重写 public override GetHashCode(){ return _hashCode; } 所以key变量的hashcode是RegisterCommon方法的第一个参数CLR属性名字字符串与第二个参数依赖对象的类型的异或运算 这样每对"CLR属性名称-宿主类型"所决定的DependencyProperty实例就是唯一的。 最后DependencyProperty以“Key-Value”的形式存入全局名为PropertyFromName的hashtable中 WPF属性系统通过CLR属性名称-宿主类型就可以在这个hashtable中取出对应的DependencyProperty实例。 注意:注册后生成的DependencyProperty实例的hashCode与存于全局表中的通过异或运算得出的hashCode是不一样的,即key值不等于value的hashCode 每个DependencyProperty实例都有一个名为GlobalIndex的int类型属性,该属性是通过一些算法实现的,确保每一个DependencyProperty实例都是唯一的 其实唯一一点很重要的是GlobalIndex属性值就是DependencyProperty的哈希值。 在谈谈附加属性 该属性的本质也是一种依赖属性,说是一种属性不属于某个对象,但由于某种需求而被后来附加上的,表现为被环境附加上的属性。 一般我们在什么时候用到附加属性呢,比如有一个Human类,它可能被学校相关的工作流用到(记录它的专业、年纪、班级),也可能被某一个公司的工作流用到(记录它的部门、项目) 宿主:School,1个Grade附加属性 class School :DependencyObject{ public static readonly DependencyProperty GradeProperty= DependencyProperty.RegisterAttached("Grade",typeof(int),typeof(School),new UIPropertyMetadata(0)); public static int GetGrade(DependencyProperty obj){ return (int)obj.GetValue(GradeProperty); } public static SetGrade(DependencyProperty obj,object value){ obj.SetValue(GradeProperty,value); } } 如何消费这个Grade附加属性 class Human:DependencyObject{ public Human{() { Human h=new Human(); School.SetGrade(h,1); int grade=School.GetGrade(h); Console.WriteLine(grade); } } 在WPF的现实工作中常常会遇到下面代码: <Grid> <Grid.ColumnDefintions> <ColumnDefinitions/> <ColumnDefinitions/> <ColumnDefinitions/> </Grid.ColumnDefintions> <Grid.RowDefinitions> <RowDefinitions/> <RowDefinitions/> <RowDefinitions/> </Grid.RowDefinitions> <Button x:Name="btn_OK" Grid.Row="1" Grid.Column="1" /> </Grid> 这里Grid.Row Grid.Column都是附加属性 等价的C#代码 Grid grid=new Grid(); //添加行 //添加列 Button button=new Button(){ Content="OK"}; Grid.SetColumn(button,1); Grid.SetRow(button,1);
Grid.Children.Add(button);
this.Content=grid;