Johnny with dotnet

WPF 学习笔记4 依赖属性

1,依赖属性就是一种可以自己没有值,并能通过使用Binding从数据源获得值(依赖在别人身上)的属性。

2, 依赖属性对内存的使用比较特别,每个CLR属性都内置一个非静态字段。而依赖属性允许对象在创建的时候并不包含用于存放数据的空间,只保留在需要用到数据是能够得到默认值,借用其他对象数据或者实时分配空间的能力。 这种对象成为依赖对象。

3,WPF系统中,依赖对象的概念被DependecyObject类实线。它有GetValue和SetValue两个方法。着两个方法都包含一个DependecyProperty的参数。

4,所有的WPF UI控件都是依赖对象,UI控件的绝大多数属性都已经依赖化了。

5, 生明和使用依赖属性

依赖属性的宿主是依赖对象。 生命一个依赖属性很特别,首先时候public static readonly来修饰,然后使用DependencyProperty.Register方法生成。 代码如下:

public Class Student : DependencyObject
{
      public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name",typeof(string),typeof(Student));
}
如何使用呢?
Student stu = new Student();
stu.SetValue(Student.NameProperty,“zhaoyun”);
string v = stu.GetValue(Student.NameProperty);
6, 既然依赖属性只是一个static对象,哪怕有1000个Student实例,那么属性对象也只有一个,那么调用SetValue方法时值被存储到哪里去了呢?用Get属性读取的手又是从哪里读出呢?而且readonly关键字修饰的变量不是只读的吗,怎么可以用来写入值呢?
* 先看下代码,我们假设把TextBox1作为数据源,将Student实例作为target,看看代码

     Student stu= new Student();

    Binding binding = new Binding("Text"){Source=textBox1};

    BindingOperations.SetBinding(stu,Student.NameProperty,binding);

 

    //使用  

    MessageBox.Show(stu.GetValue(Student.NamePerperty).ToString())

    上面这个例子说明,其实没有CLR属性作为其外包装也可以使用。

*  我们上面用到的绑定方法是BindingOperations.SetBinding, 但是我们平时使用时都是this.textBox1.SetBinding这样使用。而  Stu.SetBinding方法不存在,为啥? 因为Setbinding方法是FrameworkElement类的方法,而不是DepencyObject类的方法。下面我们做一下封装:

  Public BindingExperssionBase SetBinding(DependencyProperty dp, BindingBase binding)

  {

        return BindingOperations.SetBinding(this,dp,binding);

   }

* 如何让dependencyProperty像CLR属性那样使用呢? 添加CLR属性外包装。

public String Name
{
    Get {Return (string)GetValue(NameProperty);}
   Set {SetValue(NameProperty,value)}
}
* 尽管Student类没有实现INotifyPropertyChanged接口,当属性的值发生变化时与之关联的Binding对象依然可以得到通知,依赖属性默认带有这样的功能。
* (注:)输入propdp然后按两次Tab键,可以生成一个标准的依赖属性。使用这中方法生成的DependencyProperty.Register中使用的是带4个参数的重载,第四个参数PropertyMetadata类的作用是给依赖属性的DefaultMetadata属性赋值。顾名思义,DefaultMetadata的作用是向依赖属性的调用者提供一些基本信息。包括:
  • CoerceValueCallback 当值被强制改变时,调用。
  • DefaultValue 依赖属性没有被显示赋值时,若读取之责获得此默认值。
  • IsSealed,控制Propertymetadata的属性值是否可以更改。
  • PropertyChangedCallback 依赖属性的值被改变之后此委托会被调用,此委托可关联一个影响函数。
(注:)属性的DefaultMetadata只能通过Register方法的第4个参数进行赋值,一旦赋值就不能更改。如果想用新的PropertyMetadata替换这个默认的Metadata,需要使用DependencyProperty.OverrideMetadata方法。
7, 依赖属性值存取的秘密
* DependencyProperty具有这样一个成员: 这个HashTable是用来注册DependencyProperty是来得地方。
   private static hashTable propertyFromName = new HashTable();
* DependencyProperty.Register方法重载最后都归结为DependecnyProperty.RegesterCommon方法的调用。
  Private Statice DependencyProperty RegisterCommon
  {
        String name,
        Type PropertyType
        Type ownerType
        PropertyMetadata DefaultMetadata,
        Validate ValueCallback valuecallback
   }
  这个方法和Regester方法是一致的
   看这个方法的第行语句:
    FromNameKey key = new FromNameKey(name, ownerType);
      这里FromNamekey是一个.net framework内部数据类型,它的构造器代码如下:
      public fromNameKey(String name,Type ownerType)
  {
        _name = name;
        _owerType = ownerType;
        _hashCode= _name.GetHashCode()^_ownerType.GetHashCode();
        //并且override有其GethashCode方法
        public override int GetHashCode()
        {
              return _hashCode;
         } 
   }
 (Note:)这里的hashCode用的是Proeprty的名称和宿主类型,这就保证了,每个DependencyProperty实例是唯一的。
    if(PropertyFromName.Contains(key))
    {
        throw new ArgumentException(SR.Get(SSID.PropertyAlreadyRegistered,name,ownerType.Name));
    }
  //接下来RegisterCommon检查是否提供了PropertyMetadate,如果没有则提供一个默认的实例。当所有的准备都充
//分以后,DependencyProperty实例被创建出来。
    DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType,defaultMetadate,ValidateValueCallback);  
//注册进HashTable中。
  PrppertyFromName[key]= dp;
  return dp;
(note2) : 上面那个HashCode并不是DependencyProperty实例的哈希值,每个DepenencyProperty实例都具有一个名为GlobleIndex的int类型属性。这个值是唯一的。
   Public Override int GethashCode()  {return GlobalIndex;}
* 现在一个DependencyProperty实例已经被创建而且进入一个全局的HashTable中,下面就是如何使用Dependencyobject的SetValue和GetValue借助这个实例来把保持和读取值了。
  下面是GetValue方法:
public object GetValue(DependencyProperty dp)
{
     this.VerifyAccess();
    if(dp==null)
   {
        throw nwe ArgumentNullException("dp");
    }
    //Call forwarded
   return GetValueEntry(
        LookupEntry(dp.GlobalIndex),
        dp,
        null,
     RequestFlags.FullyResolved).Value;
}

看看最后一句话,拆开来是下面这种形式:

EntryIndex entryIndex = LookupEntry(dp.GlobalIndex);
EffectiveValueEntry valueEntry = GetValueEntry(entryIndex, dp, null,RequestFlags.FullResolved);
return valueEntry.Value;

这里可以认为依赖属性的值都存在于一个小房间里,这里的小房间就是EffectiveValueEntry类的实例。

这个类的实例有GlobalIndex和DependencyProperty来获取。

在DependencyObject类的源码中可以找到这样一个成员变量:

Private EffectiveValueEntry[] _effectiveValues;

//现在清楚了吧? 每个Student实例中都有一个EffectiveValueEntry的数组,用来存储dependencyProperty的value。

3. 现在我们知道了,被static关键字修饰的依赖属性对象的作用是用来检索真正的属性值而不是存储值;被用作检索键值的是依赖属性的GloalIndex属性。

为了保证GlobalIndex属性值得稳定性,我们用readonly来修饰它。

4. 我们说过,依赖属性的值并一定都是我们赋予的,还有其他途径。那么WPF对于这个值得读取是如何的呢? 其实有一个顺序:

    1. WPF属性系统强制值。
    2. 由动画过程控制的值。
    3. 本地变量值(存储在EffectiveValueEntry数组中)
    4. 由上级元素的Template设置的值
    5. 由隐式样式(Implicit Style)设置的值
    6. 由样式之触发器(style Trigger)设置的值
    7. 有模版之触发器(Template Trigger)设置的值
    8. 由样式之设置器(style Setter)设置的值
    9. 有默认央视(defaultStyle)设置的值,默认模式其实就是由主题(Theme)指定的模式。
    10. 默认值

5. 看看SetValue

首先验证依赖属性的值是否可以被改变,如果不能抛异常,如果可以在走以下流程:

    1. 检查值是不是DependencyProperty.UnsetValue, 如果是,说明调用者的意图是清空现有个值。此时程序会调用ClearvalueCommon方法来清空现有值。
    2. 检查EffectiveValueEntry数组中是否已经存在相应的依赖属性的位置,如果有则把旧值改成心智,如果没有则新建一个EffectiveValueEntry对象来存储心智。
    3. 调用UpdateEffectiveValue对新值做一些相应的处理。    

posted on 2012-11-27 17:19  JohnnyNet  阅读(1746)  评论(0编辑  收藏  举报

导航