zoukankan      html  css  js  c++  java
  • 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;
  • 相关阅读:
    iOS Sprite Kit最新特性Physics Field虚拟物理场Swift測试
    java中接口的定义与实现
    2014年百度之星程序设计大赛
    MyEclipse7.0破解下载
    C++中的explicitkeyword
    抽象工厂模式
    《Head First 设计模式》学习笔记——策略模型
    MFC原创:三层架构01(人事管理系统)DAL
    Design Pattern Singleton 单一模式
    C学习笔记之预处理指令
  • 原文地址:https://www.cnblogs.com/hailiang2013/p/3035350.html
Copyright © 2011-2022 走看看