十. PropertyMetadata测试代码

前面我们看到一个依赖属性的注册最全的形式是下面这样子的:
public static DependencyProperty Register(string name, Type propertyType,Type ownerType, PropertyMetadata typeMetadata,ValidateValueCallback validateValueCallback);
第一个参数是该依赖属性的名字,第二个参数是依赖属性的类型,第三个参数是该依赖属性的所有者的类型,第五个参数就是一个验证值的回调委托,那么最使我们感兴趣的还是这个可爱的  PropertyMetadata ,也就是我们接下来要讲的元数据。 提到WPF属性元数据,大家可能第一想到的是刚才的PropertyMetadata,那么这个类到底是怎样的呢?我们应该怎样使用它呢?首先我们看它的构造函数(我们选参数最多的来讲):
public PropertyMetadata(object defaultValue,PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback);
其中的第一个参数是默认值,最后两个分别是PropertyChanged(变化通知)以及Coerce(强制)的两个委托变量,我们在实例化的时候,只需要把这两个委托变量关联到具体的方法上即可。
事实上,除了PropertyMetadata以外,常见的还有 FrameworkPropertyMetadata,UIPropertyMetadata。他们的继承关系是F->U->P。其中以 FrameworkPropertyMetadata参数最多,亦最为复杂。
FrameworkPropertyMetadata的构造函数提供了很多重载,我们挑选最为复杂的重载来看它到底有哪些参数以及提供了哪些功能:
public FrameworkPropertyMetadata(object defaultValue,FrameworkPropertyMetadataOptions flags,
PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback,bool isAnimationProhibited,
UpdateSourceTrigger defaultUpdateSourceTrigger);
其中第一个参数是默认值,最后两个参数分别是是否允许动画,以及绑定时更新的策略(在Binding当中相信大家并不陌生),这个不详细解释 了。重点看一下里第三、四两个参数,两个 CallBack的委托。结合前面Register的时候提到的ValidateValueCallback共组成三大”金刚“,这三个Callback 分别代表Validate(验证),PropertyChanged(变化通知)以及Coerce(强制)。当然,作为 Metadata,FrameworkPropertyMetadata只是储存了该依赖属性的策略信息,WPF属性系统会根据这些信息来提供功能并在适 当的时机回调传入的delegate,所以最重要的还是我们定义的这些方法,通过他们传入委托才能起到真正的作用。
具体PropertyMetadata包含哪些成员呢?我们先看微软的PropertyMetadata类
在写其他测试用例之前,我们先来创建两个类,第一个类TestDepObj,内部注册了四个依赖属性,前三个没有元数据操作,也就是没有显示声 明并构造元数据类,第四个添加了一个元数据类,这个元数据类包含了默认值、值改变回调委托、强制值回调委托。第二个类TestSubclass继承自 TestDepObj。
   1: class TestDepObj : DependencyObject
   2: {
   3:     public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1", typeof(string), typeof(TestDepObj));
   4:     public static readonly DependencyProperty TestProp2 = DependencyProperty.Register("property2", typeof(string), typeof(TestDepObj));
   5:     public static readonly DependencyProperty TestProp3 = DependencyProperty.Register("property3", typeof(string), typeof(TestDepObj));
   6:  
   7:     public static readonly DependencyProperty TestProp4 = DependencyProperty.Register("property4", typeof(string), typeof(TestDepObj), new PropertyMetadata("default", changed, coerce));
   8:  
   9:     static void changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
  10:     static object coerce(DependencyObject d, object baseValue) { return baseValue; }
  11: }
  12:  
  13: class TestSubclass : TestDepObj
  14: {
  15: }

大家看到我们在创建PropertyMetadata的时候对某些功能并没有实现,这里我们就通过子类来具体实现,MONO的这种做法想沿袭微 软PropertyMetadata、FrameworkPropertyMetadata和UIPropertyMetadata的做法,但是个人觉得 它实现得并不是太好,很多地方感觉很别扭。
   1: //首先我们自定义一个元数据类,继承自我们刚创建的PropertyMetadata类
   2:   public class PropertyMetadataPoker : PropertyMetadata
   3:   {
   4:  
   5:       public bool BaseIsSealed
   6:       {
   7:           get { return base.IsSealed; }
   8:       }
   9:  
  10:       public void CallApply()
  11:       {
  12:           OnApply(TestDepObj.TestProp1, typeof(TestDepObj));
  13:       }
  14:  
  15:       public void CallMerge(PropertyMetadata baseMetadata, DependencyProperty dp)
  16:       {
  17:           Merge(baseMetadata, dp);
  18:       }
  19:  
  20:       protected override void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
  21:       {
  22:           Console.WriteLine(Environment.StackTrace);
  23:           base.Merge(baseMetadata, dp);
  24:       }
  25:  
  26:       protected override void OnApply(DependencyProperty dp, Type targetType)
  27:       {
  28:           //
  29:           base.OnApply(dp, targetType);
  30:           Console.WriteLine("IsSealed in OnApply? {0}", IsSealed);
  31:           Console.WriteLine(Environment.StackTrace);
  32:       }
  33:   }

