依赖属性之“风云再起”
九. DependencyObject实现代码
通过前面的测试用例,DependencyObject类的基本功能已经完成,不过我们要注意几个要点:
1,依赖属性其实终究要DependencyObject和DependencyProperty成对才能算得上真正的DependencyProperty。
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,Dictionary<string,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(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类。
十二. 其他协助类测试代码
这里就简单写一下对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, typeMetadata, 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(double), 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那么庞大,所以研究和复原起来不是很吃力。如果大家还想继续深入,可以去下载相关源码,也希望大家和我一起交流探讨。
十七. 相关代码下载
在文章的最后,和往常一样,我们提供代码的下载,再次温馨提示:这几篇文章最重要的就是下载代码来细细研究,代码里面也添加了比较详细的注释,如果大家有什么问题,也可以直接和我联系,如果有不正确的地方也希望多多海涵并能给我及时反馈,我将感激不尽! 上图就是整个代码包的结构图,下载链接:DependencySystem.rar