DependencyObject实现代码
通过前面的测试用例DependencyObject类的基本功能已经完成不过我们要注意几个要点1依赖属性其实终究要DependencyObject和DependencyProperty成对才能算得上真正的DependencyProperty2不管是Register、RegisterAttached、RegisterAttachedReadOnly还是RegisterReadOnly操作我们都要通过DependencyObject来操作DependencyProperty的值也就是通过DependencyObject这个外部接口来操作DependencyProperty只负责注册和内部处理不负责外部接口。3在DependencyObject中提供了几个操作LocalValue的接口的接口其中包括ReadLocalValue、GetLocalValueEnumerator、CoerceValue和ClearValue等。4在注册注册依赖属性时实质是关联DependencyObject的propertyDeclarations它是一个DictionaryType,Dictionarystring,DependencyProperty类型但是在register代码中并没有完全关联起来我也比较纳闷所以这点还希望和大家一起探讨微软的BCL并没有这么实现。1: using System.Collections.Generic;2: //using System.Windows.Threading;3:4: namespace System.Windows5: {6: public class DependencyObject7: {8: //依赖属性其实终究要DependencyObject和DependencyProperty成对才能算得上真正的DependencyProperty9: private static DictionaryType,Dictionarystring,DependencyProperty propertyDeclarations new DictionaryType,Dictionarystring,DependencyProperty();10: //该依赖属性的键值对键为DependencyProperty值为object11: private DictionaryDependencyProperty,object properties new DictionaryDependencyProperty,object();12:13: //是否已密封没有实现DependencyObject层次的IsSealed判断14: public bool IsSealed {15: get { return false; }16: }17:18: //获取该DependencyObject的DependencyObjectType19: 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: else103: 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: //这里的注册实质是关联propertyDeclarations118: internal static void register(Type t, DependencyProperty dp)119: {120: if (!propertyDeclarations.ContainsKey (t))121: propertyDeclarations[t] new Dictionarystring,DependencyProperty();122: Dictionarystring,DependencyProperty typeDeclarations propertyDeclarations[t];123: if (!typeDeclarations.ContainsKey(dp.Name))124: {125: typeDeclarations[dp.Name] dp;126: //这里仍然有一些问题期待各位共同探讨解决127: }128: else129: 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以外常见的还有FrameworkPropertyMetadataUIPropertyMetadata。他们的继承关系是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强制。当然作为 MetadataFrameworkPropertyMetadata只是储存了该依赖属性的策略信息WPF属性系统会根据这些信息来提供功能并在适当的时机回调传入的delegate所以最重要的还是我们定义的这些方法通过他们传入委托才能起到真正的作用。具体PropertyMetadata包含哪些成员呢我们先看微软的PropertyMetadata类在写其他测试用例之前我们先来创建两个类第一个类TestDepObj内部注册了四个依赖属性前三个没有元数据操作也就是没有显示声明并构造元数据类第四个添加了一个元数据类这个元数据类包含了默认值、值改变回调委托、强制值回调委托。第二个类TestSubclass继承自TestDepObj。1: class TestDepObj : DependencyObject2: {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 : TestDepObj14: {15: }大家看到我们在创建PropertyMetadata的时候对某些功能并没有实现这里我们就通过子类来具体实现MONO的这种做法想沿袭微软PropertyMetadata、FrameworkPropertyMetadata和UIPropertyMetadata的做法但是个人觉得它实现得并不是太好很多地方感觉很别扭。1: //首先我们自定义一个元数据类继承自我们刚创建的PropertyMetadata类2: public class PropertyMetadataPoker : PropertyMetadata3: {4:5: public bool BaseIsSealed6: {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或者AddOwner5: 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: // 直接 OverrideMetadata16: m new PropertyMetadataPoker();17: TestDepObj.TestProp1.OverrideMetadata(typeof(TestSubclass), m);18: Assert.IsTrue(m.BaseIsSealed);19:20: Console.WriteLine(3);21: // 调用 DependencyProperty.AddOwner, 通过这种方式 OverrideMetadata22: 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是同一个DependencyProperty5: PropertyMetadataPoker m new PropertyMetadataPoker();6: DependencyProperty p TestDepObj.TestProp3.AddOwner(typeof(TestSubclass), m);7:8: //结果是同一个DependencyProperty9: 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 property11: m.CoerceValueCallback null;12: }这个和上面的那个测试用例基本一样只不过把CoerceValueCallback换成了PropertyChangedCallback1: [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 property11: m.PropertyChangedCallback null;12: }下面这个测试用例也和上面的两个测试用例类似它是修改元数据的DefaultValue1: [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 property11: 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的内部设计和实现。