下面的测试代码主要看一下元数据的默认值,实例化一个元数据类,然后调用它的DefaultValue、PropertyChangedCallback、CoerceValueCallback,测试他们是否为Null。
   1: [Test]
   2:     public void DefaultValues()
   3:     {
   4:         //首先看看元数据的默认值
   5:         PropertyMetadataPoker m = new PropertyMetadataPoker();
   6:         Assert.AreEqual(null, m.DefaultValue);
   7:         Assert.AreEqual(null, m.PropertyChangedCallback);
   8:         Assert.AreEqual(null, m.CoerceValueCallback);
   9:     }

我们在WPF和Silverlight中都有过这样的体会:到底什么时候这个依赖属性不能再修改了,其实这个操作得归功于OnApply什么时 候触发,我们也可以调用IsSealed来查看,那么这里我们就先写测试代码。第一段代码直接显示调用CallApply方法进行密封;第二段代码则是通 过OverrideMetadata操作后内部调用的CallApply;第三段代码是通过AddOwner操作中调用的CallApply;最后一段代 码通过调用DependencyProperty.Register时传入元数据,在其内部调用CallApply。
   1: [Test]
   2:   public void IsSealed()
   3:   {
   4:       //测试元数据是否密封,这个很重要,因为封闭之后就不能修改了,除非用OverrideMetadata或者AddOwner
   5:       PropertyMetadataPoker m;
   6:  
   7:       Console.WriteLine(1);
   8:       // 直接调用 OnApply 查看元数据是否密封
   9:       m = new PropertyMetadataPoker();
  10:       Assert.IsFalse(m.BaseIsSealed);
  11:       m.CallApply();
  12:       Assert.IsFalse(m.BaseIsSealed);
  13:  
  14:       Console.WriteLine(2);
  15:       // 直接 OverrideMetadata
  16:       m = new PropertyMetadataPoker();
  17:       TestDepObj.TestProp1.OverrideMetadata(typeof(TestSubclass), m);
  18:       Assert.IsTrue(m.BaseIsSealed);
  19:  
  20:       Console.WriteLine(3);
  21:       // 调用 DependencyProperty.AddOwner, 通过这种方式 OverrideMetadata
  22:       m = new PropertyMetadataPoker();
  23:       TestDepObj.TestProp2.AddOwner(typeof(TestSubclass), m);
  24:       Assert.IsTrue(m.BaseIsSealed);
  25:  
  26:       Console.WriteLine(4);
  27:       // 最后, 调用DependencyProperty.Register时传入元数据
  28:       m = new PropertyMetadataPoker();
  29:       DependencyProperty.Register("xxx", typeof(string), typeof(TestDepObj), m);
  30:       Assert.IsTrue(m.BaseIsSealed);
  31:   }

下面这段测试代码是验证AddOwner后的DependencyProperty是否和原来的DependencyProperty是同一个DependencyProperty。
   1: [Test]
   2:    public void TestAddOwnerResult()
   3:    {
   4:        //测试AddOwner后的DependencyProperty是否和原来的DependencyProperty是同一个DependencyProperty
   5:        PropertyMetadataPoker m = new PropertyMetadataPoker();
   6:        DependencyProperty p = TestDepObj.TestProp3.AddOwner(typeof(TestSubclass), m);
   7:  
   8:        //结果是同一个DependencyProperty
   9:        Assert.AreSame(p, TestDepObj.TestProp3);
  10:    }

下面这个测试用例是首先实例化元数据并作为注册依赖属性时的参数传入,大家都知道此时如果想修改元数据,可以通过AddOwner或者OverrideMetadata,如果直接赋值,会抛出错误,因为元数据已经密封。
   1: [Test]
   2:     [ExpectedException(typeof(InvalidOperationException))]
   3:     public void ModifyAfterSealed1()
   4:     {
   5:         //首先实例化元数据并注册依赖属性时作为参数传入
   6:         PropertyMetadataPoker m = new PropertyMetadataPoker();
   7:         DependencyProperty.Register("p1", typeof(string), typeof(TestDepObj), m);
   8:         Assert.IsTrue(m.BaseIsSealed);
   9:  
  10:         //由于元数据已密封,所以抛出如下错误信息:Cannot change metadata once it has been applied to a property
  11:         m.CoerceValueCallback = null;
  12:     }

