WPF基础到企业应用系列8——依赖属性之“风云再起”
一. 摘要首先圣殿骑士很高兴”WPF 基础到企业应用系列” 能得到大家的关注、支持和认可。看到很多朋友留言希望加快速度的问题,我会尽力的,对你们的热情关注也表示由衷的感谢。这段时间更新慢的主要原因是因为忙着用TDD还原MONO的框架,同时也因为一直在研究云计算,所以就拖拖拉拉一直没有发布后面的文章。由于WPF整个系列是自己的一些粗浅心得和微薄经验,所以不会像写书那么面面俱到,如果有不足或者错误之处也请大家见谅。在今年之内圣殿骑士会尽量完成”WPF 基础到企业应用系列”和”云计算之旅系列“,诚然,由于本人才识浅薄,所以热切希望和大家共勉! 由于依赖属性是WPF和Silverlight的核心概念,微软在CS和BS平台上主要精力都放到了WPF和Silverlight技术上,同时Silverlight也是Windows Phone的两大编程模型之一(另外一种是XNA),所以我们花费了大量的时间和篇幅进行论述。在上一篇WPF基础到企业应用系列7——深入剖析依赖属性中,我们首先从依赖属性基本介绍讲起,然后过渡到依赖属性的优先级、附加属性、只读依赖属性、依赖属性元数据、依赖属性回调、验证及强制值、依赖属性监听、代码段(自动生成) 等相关知识,最后我们模拟一个WPF依赖属性的实现,由于上篇是根据微软WPF的BCL源码剖析的,所以这篇我们就研究一下.NET的跨平台版本MONO,看下它是怎么来实现这个依赖属性机制。 二. 本文提纲
三. 兵马未动,废话先行在讲这篇文章之前,我们先来拉一拉家常,说点题外话,就当进入正餐之前的一些甜点,当然这里主要针对.NET平台而言: 1,浅谈软件技术的发展趋势及定位互联网的普及应用催生了很多技术的发展与更新,如果仔细深究,你会发现软件技术的发展趋势将主要体现在以下四个方面:客户端软件开发(其中包括客户端软件、游戏、中间件和嵌入式开发等)、Web 开发(包括传统的Web技术、Web游戏以及一些在线应用)、移动设备软件开发(主要涉及到手机等移动设备)、云计算开发(公有云、私有云、混合云会逐渐界限清晰,云厂商以及云平台也会逐渐整合和成熟起来)。就微软来说,这四个方面主要如下:
其实把这四个方面总结起来就是传说中的微软“三屏一云”战略,从中也可以看出微软逍遥于天地,纵横于宇内,啸傲于世间,雄霸于大地的枭雄战略! 2,浅谈微软跨平台与MONO在谈之前我们先看一下什么是MONO?MONO项目是由Ximian发起、Miguel de lcaza领导、Novell公司主持的项目。它是一个致力于开创.NET在Linux,FreeBSD,Unix,Mac OS X和Solaris等其他平台使用的开源工程。它包含了一个C#语言的编译器,一个CLR的运行时,和一组类库,并逐渐实现了 ADO.NET、ASP.NET、WinForm、Silverlight(可惜没有实现强大的WPF),能够使得开发人员在其他平台用C#开发程序。 ◆ 值得看好的地方:
◆ 不足之处:
◆ 与微软之间的关系微软与MONO之间的关系也一直处于不冷不热的状态,没有明确的反对,也没有明确的支持,究其原因笔者认为主要有以下两点:
◆ 总结虽然目前来说MONO喜忧参半,但优点始终要大于缺点,毕竟每一个框架或者产品都是慢慢不断改进而完善的,更何况开源必将是未来的一个趋势,所以我们有理由也有信心期待它接下来的发展。 3,谈谈源码研究与TDD大家都有一个共识:如果你想研究某个框架或者工具的源码,那先必须熟练使用它,熟练之后自然就有一种研究它的冲动,但是往往这个框架或工具比较庞大,很不容易下手,一个很不错的方法就是使用TDD。我们都知道TDD的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完全部功能的开发,在此过程中我们可以借助一些工具来协助。比如我们现在要研究Nhibernate,那么我们首先要熟练它的一些功能,然后从一个点出发慢慢编写单元测试,然后逐渐完善代码,最后直至完成框架的搭建,这样会给我们带来莫大的驱动力和成就感。除了微软的BCL(Base Class Library)和企业库以外,大家还可以用TDD来试试还原以下的任一开源代码: Spring.NET(http://www.springframework.net/)、Castle(http://www.castleproject.org)、log4net(http://logging.apache.org/log4net/)、 NHibernate(http://www.hibernate.org/343.html)、iBATIS.NET(http://ibatis.apache.org)、Caliburn(http://caliburn.codeplex.com/)、 MVVM Light Toolkit(http://mvvmlight.codeplex.com/)、Prism(http://compositewpf.codeplex.com/)、MONO源码(www.mono-project.com) 四. 依赖属性续前缘大家都知道WPF和Silverlight带来了很多新的特性,其中一大亮点是引入了一种新的属性机制——依赖属性。依赖属性基本应用在了WPF的所有需要设置属性的元素。依赖属性根据多个提供对象来决定它的值(可以是动画、父类元素、绑定、样式和模板等),同时这个值也能及时响应变化。所以WPF拥有了依赖属性后,代码写起来就比较得心应手,功能实现上也变得非常容易了。如果没有依赖属性,我们将不得不编写大量的代码。依赖属性在WPF中用得非常广泛,具体在以下几个方面中表现得尤为突出:
在上一篇WPF基础到企业应用系列7——深入剖析依赖属性中,我们对依赖属性做了较详细的介绍,那么下面我们就简单回顾一下,其实依赖属性的实现很简单,只要做以下步骤就可以实现:
根据前面的四步操作,我们就可以写出下面的代码: 1: public class SampleDPClass : DependencyObject 2: {
3: //声明一个静态只读的DependencyProperty字段 4: public static readonly DependencyProperty SampleProperty; 5:
6: static SampleDPClass() 7: {
8: //注册我们定义的依赖属性Sample 9: SampleProperty = DependencyProperty.Register("Sample",typeof(string),typeof(SampleDPClass), 10: new PropertyMetadata("Knights Warrior!",OnValueChanged)); 11: }
12:
13: private static void OnValueChanged(DependencyObject o,DependencyPropertyChangedEventArgs e) 14: {
15: //当值改变时,我们可以在此做一些逻辑处理 16: }
17:
18: //属性包装器,通过它来读取和设置我们刚才注册的依赖属性 19: public string Sample 20: {
21: get { return (string)GetValue(SampleProperty); } 22: set { SetValue(SampleProperty,value); } 23: }
24: }
通过上面的例子可以看出,我们一般.NET属性是直接对类的一个私有属性进行封装,所以读取值的时候,也就是直接读取这个字段;而依赖属性则是通过调用继承自DependencyObject的GetValue()和SetValue来进行操作,它实际存储在DependencyProperty的一个IDictionary的键-值配对字典中,所以一条记录中的键(Key)就是该属性的HashCode值,而值(Value)则是我们注册的DependencyProperty。 回顾了一些基础知识,那我们下面就开始今天的依赖属性系统TDD之旅。 五. 引入测试驱动开发1,引入概念由于本篇的依赖属性体系是基于测试驱动开发完成的,所以我们就先来看一下什么叫测试驱动开发:测试驱动开发的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完全部功能的开发。由于过程很长,在写的时候也省略了不少步骤,所以有些地方衔接不是那么的流畅,对此表示非常的抱歉! 2,注意事项根据自身做项目使用TDD的一点微薄经验,总结了以下几个注意事项:
3,工具介入以后写关于TDD的文章可能比较多,同时也都会用到这个工具,所以我们今天对它也稍带介绍一下,正所谓“工欲善其事,必先利其器”。根据官方文档解释:TestDriven.NET是Visual Studio的一个TDD插件,最近版本是TestDriven.NET-3.0.2749 RTM版。其中一些新特性有:支持MSTest、.NET Reflector 6 Pro、VS 2010、Silverlight 4、NUnit 2.5.3,使用项目所用的.NET框架等。 下载地址:http://www.testdriven.net/ 这个工具使用起来比VS自带的单元测试和测试覆盖功能好用,所以从2008年开始基本就用它作为一个必备的工具使用。关于它具体的功能和怎么使用,我们这里不详细介绍,网上也有很多文章,大家可以做一下参考和研究。下图是安装后以插件的形式出现在VS中的效果:
A,基本介绍TestDriven.NET原来叫做NUnitAddIn,它是个Visual Studio插件,集成了如下测试框架:NUnit、MbUnit、 ZaneBug、MSTest、NCover、NCoverExplorer、Reflector、TypeMock、dotTrace和MSBee,它主要面向使用TDD的开发者,主要特性列举如下:
B,TestDriven.NET 3.0中的新特性:
C,兼容性
D,版本
4,关于本篇本篇文章没有明确的写作意图,只是最近在深入研究MONO源码时有感而发,当然作者本人也只是起到了一个研究者或者剖析者的角色。首先实现最简单且基本的DependencyProperty.Register功能,然后再实现DependencyObject的GetValue和SetValue,接着实现PropertyMetadata的DefaultValue、PropertyChangedCallback、CoerceValueCallback等功能,然后完善DependencyProperty.Register注册时添加ValidateValueCallback、RegisterAttached、RegisterAttachedReadOnly、RegisterReadOnly、OverrideMetadata、GetMetadata和AddOwner等相关功能。既然有了这些功能,自然就需要完善PropertyMetadata的IsSealed、Merge和OnApply等相关底层操作。当然在中间还需要DependencyObject的ClearValue、CoerceValue、GetLocalValueEnumerator、ReadLocalValue以及其他的Helper类,这里就不一一进行说明。对于边际交互比较多且关联比较大的操作,采用了Mock进行暂时模拟,在开发完了以后再进行了替换。在开发过程中,随时进行单元测试和覆盖率的检查,这样可以方便查看哪些功能还有问题以及整体的进度和质量的监控。 六. DependencyProperty测试代码在写DependencyProperty测试代码之前,我们先看一下它到底有哪些成员和方法,如下图:
了解了上面DependencyProperty的基本功能,我们首先创建一个继承自DependencyObject的类ObjectPoker,由于DependencyObject还没有被创建,所以我们这里就先创建它,然后在ObjectPoker类里面实现我们的经典语句DependencyProperty.Register,由于Register有很多重载,为了方便TDD,就从最简单的开始(三个参数,不牵涉到元数据类),然后再创建一个ObjectPoker的子类,这是方便后面测试DependencyProperty的相关功能。 1: class ObjectPoker : DependencyObject 2: {
3: //注册依赖属性property1 4: public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1",typeof(ObjectPoker)); 5: }
6:
7: class SubclassPoker : ObjectPoker 8: {
9: }
经过上面的测试用例通过以后,自然DependencyProperty.Register的基本功能也就完善了,然后我们来测试一下Register两个相同的依赖属性有什么反应,由于我们为了实现Register时没有考虑那么多,所以测试是先会失败,然后在引入键值对的形式来存储DependencyProperty,然后每个DependencyProperty都用Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode()来区别唯一,所以相同下面的测试用例也将完成。 1: [Test]
2: [ExpectedException(typeof(ArgumentException))] 3: public void TestMultipleRegisters() 4: {
5: //测试注册相同名的依赖属性 6: DependencyProperty.Register("p1",typeof(ObjectPoker)); 7: DependencyProperty.Register("p1",typeof(ObjectPoker)); 8: }
我们说到依赖属性系统,其实依赖属性要依附于DependencyObject才能成为真正的依赖属性系统。所以我们来测试一下AddOwner,每一个Owner都有自己的元数据,这个时候我们需要完善OverrideMetadata方法,然而OverrideMetadata方法需要用到PropertyMetadata类作为参数,同时需要调用PropertyMetadata类的DoMerge方法,我们可以创建该类,然后结合Mock完成该操作。 1: [Test]
2: [ExpectedException(typeof(ArgumentException))] 3: public void TestMultipleAddOwner() 4: {
5: //测试AddOwner,添加相同类型的Owner 6: ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker),new PropertyMetadata()); 7: ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker),new PropertyMetadata()); 8: }
通过上面的测试用例以后,其实PropertyMetadata的原型已经具备了,然后我们要做的就是测试DependencyProperty的默认元数据和默认元数据的默认值。 1: [Test]
2: public void TestDefaultMetadata() 3: {
4: //测试默认元数据 5: DependencyProperty p;
6: p = DependencyProperty.Register("TestDefaultMetadata1",typeof(ObjectPoker)); 7: Assert.IsNotNull(p.DefaultMetadata);
8:
9: //测试元数据的默认值 10: p = DependencyProperty.Register("TestDefaultMetadata2",typeof(ObjectPoker),new PropertyMetadata("hi")); 11: Assert.IsNotNull(p.DefaultMetadata);
12: Assert.AreEqual("hi",p.DefaultMetadata.DefaultValue); 13: }
我们都知道一个DependencyProperty可以拥有多个Owner,每个Owner之间的区别就是用PropertyMetadata,那么这里就给该DependencyProperty添加一个Owner,然后通过该Owner来获取元数据。 1: [Test]
2: public void TestAddOwnerNullMetadata() 3: {
4: //首先注册一个依赖属性,然后再AddOwner,最后根据新的Owner获取元数据 5: DependencyProperty p = DependencyProperty.Register("TestAddOwnerNullMetadata",typeof(ObjectPoker)); 6: p.AddOwner(typeof(SubclassPoker),null); 7:
8: PropertyMetadata pm = p.GetMetadata(typeof(SubclassPoker)); 9: Assert.IsNotNull(pm);
10: }
通过上面的测试用例,我们牵涉到了OverrideMetadata方法,当然上面没有进行实现,这个时候我们可以来实现OverrideMetadata这个方法,首先注册一个ObjectPoker类型的依赖属性,然后通过SubclassPoker来OverrideMetadata。 1: //首先注册一个依赖属性,然后再OverrideMetadata 2: [Test]
3: [ExpectedException(typeof(ArgumentNullException))] 4: public void TestOverrideMetadataNullMetadata() 5: {
6: //有Type但PropertyMetadata为null时,OverrideMetadata操作 7: DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullMetadata",typeof(ObjectPoker)); 8: p.OverrideMetadata(typeof(SubclassPoker),null); 9: }
上面实现了OverrideMetadata的函数,但是只是简单实现,这里我们可以传入一个null类型的Type作为测试,当然测试不会通过,然后就修改代码直到测试通过吧! 1: [Test]
2: [ExpectedException(typeof(ArgumentNullException))] 3: public void TestOverrideMetadataNullType() 4: {
5: //当Type为null,OverrideMetadata操作 6: DependencyProperty p = DependencyProperty.Register("TestOverrideMetadataNullType",typeof(ObjectPoker)); 7: p.OverrideMetadata(null,new PropertyMetadata()); 8: }
如果仔细分析DependencyProperty的源码,你会发现有一个DependencyPropertyKey类,这个类到底是干嘛的呢?其实这个类的主要作用就是构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata,这里只是提供了一个简单的封装,如果没有这个类,其他功能照样正常。 1: [Test]
2: [ExpectedException(typeof(InvalidOperationException))] 3: public void TestReadonlyOverrideMetadata() 4: {
5: //通过DependencyPropertyKey的方式OverrideMetadata 6: DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop1", 7: typeof(double), 8: typeof(ObjectPoker), 9: new PropertyMetadata(double.NaN)); 10: ro_key.DependencyProperty.OverrideMetadata(typeof(SubclassPoker),new PropertyMetadataPoker()); 11: }
最后我们来测试一样通过DependencyPropertyKey类来注册一个ReadOnly的依赖属性,然后进行OverrideMetadata,基本和上一个测试用例类似。 1: [Test]
2: public void TestReadonlyOverrideMetadataFromKey() 3: {
4: //通过DependencyPropertyKey的方式OverrideMetadata 5: DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop2", 6: typeof(double), 7: typeof(ObjectPoker), 8: new PropertyMetadata(double.NaN)); 9: ro_key.OverrideMetadata(typeof(SubclassPoker),new PropertyMetadataPoker()); 10: }
通过上面的测试用例,DependencyProperty类已经基本完成,除了该类,其他诸如DependencyObject、PropertyMetadata、DependencyPropertyKey也已经初步完成,所以我们这里先以DependencyProperty作为切入点,那么下面就来看一下刚才创建的DependencyProperty类。 七. DependencyProperty实现代码具体代码如下,我们就不做过多阐述,不过有几点需要注意: 1,一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据。 2,依赖属性私有构造函数,作为初始化操作,每个依赖属性在注册的时候都会调用并初始化数据 3,为了区别不同的依赖属性,Name、PropertyType、OwnerType的哈希值取异。 4,注册依赖属性有以下几个种类:Register、RegisterAttached、RegisterAttachedReadOnly和RegisterReadOnly,所以要区别对待。 5,由于一个依赖属性可能有多个Owner,根据每个Owner都有自己的元数据,所以要有根据Owner的AddOwner、GetMetadata和OverrideMetadata的操作。 1:
2: using System.Collections.Generic; 3: namespace System.Windows 4: {
5: public sealed class DependencyProperty 6: {
7: //一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据 8: private Dictionary<Type,PropertyMetadata> metadataByType = new Dictionary<Type,PropertyMetadata>(); 9:
10: //声明一个UnsetValue 11: public static readonly object UnsetValue = new object (); 12:
13: //依赖属性私有构造函数,作为初始化操作,每个依赖属性在注册的时候都会调用并初始化数据 14: private DependencyProperty (bool isAttached,string name,Type propertyType,Type ownerType, 15: PropertyMetadata defaultMetadata,
16: ValidateValueCallback validateValueCallback)
17: {
18: IsAttached = isAttached;
19: DefaultMetadata = (defaultMetadata == null ? new PropertyMetadata() : defaultMetadata); 20: Name = name;
21: OwnerType = ownerType;
22: PropertyType = propertyType;
23: ValidateValueCallback = validateValueCallback;
24: }
25:
26: internal bool IsAttached { get; set; } 27: public bool ReadOnly { get; private set; } 28: public PropertyMetadata DefaultMetadata { get; private set; } 29: public string Name { get; private set; } 30: public Type OwnerType { get; private set; } 31: public Type PropertyType { get; private set; } 32: public ValidateValueCallback ValidateValueCallback { get; private set; } 33:
34: //获取依赖属性的编号,暂未实现,在上一篇“WPF基础到企业应用系列7——深入剖析依赖属性”有实现,原理是在初始化的时候++ 35: public int GlobalIndex { 36: get { throw new NotImplementedException (); } 37: }
38:
39: //传入ownerType增加Owner 40: public DependencyProperty AddOwner(Type ownerType) 41: {
42: return AddOwner (ownerType,null); 43: }
44:
45: //增加所有者,根据ownerType和typeMetadata 46: public DependencyProperty AddOwner(Type ownerType,PropertyMetadata typeMetadata) 47: {
48: if (typeMetadata == null) typeMetadata = new PropertyMetadata (); 49: OverrideMetadata (ownerType,typeMetadata);
50:
51: // MS seems to always return the same DependencyProperty 52: return this; 53: }
54:
55: //获取元数据,依据forType 56: public PropertyMetadata GetMetadata(Type forType) 57: {
58: if (metadataByType.ContainsKey (forType)) 59: return metadataByType[forType]; 60: return null; 61: }
62:
63: //获取元数据,依据该依赖属性 64: public PropertyMetadata GetMetadata(DependencyObject d) 65: {
66: if (metadataByType.ContainsKey (d.GetType())) 67: return metadataByType[d.GetType()]; 68: return null; 69: }
70:
71: //获取元数据,依据dependencyObjectType 72: public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType) 73: {
74: if (metadataByType.ContainsKey (dependencyObjectType.SystemType)) 75: return metadataByType[dependencyObjectType.SystemType]; 76: return null; 77: }
78:
79: //验证类型是否有效 80: public bool IsValidType(object value) 81: {
82: return PropertyType.IsInstanceOfType (value); 83: }
84:
85: //验证值是否有效 86: public bool IsValidValue(object value) 87: {
88: if (!IsValidType (value)) 89: return false; 90: if (ValidateValueCallback == null) 91: return true; 92: return ValidateValueCallback (value); 93: }
94:
95: //重写元数据,使用PropertyMetadata类的DoMerge方法来操作 96: public void OverrideMetadata(Type forType,PropertyMetadata typeMetadata) 97: {
98: if (forType == null) 99: throw new ArgumentNullException ("forType"); 100: if (typeMetadata == null) 101: throw new ArgumentNullException ("typeMetadata"); 102:
103: if (ReadOnly) 104: throw new InvalidOperationException (String.Format ("Cannot override metadata on readonly property '{0}' without using a DependencyPropertyKey",Name)); 105:
106: typeMetadata.DoMerge (DefaultMetadata,this,forType); 107: metadataByType.Add (forType,typeMetadata);
108: }
109:
110: //重写元数据,使用PropertyMetadata类的DoMerge方法来操作 111: public void OverrideMetadata (Type forType,PropertyMetadata typeMetadata,DependencyPropertyKey key) 112: {
113: if (forType == null) 114: throw new ArgumentNullException ("forType"); 115: if (typeMetadata == null) 116: throw new ArgumentNullException ("typeMetadata"); 117:
118: typeMetadata.DoMerge (DefaultMetadata,forType);
119: metadataByType.Add (forType,typeMetadata);
120: }
121:
122: public override string ToString () 123: {
124: return Name; 125: }
126:
127: //得到哈希值,区别不同的依赖属性,Name、PropertyType、OwnerType的哈希值取异 128: public override int GetHashCode () 129: {
130: return Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode(); 131: }
132:
133: //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type) 134: public static DependencyProperty Register(string name,Type ownerType) 135: {
136: return Register(name,propertyType,ownerType,null,null); 137: }
138:
139: //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据) 140: public static DependencyProperty Register(string name, 141: PropertyMetadata typeMetadata)
142: {
143: return Register(name,typeMetadata,null); 144: }
145:
146: //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托) 147: public static DependencyProperty Register(string name, 148: PropertyMetadata typeMetadata,
149: ValidateValueCallback validateValueCallback)
150: {
151: if (typeMetadata == null) 152: typeMetadata = new PropertyMetadata(); 153:
154: DependencyProperty dp = new DependencyProperty(false,name, 155: typeMetadata,validateValueCallback);
156: DependencyObject.register(ownerType,dp);
157:
158: dp.OverrideMetadata (ownerType,typeMetadata);
159:
160: return dp; 161: }
162:
163: //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type) 164: public static DependencyProperty RegisterAttached(string name,Type ownerType) 165: {
166: return RegisterAttached(name,null); 167: }
168:
169: //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据) 170: public static DependencyProperty RegisterAttached(string name, 171: PropertyMetadata defaultMetadata)
172: {
173: return RegisterAttached(name,defaultMetadata,null); 174: }
175:
176: //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托) 177: public static DependencyProperty RegisterAttached(string name, 178: PropertyMetadata defaultMetadata,
179: ValidateValueCallback validateValueCallback)
180: {
181: DependencyProperty dp = new DependencyProperty(true, 182: defaultMetadata,validateValueCallback);
183: DependencyObject.register(ownerType,dp);
184: return dp; 185: }
186:
187: //注册只读依赖属性,暂未实现 188: public static DependencyPropertyKey RegisterAttachedReadOnly(string name, 189: PropertyMetadata defaultMetadata)
190: {
191: throw new NotImplementedException("RegisterAttachedReadOnly(string name,PropertyMetadata defaultMetadata)"); 192: }
193:
194: //注册只读依赖属性,暂未实现 195: public static DependencyPropertyKey RegisterAttachedReadOnly(string name, 196: PropertyMetadata defaultMetadata,
197: ValidateValueCallback validateValueCallback)
198: {
199: throw new NotImplementedException("RegisterAttachedReadOnly(string name,PropertyMetadata defaultMetadata,ValidateValueCallback validateValueCallback)"); 200: }
201:
202: //注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据) 203: public static DependencyPropertyKey RegisterReadOnly(string name, 204: PropertyMetadata typeMetadata)
205: {
206: return RegisterReadOnly (name,null); 207: }
208:
209: //注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托) 210: public static DependencyPropertyKey RegisterReadOnly(string name, 211: PropertyMetadata typeMetadata,
212: ValidateValueCallback validateValueCallback)
213: {
214: DependencyProperty prop = Register (name,validateValueCallback);
215: prop.ReadOnly = true; 216: return new DependencyPropertyKey (prop); 217: }
218:
219: }
220: }
通过前面的步骤,DependencyProperty已经完成,那么下面我们再来看一下DependencyObject类。 八. DependencyObject测试代码在写DependencyObject测试代码之前,我们先看一下它到底有哪些成员和方法,如下图: 通过上面的这幅图,我们知道它的主要功能包括:各种依赖属性的GetValue、SetValue操作(核心功能)和ClearValue、CoerceValue、GetLocalValueEnumerator、ReadLocalValue等操作。为了测试这些功能,我们首先创建几个类,第一个类X,内部首先注册一个附加依赖属性,我们都知道,不管是附加依赖属性还是依赖属性,都需要使用到GetValue和SetValue操作,只是一个封装成了属性,而另一个封装成了静态方法而已。第二个类直接继承自我们前面在实现DependencyProperty时创建的DependencyObject原型类。 1: class X { 2: //注册一个附加依赖属性A 3: public static readonly DependencyProperty AProperty = DependencyProperty.RegisterAttached("A",typeof(int),typeof(X)); 4: //获取附加属性A的值 5: public static void SetA(DependencyObject obj,int value) 6: {
7: obj.SetValue(AProperty,value); 8: }
9: //设置附加属性A的值 10: public static int GetA(DependencyObject obj) 11: {
12: return (int)obj.GetValue(AProperty); 13: }
14: //注册一个附加依赖属性B 15: public static readonly DependencyProperty BProperty = DependencyProperty.RegisterAttached("B",typeof(X)); 16: //设置附加属性B的值 17: public static void SetB(DependencyObject obj,string value) 18: {
19: obj.SetValue(BProperty,value); 20: }
21: //获取附加属性B的值 22: public static string GetB(DependencyObject obj) 23: {
24: return (string)obj.GetValue(BProperty); 25: }
26:
27: }
28:
29: class Y : DependencyObject { 30: }
第三个类则是为了直接测试注册一个依赖属性,这个类首先继承自DependencyObject原型类。 1: class Z : DependencyObject 2: {
3: public static readonly DependencyProperty SimpleDPProperty = 4: DependencyProperty.Register("SimpleDP",typeof(double),typeof(Z), 5: new PropertyMetadata((double)0.0, 6: new PropertyChangedCallback(OnValueChanged), 7: new CoerceValueCallback(CoerceValue)), 8: new ValidateValueCallback(IsValidValue)); 9:
10: public double SimpleDP 11: {
12: get { return (double)GetValue(SimpleDPProperty); } 13: set { SetValue(SimpleDPProperty,value); } 14: }
15:
16: private static void OnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) 17: {
18: Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}",e.NewValue); 19: }
20:
21: private static object CoerceValue(DependencyObject d,object value) 22: {
23: Console.WriteLine("对值进行限定,强制值: {0}",value); 24: return value; 25: }
26:
27: private static bool IsValidValue(object value) 28: {
29: Console.WriteLine("验证值是否通过,如果返回True表示验证通过,否则会以异常的形式暴露: {0}",value); 30: return true; 31: }
32:
33: }
首先我们先写测试GetValue和SetValue操作的测试代码,然后不能通过,最后完善DependencyObject类的GetValue和SetValue方法直到测试用例通过。 1: [Test]
2: [Category ("NotWorking")] 3: public void TestAttachedProperty() 4: {
5: Y y1 = new Y(); 6: X.SetA(y1,2);
7: Assert.AreEqual(2,X.GetA(y1));
8: }
由于这里是y1和y2两个对象,所以他们的GetValue和SetValue也是设置和取得各自的值。 1: [Test]
2: [Category ("NotWorking")] 3: public void Test2AttachedProperties() 4: {
5: Y y1 = new Y(); 6: Y y2 = new Y(); 7: X.SetA(y1,2);
8: X.SetA(y2,3);
9: Assert.AreEqual(2,X.GetA(y1));
10: Assert.AreEqual(3,X.GetA(y2));
11: }
通过前面的图,大家可以看到DependencyObject提供了一个取得本地值枚举器的GetLocalValueEnumerator方法,它实现一个IEnumerator来方便访问LocalValue,这里我们要实现它,所以先写测试代码。 1: [Test]
2: [Category ("NotWorking")] 3: public void TestEnumerationOfAttachedProperties() 4: {
5: int count = 0; 6: Y y = new Y(); 7: X.SetA(y,2);
8: X.SetB(y,"Hi"); 9:
10: //根据DependencyObject得到所有本地值 11: LocalValueEnumerator e = y.GetLocalValueEnumerator();
12: while (e.MoveNext()) { 13: count++;
14: if (e.Current.Property == X.AProperty) 15: Assert.AreEqual(e.Current.Value,2);
16: else if (e.Current.Property == X.BProperty) 17: Assert.AreEqual(e.Current.Value,"Hi"); 18: else 19: Assert.Fail("Wrong sort of property" + e.Current.Property); 20: }
21: //count为2 22: Assert.AreEqual(2,count);
23: }
还有几个功能,既然Mono也没做研究,我们也就不费那个力气了,接下来我们就看看刚才实现的DependencyObject代码吧! 九. DependencyObject实现代码通过前面的测试用例,DependencyObject类的基本功能已经完成,不过我们要注意几个要点: 2,不管是Register、RegisterAttached、RegisterAttachedReadOnly还是RegisterReadOnly操作,我们都要通过DependencyObject来操作DependencyProperty的值,也就是通过DependencyObject这个外部接口来操作,DependencyProperty只负责注册和内部处理,不负责外部接口。 3,在DependencyObject中提供了几个操作LocalValue的接口的接口,其中包括ReadLocalValue、GetLocalValueEnumerator、CoerceValue和ClearValue等。 4,在注册注册依赖属性时,实质是关联DependencyObject的propertyDeclarations,它是一个Dictionary<Type,Dictionary<string,DependencyProperty>>类型,但是在register代码中并没有完全关联起来,我也比较纳闷,所以这点还希望和大家一起探讨,微软的BCL并没有这么实现。 1: using System.Collections.Generic; 2: //using System.Windows.Threading; 3:
4: namespace System.Windows 5: {
6: public class DependencyObject 7: {
8: //依赖属性其实终究要DependencyObject和DependencyProperty成对才能算得上真正的DependencyProperty 9: private static Dictionary<Type,Dictionary<string,DependencyProperty>> propertyDeclarations = new Dictionary<Type,DependencyProperty>>(); 10: //该依赖属性的键值对,键为DependencyProperty,值为object 11: private Dictionary<DependencyProperty,object> properties = new Dictionary<DependencyProperty,object>(); 12:
13: //是否已密封,没有实现DependencyObject层次的IsSealed判断 14: public bool IsSealed { 15: get { return false; } 16: }
17:
18: //获取该DependencyObject的DependencyObjectType 19: public DependencyObjectType DependencyObjectType { 20: get { return DependencyObjectType.FromSystemType (GetType()); } 21: }
22:
23: //根据该依赖属性名,清除它的值 24: public void ClearValue(DependencyProperty dp) 25: {
26: if (IsSealed) 27: throw new InvalidOperationException ("Cannot manipulate property values on a sealed DependencyObject"); 28:
29: properties[dp] = null; 30: }
31:
32: //根据该依赖属性DependencyPropertyKey,清除它的值 33: public void ClearValue(DependencyPropertyKey key) 34: {
35: ClearValue (key.DependencyProperty);
36: }
37:
38: //根据该依赖属性名,强制值 39: public void CoerceValue (DependencyProperty dp) 40: {
41: PropertyMetadata pm = dp.GetMetadata (this); 42: if (pm.CoerceValueCallback != null) 43: pm.CoerceValueCallback (this,GetValue (dp)); 44: }
45:
46: public sealed override bool Equals (object obj) 47: {
48: throw new NotImplementedException("Equals"); 49: }
50:
51: public sealed override int GetHashCode () 52: {
53: throw new NotImplementedException("GetHashCode"); 54: }
55:
56: //得到本地值的枚举器 57: public LocalValueEnumerator GetLocalValueEnumerator() 58: {
59: return new LocalValueEnumerator(properties); 60: }
61:
62: //根据依赖属性名获取值 63: public object GetValue(DependencyProperty dp) 64: {
65: object val = properties[dp]; 66: return val == null ? dp.DefaultMetadata.DefaultValue : val; 67: }
68:
69:
70: public void InvalidateProperty(DependencyProperty dp) 71: {
72: throw new NotImplementedException("InvalidateProperty(DependencyProperty dp)"); 73: }
74:
75: //当属性值改变时,触发回调 76: protected virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 77: {
78: PropertyMetadata pm = e.Property.GetMetadata (this); 79: if (pm.PropertyChangedCallback != null) 80: pm.PropertyChangedCallback (this,e); 81: }
82:
83: //提供一个外界查看LocalValue的接口 84: public object ReadLocalValue(DependencyProperty dp) 85: {
86: object val = properties[dp]; 87: return val == null ? DependencyProperty.UnsetValue : val; 88: }
89:
90: //根据依赖属性名设置其值 91: public void SetValue(DependencyProperty dp,object value) 92: {
93: if (IsSealed) 94: throw new InvalidOperationException ("Cannot manipulate property values on a sealed DependencyObject"); 95:
96: if (!dp.IsValidType (value)) 97: throw new ArgumentException ("value not of the correct type for this DependencyProperty"); 98:
99: ValidateValueCallback validate = dp.ValidateValueCallback;
100: if (validate != null && !validate(value)) 101: throw new Exception("Value does not validate"); 102: else 103: properties[dp] = value; 104: }
105:
106: //根据依赖属性DependencyPropertyKey设置其值 107: public void SetValue(DependencyPropertyKey key,object value) 108: {
109: SetValue (key.DependencyProperty,value); 110: }
111:
112: protected virtual bool ShouldSerializeProperty (DependencyProperty dp) 113: {
114: throw new NotImplementedException (); 115: }
116:
117: //这里的注册实质是关联propertyDeclarations 118: internal static void register(Type t,DependencyProperty dp) 119: {
120: if (!propertyDeclarations.ContainsKey (t)) 121: propertyDeclarations[t] = new Dictionary<string,DependencyProperty>(); 122: Dictionary<string,DependencyProperty> typeDeclarations = propertyDeclarations[t]; 123: if (!typeDeclarations.ContainsKey(dp.Name)) 124: {
125: typeDeclarations[dp.Name] = dp;
126: //这里仍然有一些问题,期待各位共同探讨解决 127: }
128: else 129: throw new ArgumentException("A property named " + dp.Name + " already exists on " + t.Name); 130: }
131: }
132: }
通过前面对DependencyObject和DependencyProperty的研究之后,我们来看看最重要的一个角色,这也是微软最喜欢用的概念——元数据,如果大家研究过微软BCL的源码,应该都知道,它是贯穿于整个CLR当中的。 十. 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(TestDepObj)); 4: public static readonly DependencyProperty TestProp2 = DependencyProperty.Register("property2",typeof(TestDepObj)); 5: public static readonly DependencyProperty TestProp3 = DependencyProperty.Register("property3",typeof(TestDepObj)); 6:
7: public static readonly DependencyProperty TestProp4 = DependencyProperty.Register("property4",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",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",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",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",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) 63: {
64: }
65:
66: public PropertyMetadata(object defaultValue) 67: : this(defaultValue,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,null) 78: {
79: }
80:
81: public PropertyMetadata(object defaultValue,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类。 十二. 其他协助类测试代码这里就简单写一下对DependencyObjectTypeTest的测试代码: 1: using System; 2: using System.Windows; 3: using NUnit.Framework; 4:
5: namespace TDDDependencyTest.System.Windows 6: {
7: [TestFixture]
8: public class DependencyObjectTypeTest 9: {
10:
11: [Test]
12: public void Accessors() 13: {
14: DependencyObjectType t = DependencyObjectType.FromSystemType(typeof(TestDepObj)); 15: Assert.AreEqual("TestDepObj",t.Name); 16: Assert.AreEqual(typeof(TestDepObj),t.SystemType); 17: Assert.AreEqual(typeof(DependencyObject),t.BaseType.SystemType); 18: }
19:
20: [Test]
21: public void IsInstanceOfType() 22: {
23: DependencyObjectType t = DependencyObjectType.FromSystemType(typeof(TestDepObj)); 24: DependencyObjectType t2 = DependencyObjectType.FromSystemType(typeof(TestSubclass)); 25: Assert.IsTrue(t.IsInstanceOfType(new TestSubclass())); 26: Assert.IsTrue(t2.IsSubclassOf(t));
27: Assert.IsFalse(t.IsSubclassOf(t2));
28: }
29:
30: [Test]
31: public void TestCache() 32: {
33: DependencyObjectType t = DependencyObjectType.FromSystemType(typeof(TestDepObj)); 34: DependencyObjectType t2 = DependencyObjectType.FromSystemType(typeof(TestDepObj)); 35: Assert.AreSame(t,t2);
36: }
37: }
38: }
由于它的功能比较简单,所以我们就不做过多介绍,大家想了解更多,可以参看代码。 十三. 其他协助类的实现代码LocalValueEnumerator:手动实现一个IEnumerator来方便访问LocalValue 1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Collections; 6:
7: namespace System.Windows 8: {
9: //手动实现一个IEnumerator来方便访问LocalValue 10: public struct LocalValueEnumerator : IEnumerator 11: {
12: private IDictionaryEnumerator propertyEnumerator; 13: private Dictionary<DependencyProperty,object> properties; 14:
15: private int count; 16:
17: internal LocalValueEnumerator(Dictionary<DependencyProperty,object> properties) 18: {
19: this.count = properties.Count; 20: this.properties = properties; 21: this.propertyEnumerator = properties.GetEnumerator(); 22: }
23:
24: public int Count 25: {
26: get { return count; } 27: }
28:
29: //获取当前LocalValue 30: public LocalValueEntry Current 31: {
32: get
33: {
34: return new LocalValueEntry((DependencyProperty)propertyEnumerator.Key, 35: propertyEnumerator.Value);
36: }
37: }
38:
39: object IEnumerator.Current 40: {
41: get { return this.Current; } 42: }
43:
44:
45: public bool MoveNext() 46: {
47: return propertyEnumerator.MoveNext(); 48: }
49:
50: //重置propertyEnumerator 51: public void Reset() 52: {
53: propertyEnumerator.Reset();
54: }
55:
56: public static bool operator !=(LocalValueEnumerator obj1,LocalValueEnumerator obj2) 57: {
58: throw new NotImplementedException(); 59: }
60:
61: public static bool operator ==(LocalValueEnumerator obj1,LocalValueEnumerator obj2) 62: {
63: throw new NotImplementedException(); 64: }
65:
66: public override bool Equals(object obj) 67: {
68: throw new NotImplementedException(); 69: }
70:
71: public override int GetHashCode() 72: {
73: throw new NotImplementedException(); 74: }
75: }
76:
77: //LocalValue实体类 78: public struct LocalValueEntry 79: {
80: private DependencyProperty property; 81: private object value; 82:
83: internal LocalValueEntry(DependencyProperty property,object value) 84: {
85: this.property = property; 86: this.value = value; 87: }
88:
89: public DependencyProperty Property 90: {
91: get { return property; } 92: }
93:
94: public object Value 95: {
96: get { return value; } 97: }
98:
99: public static bool operator !=(LocalValueEntry obj1,LocalValueEntry obj2) 100: {
101: throw new NotImplementedException(); 102: }
103:
104: public static bool operator ==(LocalValueEntry obj1,LocalValueEntry obj2) 105: {
106: throw new NotImplementedException(); 107: }
108:
109: public override bool Equals(object obj) 110: {
111: throw new NotImplementedException(); 112: }
113:
114: public override int GetHashCode() 115: {
116: throw new NotImplementedException(); 117: }
118: }
119: }
120:
DependencyPropertyChangedEventArgs:PropertyChangedCallback(DependencyObject d,DependencyPropertyChangedEventArgs e)的参数,它的第一个参数为该DependencyProperty、第二个参数为原来的值、第三个参数为改变了的值。 1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5:
6: namespace System.Windows 7: {
8: public class DependencyPropertyChangedEventArgs 9: {
10: //第一个参数为该DependencyProperty、第二个参数为原来的值、第三个参数为新值 11: public DependencyPropertyChangedEventArgs(DependencyProperty property,object oldValue,object newValue) 12: {
13: this.Property = property; 14: this.OldValue = oldValue; 15: this.NewValue = newValue; 16: }
17:
18: //注意所有的属性只对外界开放只读操作 19: public object NewValue 20: {
21: get;
22: private set; 23: }
24:
25: public object OldValue 26: {
27: get;
28: private set; 29: }
30:
31: public DependencyProperty Property 32: {
33: get;
34: private set; 35: }
36:
37: public override bool Equals(object obj) 38: {
39: if (!(obj is DependencyPropertyChangedEventArgs)) 40: return false; 41:
42: return Equals((DependencyPropertyChangedEventArgs)obj); 43: }
44:
45: public bool Equals(DependencyPropertyChangedEventArgs args) 46: {
47: return (Property == args.Property && 48: NewValue == args.NewValue &&
49: OldValue == args.OldValue);
50: }
51:
52: public static bool operator !=(DependencyPropertyChangedEventArgs left,DependencyPropertyChangedEventArgs right) 53: {
54: throw new NotImplementedException(); 55: }
56:
57: public static bool operator ==(DependencyPropertyChangedEventArgs left,DependencyPropertyChangedEventArgs right) 58: {
59: throw new NotImplementedException(); 60: }
61:
62: public override int GetHashCode() 63: {
64: throw new NotImplementedException(); 65: }
66:
67: }
68: }
DependencyPropertyKey:构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata,此类只是起到了封装作用。 1:
2: namespace System.Windows 3: {
4: //构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata 5: public sealed class DependencyPropertyKey 6: {
7: internal DependencyPropertyKey (DependencyProperty dependencyProperty) 8: {
9: this.dependencyProperty = dependencyProperty; 10: }
11:
12: private DependencyProperty dependencyProperty; 13: public DependencyProperty DependencyProperty { 14: get { return dependencyProperty; } 15: }
16:
17: public void OverrideMetadata(Type forType,PropertyMetadata typeMetadata) 18: {
19: dependencyProperty.OverrideMetadata (forType,this); 20: }
21: }
22: }
DependencyObjectType:用静态Dictionary<Type,DependencyObjectType>来存储DependencyObjectType,主要有FromSystemType、IsInstanceOfType和IsSubclassOf三个功能。 1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5:
6: namespace System.Windows 7: {
8: public class DependencyObjectType 9: {
10: //键为Type(即OwnerType),值为DependencyObjectType(即ID和systemType)的键值对 11: private static Dictionary<Type,DependencyObjectType> typeMap = new Dictionary<Type,DependencyObjectType>(); 12: private static int current_id; 13:
14: private int id; 15: private Type systemType; 16:
17: //构造函数私有,在FromSystemType里进行构造,初始化id和systemType 18: private DependencyObjectType(int id,Type systemType) 19: {
20: this.id = id; 21: this.systemType = systemType; 22: }
23:
24: //基类型的DependencyObjectType 25: public DependencyObjectType BaseType 26: {
27: get { return DependencyObjectType.FromSystemType(systemType.BaseType); } 28: }
29:
30: public int Id 31: {
32: get { return id; } 33: }
34:
35: public string Name 36: {
37: get { return systemType.Name; } 38: }
39:
40: public Type SystemType 41: {
42: get { return systemType; } 43: }
44:
45: //用静态Dictionary<Type,DependencyObjectType>来存储DependencyObjectType 46: public static DependencyObjectType FromSystemType(Type systemType) 47: {
48: if (typeMap.ContainsKey(systemType)) 49: return typeMap[systemType]; 50:
51: DependencyObjectType dot;
52:
53: typeMap[systemType] = dot = new DependencyObjectType(current_id++,systemType); 54:
55: return dot; 56: }
57:
58: //是否是该DependencyObject的子类实例 59: public bool IsInstanceOfType(DependencyObject d) 60: {
61: return systemType.IsInstanceOfType(d); 62: }
63:
64: //该DependencyObjectType是否是传入DependencyObjectType的子实例 65: public bool IsSubclassOf(DependencyObjectType dependencyObjectType) 66: {
67: return systemType.IsSubclassOf(dependencyObjectType.SystemType); 68: }
69:
70: public override int GetHashCode() 71: {
72: throw new NotImplementedException(); 73: }
74: }
75: }
76:
十四. 回归并统计覆盖率在上面的开发过程中,我们会不断的运行和查看代码通过情况,最后我们也来看一下测试用例的总体通过情况,其实在前面已经运行过很多次了,因为每个功能都要经过”测试代码-功能代码-测试-重构“等步骤。
最后也看一下代码测试覆盖率,代码测试覆盖率对一个系统或者产品来说是一个比较重要的质量指标,可以通过它看出系统的稳定性和可控性。一般在项目的开发中,我们都会以85%~90%的测试代码覆盖率作为达标的参考标准。
由于MONO本身对依赖属性没有那么健全,我们也没有写那么详细的测试代码,中间直接就实现了一些功能,严格地说,所以本文并没有完全遵从正规的测试驱动开发流程。 十五. 简单验证依赖属性系统其实通过上面的测试用例,基本就用不着再单独测试了,但鉴于覆盖率比较低的问题,所以最后我们还是来测试一下刚才构建的依赖属性系统: 1: class Program 2: {
3: static void Main(string[] args) 4: {
5: SimpleDPClass sDPClass = new SimpleDPClass(); 6: sDPClass.SimpleDP = 8;
7: Console.ReadLine();
8: }
9: }
10:
11: public class SimpleDPClass : DependencyObject 12: {
13: public static readonly DependencyProperty SimpleDPProperty = 14: DependencyProperty.Register("SimpleDP",typeof(SimpleDPClass), 15: new PropertyMetadata((double)0.0, 16:
17: new PropertyChangedCallback(OnValueChanged), 18: new CoerceValueCallback(CoerceValue)), 19: new ValidateValueCallback(IsValidValue)); 20:
21: public double SimpleDP 22: {
23: get { return (double)GetValue(SimpleDPProperty); } 24: set { SetValue(SimpleDPProperty,value); } 25: }
26:
27: private static void OnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) 28: {
29: Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}",e.NewValue); 30: }
31:
32: private static object CoerceValue(DependencyObject d,object value) 33: {
34: Console.WriteLine("对值进行限定,强制值: {0}",value); 35: return value; 36: }
37:
38: private static bool IsValidValue(object value) 39: {
40: Console.WriteLine("验证值是否通过,如果返回True表示验证通过,否则会以异常的形式暴露: {0}",value); 41: return true; 42: }
43:
44: }
测试结果: 到处为止,我们这篇文章也宣告结束。 十六. 本文总结本篇承接上一篇WPF基础到企业应用系列7——深入剖析依赖属性的写作风格,对上篇模拟一个WPF依赖属性的实现重现演绎了一遍,上篇是根据微软WPF的BCL源码剖析的,所以这篇我们就详细的研究一下.NET的跨平台版本MONO关于依赖属性系统的实现。在这篇文章中,我只是起到了剖析源码的作用,就像研究微软的BCL一样,不过MONO的代码远没有微软的BCL那么庞大,所以研究和复原起来不是很吃力。如果大家还想继续深入,可以去下载相关源码,也希望大家和我一起交流探讨。 十七. 相关代码下载在文章的最后,和往常一样,我们提供代码的下载,再次温馨提示:这几篇文章最重要的就是下载代码来细细研究,代码里面也添加了比较详细的注释,如果大家有什么问题,也可以直接和我联系,如果有不正确的地方也希望多多海涵并能给我及时反馈,我将感激不尽! 十八.系列进度
云计算专区(http://home.cnblogs.com/group/CloudComputing/),如果大家有什么云计算相关的疑问或话题也可以在里面进行探讨。由于圣殿骑士以后会定格和专注于这几个方向,所以很希望同大家一起交流和进步! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |