WPF之属性

 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*10005.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;
原文地址:https://www.cnblogs.com/hailiang2013/p/3035350.html