这个和上面的那个测试用例基本一样,只不过把CoerceValueCallback换成了PropertyChangedCallback
   1: [Test]
   2:    [ExpectedException(typeof(InvalidOperationException))]
   3:    public void ModifyAfterSealed2()
   4:    {
   5:        //首先实例化元数据并注册依赖属性时作为参数传入
   6:        PropertyMetadataPoker m = new PropertyMetadataPoker();
   7:        DependencyProperty.Register("p2", typeof(string), typeof(TestDepObj), m);
   8:        Assert.IsTrue(m.BaseIsSealed);
   9:  
  10:        //由于元数据已密封,所以抛出如下错误信息:Cannot change metadata once it has been applied to a property
  11:        m.PropertyChangedCallback = null;
  12:    }

下面这个测试用例也和上面的两个测试用例类似,它是修改元数据的DefaultValue
   1: [Test]
   2:       [ExpectedException(typeof(InvalidOperationException))]
   3:       public void ModifyAfterSealed3()
   4:       {
   5:           //首先实例化元数据并注册依赖属性时作为参数传入
   6:           PropertyMetadataPoker m = new PropertyMetadataPoker();
   7:           DependencyProperty.Register("p3", typeof(string), typeof(TestDepObj), m);
   8:           Assert.IsTrue(m.BaseIsSealed);
   9:  
  10:           //由于元数据已密封,所以抛出如下错误信息:Cannot change metadata once it has been applied to a property
  11:           m.DefaultValue = "hi";
  12:       }

通过前面的测试用例,大家可能都会发现有一个Merge这个方法,它在什么时候调用呢?其实它在OverrideMetadata和AddOwner操作中都会调用,在
DependencyProperty中的Register也会显示调用一次。我们需要注意的是:在元数据密封了以后就会抛出错误。
   1: [Test]
   2:     public void TestMerge()
   3:     {
   4:         //需要注意的是:在元数据密封了以后就会抛出错误
   5:         PropertyMetadataPoker m = new PropertyMetadataPoker();
   6:         m.CallMerge(TestDepObj.TestProp4.GetMetadata(typeof(TestDepObj)), TestDepObj.TestProp4);
   7:         Assert.AreEqual("default", m.DefaultValue);
   8:         Assert.IsNotNull(m.CoerceValueCallback);
   9:         Assert.IsNotNull(m.PropertyChangedCallback);
  10:  
  11:         m = new PropertyMetadataPoker();
  12:         m.DefaultValue = "non-default";
  13:         m.CallMerge(TestDepObj.TestProp4.GetMetadata(typeof(TestDepObj)), TestDepObj.TestProp4);
  14:         Assert.AreEqual("non-default", m.DefaultValue);
  15:         Assert.IsNotNull(m.CoerceValueCallback);
  16:         Assert.IsNotNull(m.PropertyChangedCallback);
  17:  
  18:         //我们知道元数据包括DefaultValue、 coerce 和 property changed等
  19:         //这里我们就不一一测试了,其他测试结果都是一样的
  20:     }

下面的测试用例主要是默认值是不能被设置成Unset的
   1: [Test]
   2:    [ExpectedException(typeof(ArgumentException))]
   3:    public void TestSetDefaultToUnsetValue()
   4:    {
   5:        //默认值是不能被设置成Unset的
   6:        PropertyMetadata m = new PropertyMetadata();
   7:        m.DefaultValue = DependencyProperty.UnsetValue;
   8:    }
   9:  
  10:    [Test]
  11:    [ExpectedException(typeof(ArgumentException))]
  12:    public void TestInitDefaultToUnsetValue()
  13:    {
  14:        //默认值是不能被设置成Unset的
  15:        new PropertyMetadata(DependencyProperty.UnsetValue);
  16:    }

通过前面的多个测试用例,其实已经包含了PropertyMetadata的基本功能,那我们接下来就看一下PropertyMetadata的内部设计和实现。

十一. PropertyMetadata实现代码

MONO的PropertyMetadata类要比微软的PropertyMetadata类简单很多,不过我们也需要注意一下几点:
1,元数据类包含哪些成员以及有几个构造函数重载?因为这些直接关系到外部的调用。
2,大家要注意ValidateValueCallback不是PropertyMetadata的成员,所以在PropertyMetadata的构造函数中不要把它作为参数传入。
3,注意OnApply函数,因为调用它之后就不能修改元数据的成员,只有通过OverrideMetadata和AddOwner间接实现,如果大家想知道到底这个元数据有没有被密封,可以调用IsSealed属性来查看,这个功能我们也会经常用到。
4,元数据类中提供了Merge的功能,用来方便合并父类和子类的元数据。
   1: namespace System.Windows
   2: {
   3:     //依赖属性三大回调委托:PropertyChangedCallback、CoerceValueCallback和ValidateValueCallback
   4:     public delegate void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e);
   5:     public delegate object CoerceValueCallback(DependencyObject d, object baseValue);
   6:     public delegate bool ValidateValueCallback(object value);
   7:  
   8:     public class PropertyMetadata
   9:     {
  10:         private object defaultValue;
  11:         private bool isSealed;
  12:         private PropertyChangedCallback propertyChangedCallback;
  13:         private CoerceValueCallback coerceValueCallback;
  14:  
  15:         //返回该元数据是否已密封
  16:         protected bool IsSealed
  17:         {
  18:             get { return isSealed; }
  19:         }
  20:  
  21:         //获取和设置元数据默认值
  22:         public object DefaultValue
  23:         {
  24:             get { return defaultValue; }
  25:             set
  26:             {
  27:                 if (IsSealed)
  28:                     throw new InvalidOperationException("Cannot change metadata once it has been applied to a property");
  29:                 if (value == DependencyProperty.UnsetValue)
  30:                     throw new ArgumentException("Cannot set property metadata's default value to 'Unset'");
  31:  
  32:                 defaultValue = value;
  33:             }
  34:         }
  35:  
  36:         //ChangedCallback委托赋值,注意检查元数据是否已经密封
  37:         public PropertyChangedCallback PropertyChangedCallback
  38:         {
  39:             get { return propertyChangedCallback; }
  40:             set
  41:             {
  42:                 if (IsSealed)
  43:                     throw new InvalidOperationException("Cannot change metadata once it has been applied to a property");
  44:                 propertyChangedCallback = value;
  45:             }
  46:         }
  47:  
  48:         //CoerceValueCallback委托赋值,注意检查元数据是否已经密封
  49:         public CoerceValueCallback CoerceValueCallback
  50:         {
  51:             get { return coerceValueCallback; }
  52:             set
  53:             {
  54:                 if (IsSealed)
  55:                     throw new InvalidOperationException("Cannot change metadata once it has been applied to a property");
  56:                 coerceValueCallback = value;
  57:             }
  58:         }
  59:  
  60:         #region PropertyMetadata构造函数,根据不同参数做初始化操作
  61:         public PropertyMetadata()
  62:             : this(null, null, null)
  63:         {
  64:         }
  65:  
  66:         public PropertyMetadata(object defaultValue)
  67:             : this(defaultValue, null, null)
  68:         {
  69:         }
  70:  
  71:         public PropertyMetadata(PropertyChangedCallback propertyChangedCallback)
  72:             : this(null, propertyChangedCallback, null)
  73:         {
  74:         }
  75:  
  76:         public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback)
  77:             : this(defaultValue, propertyChangedCallback, null)
  78:         {
  79:         }
  80:  
  81:         public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback)
  82:         {
  83:             if (defaultValue == DependencyProperty.UnsetValue)
  84:                 throw new ArgumentException("Cannot initialize property metadata's default value to 'Unset'");
  85:  
  86:             this.defaultValue = defaultValue;
  87:             this.propertyChangedCallback = propertyChangedCallback;
  88:             this.coerceValueCallback = coerceValueCallback;
  89:         }
  90:         #endregion
  91:  
  92:         //合并元数据
  93:         protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
  94:         {
  95:             if (defaultValue == null)
  96:                 defaultValue = baseMetadata.defaultValue;
  97:             if (propertyChangedCallback == null)
  98:                 propertyChangedCallback = baseMetadata.propertyChangedCallback;
  99:             if (coerceValueCallback == null)
 100:                 coerceValueCallback = baseMetadata.coerceValueCallback;
 101:         }
 102:  
 103:         protected virtual void OnApply(DependencyProperty dp, Type targetType)
 104:         {
 105:             //留给子类来实现吧!
 106:         }
 107:  
 108:         //合并元数据并密封
 109:         internal void DoMerge(PropertyMetadata baseMetadata, DependencyProperty dp, Type targetType)
 110:         {
 111:             Merge(baseMetadata, dp);
 112:             OnApply(dp, targetType);
 113:             isSealed = true;
 114:         }
 115:     }
 116: }

在上面几个类就是依赖属性系统的核心类,下面将看到几个Helper类。
本文转自KnightsWarrior51CTO博客,原文链接: http://blog.51cto.com/knightswarrior/405233 ,如需转载请自行联系原作者

依赖属性之“风云再起”四相关推荐

  1. WPF基础到企业应用系列8——依赖属性之“风云再起”

    一. 摘要 首先圣殿骑士很高兴"WPF 基础到企业应用系列" 能得到大家的关注.支持和认可.看到很多朋友留言希望加快速度的问题,我会尽力的,对你们的热情关注也表示由衷的感谢.这段时 ...

  2. 依赖属性之“风云再起”五

    十二. 其他协助类测试代码 这里就简单写一下对DependencyObjectTypeTest的测试代码: 1: using System; 2: using System.Windows; 3: u ...

  3. 依赖属性之“风云再起”三

    八. DependencyObject测试代码 在写DependencyObject测试代码之前,我们先看一下它到底有哪些成员和方法,如下图: 通过上面的这幅图,我们知道它的主要功能包括:各种依赖属性 ...

  4. 依赖属性之“风云再起”二

    五. 引入测试驱动开发 1,引入概念 由于本篇的依赖属性体系是基于测试驱动开发完成的,所以我们就先来看一下什么叫测试驱动开发:测试驱动开发的基本思想就是在开发功能代码之前, 先编写测试代码.也就是说在 ...

  5. WPF入门教程系列十四——依赖属性(四)

    六.依赖属性回调.验证及强制值 我们通过下面的这幅图,简单介绍一下WPF属性系统对依赖属性操作的基本步骤: 借用一个常见的图例,介绍一下WPF属性系统对依赖属性操作的基本步骤: 第一步,确定Base ...

  6. WPF入门教程(七)---依赖属性(3)(转)

    WPF入门教程(七)---依赖属性(3) 2018年08月24日 08:33:43 weixin_38029882 阅读数:50 四. 只读依赖属性 在以前在对于非WPF的功能来说,对于类的属性的封装 ...

  7. 初步了解WPF依赖属性

    一 依赖属性 在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性.但必须明确,在WPF中我们大多数都在使用依赖属性,而不是使用属性.依赖属性重要性在 ...

  8. WPF入门教程系列十三——依赖属性(三)

    四. 只读依赖属性 在以前在对于非WPF的功能来说,对于类的属性的封装中,经常会对那些希望暴露给外界只读操作的字段封装成只读属性,同样在WPF中也提供了只读属性的概念,如一些 WPF控件的依赖属性是只 ...

  9. WPF快速入门系列(2)——深入解析依赖属性

    一.引言 感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己开始更新WPF系列.尽管最近看到一篇WPF技术是否老矣的文章,但是还是不能阻止我系统学习WPF.今天继续分享WPF中一个最 ...

最新文章

  1. c bool 类型检查_C语言和C+的区别是什么?8个点通俗易懂的告诉你!
  2. Android 实现边听边录音探究
  3. uva-10152-乌龟排序
  4. 【CyberSecurityLearning 52】Web架构安全分析(web工作机制、HTTP协议)
  5. k8s修改kube-apiserver的service-node-port-range端口范围
  6. 本案例通过ArrayList集合来显示Collection接口中的共性函数
  7. python循环结构代码_Python --- 程序的循环结构
  8. vue全局引入字体并使用
  9. 图解通信原理与案例分析-34:车联网中的系统架构与主要的通信技术
  10. 显卡简介,显卡怎么查看
  11. IE与非IE浏览器调用PC摄像头拍摄并且上传
  12. 匈牙利命名法(Hungarian Notation)
  13. JavaScript 高级
  14. 服务器名称没有显示,远程服务器名称问题没有解决
  15. 程序员不能忍996了!发起抗议网站,GitHub一小时破千星
  16. 笔记本win10相机打不开 无法启动 显示灰色相机
  17. 计算机夏令营英语自我介绍,夏令营英文自我介绍范文5篇
  18. Highchart 改编风力风向图
  19. Samba服务+Samba实验
  20. 对于机械键盘的一些见解(转载)

热门文章

  1. 淘淘商城总结-后台管理
  2. Android开发中之手机定位(记录)
  3. 用表单实现qq注册页面
  4. 使用Matlab实现美颜功能(双边滤波器)
  5. 快速幂 FastPow
  6. 食谱菜单系统(通用版).exe
  7. secureCRT永久注册码和资源
  8. 记录自己看selenium源码的一些收获(五)RemoteWebDriver类
  9. 微信小程序 webiew缓存问题
  10. linux那些事之pin memory(get_user_pages())