依赖属性之“风云再起”四
前端之家收集整理的这篇文章主要介绍了
依赖属性之“风云再起”四,
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
十. PropertyMetadata测试代码
前面我们看到一个依赖
属性的
注册最全的形式是下面这样子的:
public static DependencyProperty Register(string name,
Type propertyType,175);">Type ownerType,175);">PropertyMetadata typeMetadata,175);">ValidateValueCallback validateValueCallback);
第一个参数是该依赖
属性的名字,第二个参数是依赖
属性的类型,第三个参数是该依赖
属性的所有者的类型,第五个参数就是一个验证值的回调委托,那么最使我们感兴趣的还是这个可爱的
PropertyMetadata ,也就是我们接下来要讲的元数据。 提到WPF
属性元数据,大家可能第一想到的是刚才的Property
Metadata,那么这个类到底是怎样的呢?我们应该怎样使用它呢?首先我们看它的构造
函数(我们选参数最多的来讲):
public PropertyMetadata(object defaultValue,
PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback);
其中的第一个参数是默认值,最后两个分别是PropertyChanged(变化
通知)以及Coerce(强制)的两个委托变量,我们在实例化的时候,只需要把这两个委托变量关联到具体的
方法上即可。
事实上,除了Property
Metadata以外,常见的还有 FrameworkProperty
Metadata,UIProperty
Metadata。他们的继承关系是F->U->P。其中以 FrameworkProperty
Metadata参数最多,亦最为复杂。
FrameworkProperty
Metadata的构造
函数提供了很多重载,我们挑选最为复杂的重载来看它到底有哪些参数以及提供了哪些
功能:
public FrameworkPropertyMetadata(object defaultValue,
FrameworkPropertyMetadataOptions flags,
PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback,
bool isAnimationProhibited,
UpdateSourceTrigger defaultUpdateSourceTrigger);
其中第一个参数是默认值,最后两个参数分别是是否允许动画,以及绑定时更新的策略(在Binding当中相信大家并不陌生),这个不详细解释 了。重点看一下里第三、四两个参数,两个 CallBack的委托。结合前面Register的时候提到的ValidateValueCallback共组成三大”金刚“,这三个Callback 分别代表Validate(验证),PropertyChanged(变化
通知)以及Coerce(强制)。当然,作为
Metadata,FrameworkProperty
Metadata只是储存了该依赖
属性的策略信息,WPF
属性系统会根据这些信息来提供
功能并在适 当的时机回调传入的delegate,所以最重要的还是我们定义的这些
方法,通过他们传入委托才能起到真正的作用。
具体Property
Metadata包含哪些成员呢?我们先看微软的Property
Metadata类
在写其他测试用例之前,我们先来创建两个类,第一个类TestDepObj,内部
注册了四个依赖
属性,前三个没有元数据操作,也就是没有
显示声 明并构造元数据类,第四个
添加了一个元数据类,这个元数据类包含了默认值、值改变回调委托、强制值回调委托。第二个类TestSubclass继承自 TestDepObj。
1: class TestDepObj : DependencyObject
2: {
3: public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1",typeof(string),255);">typeof(TestDepObj));
4: readonly DependencyProperty TestProp2 = DependencyProperty.Register("property2",255);">typeof(TestDepObj));
5: readonly DependencyProperty TestProp3 = DependencyProperty.Register("property3",255);">typeof(TestDepObj));
6:
7: readonly DependencyProperty TestProp4 = DependencyProperty.Register("property4",255);">typeof(TestDepObj),255);">new PropertyMetadata("default",changed,coerce));
8:
9: void changed(DependencyObject d,DependencyPropertyChangedEventArgs e) { }
10: object coerce(DependencyObject d,255);">object baseValue) { return baseValue; }
11: }
12:
13: class TestSubclass : TestDepObj
14: {
15: }
大家看到我们在创建Property
Metadata的时候对某些
功能并没有实现,这里我们就通过子类来具体实现,MONO的这种做法想沿袭微 软Property
Metadata、FrameworkProperty
Metadata和UIProperty
Metadata的做法,但是个人觉得 它实现得并不是太好,很多地方感觉很别扭。
//首先我们
自定义一个元数据类,继承自我们刚创建的Property
Metadata类
2: class PropertyMetadataPoker : PropertyMetadata
3: {
4:
5: bool BaseIsSealed
6: {
7: get { return base.IsSealed; }
8: }
9:
10: void CallApply()
11: {
12: OnApply(TestDepObj.TestProp1,255);">typeof(TestDepObj));
13: }
14:
15: 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: 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: void DefaultValues()
3: {
4: //首先看看元数据的默认值
5: PropertyMetadataPoker m = new PropertyMetadataPoker();
6: Assert.AreEqual(null,m.DefaultValue);
7: Assert.AreEqual( 8: Assert.AreEqual( 9: }
我们在WPF和Silverlight中都有过这样的体会:到底什么时候这个依赖
属性不能再
修改了,其实这个操作得归功于OnApply什么时 候触发,我们也可以
调用IsSealed来查看,那么这里我们就先写测试
代码。第一段
代码直接
显示调用CallApply
方法进行密封;第二段
代码则是通 过Override
Metadata操作后内部
调用的CallApply;第三段
代码是通过AddOwner操作中
调用的CallApply;最后一段代 码通过
调用DependencyProperty.Register时传入元数据,在其内部
调用CallApply。
@H_
502_384@void IsSealed()
4: //测试元数据是否密封,这个很重要,因为封闭之后就不能修改了,除非用OverrideMetadata或者AddOwner
5: PropertyMetadataPoker m;
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);
// 直接 OverrideMetadata
16: m = new PropertyMetadataPoker();
17: TestDepObj.TestProp1.OverrideMetadata(typeof(TestSubclass),m);
18: Assert.IsTrue(m.BaseIsSealed);
20: Console.WriteLine(3);
21: // 调用 DependencyProperty.AddOwner,通过这种方式 OverrideMetadata
22: m = new PropertyMetadataPoker();
23: TestDepObj.TestProp2.AddOwner( 24: Assert.IsTrue(m.BaseIsSealed);
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。
2:
void TestAddOwnerResult()
3: {
4: //测试AddOwner后的DependencyProperty是否和原来的DependencyProperty是同一个DependencyProperty
5: PropertyMetadataPoker m = 6: DependencyProperty p = TestDepObj.TestProp3.AddOwner( 7:
8: //结果是同一个DependencyProperty
9: Assert.AreSame(p,TestDepObj.TestProp3);
10: }
下面这个测试用例是首先实例化元数据并作为
注册依赖
属性时的参数传入,大家都知道此时如果想
修改元数据,可以通过AddOwner或者Override
Metadata,如果直接赋值,会抛出
错误,因为元数据已经密封。
2: [ExpectedException(
typeof(InvalidOperationException))]
void ModifyAfterSealed1()
4: {
5: //首先实例化元数据并注册依赖属性时作为参数传入
6: PropertyMetadataPoker m = new PropertyMetadataPoker();
7: DependencyProperty.Register("p1",m);
8: Assert.IsTrue(m.BaseIsSealed);
10: //由于元数据已密封,所以抛出如下错误信息:Cannot change Metadata once it has been applied to a property
11: m.CoerceValueCallback = null;
12: }
这个和上面的那个测试用例基本一样,只不过把CoerceValueCallback换成了PropertyChangedCallback
2: [ExpectedException(
3: void ModifyAfterSealed2()
4: {
5: 6: PropertyMetadataPoker m = 7: DependencyProperty.Register("p2",96);"> 8: Assert.IsTrue(m.BaseIsSealed);
10: 11: m.PropertyChangedCallback = 12: }
下面这个测试用例也和上面的两个测试用例类似,它是
修改元数据的DefaultValue
2: [ExpectedException(
3: void ModifyAfterSealed3()
4: {
5: 6: PropertyMetadataPoker m = 7: DependencyProperty.Register("p3",96);"> 8: Assert.IsTrue(m.BaseIsSealed);
10: 11: m.DefaultValue = "hi";
12: }
通过前面的测试用例,大家可能都会发现有一个Merge这个
方法,它在什么时候
调用呢?其实它在Override
Metadata和AddOwner操作中都会
调用,在
DependencyProperty中的Register也会
显示调用一次。我们需要注意的是:在元数据密封了以后就会抛出
错误。
@H_502_384@void TestMerge()
//需要注意的是:在元数据密封了以后就会抛出错误
6: m.CallMerge(TestDepObj.TestProp4.GetMetadata(typeof(TestDepObj)),TestDepObj.TestProp4);
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( 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的
@H_502_384@typeof(ArgumentException))]
void TestSetDefaultToUnsetValue()
//默认值是不能被设置成Unset的
6: PropertyMetadata m = new PropertyMetadata();
7: m.DefaultValue = DependencyProperty.UnsetValue;
8: }
10: [Test]
11: [ExpectedException(typeof(ArgumentException))]
12: void TestInitDefaultToUnsetValue()
13: {
14: //默认值是不能被设置成Unset的
15: new PropertyMetadata(DependencyProperty.UnsetValue);
16: }
通过前面的多个测试用例,其实已经包含了Property
Metadata的基本
功能,那我们接下来就看一下Property
Metadata的内部设计和实现。
十一. PropertyMetadata实现代码
MONO的Property
Metadata类要比微软的Property
Metadata类简单很多,不过我们也需要注意一下几点:
1,元数据类包含哪些成员以及有几个构造
函数重载?因为这些直接关系到外部的
调用。
2,大家要注意ValidateValueCallback不是Property
Metadata的成员,所以在Property
Metadata的构造
函数中不要把它作为参数传入。
3,注意OnApply
函数,因为
调用它之后就不能
修改元数据的成员,只有通过Override
Metadata和AddOwner间接实现,如果大家想知道到底这个元数据有没有被密封,可以
调用IsSealed
属性来查看,这个
功能我们也会经常用到。
4,元数据类中提供了Merge的
功能,用来方便合并
父类和子类的元数据。
@H_502_384@namespace System.Windows
//依赖属性三大回调委托:PropertyChangedCallback、CoerceValueCallback和ValidateValueCallback
delegate void PropertyChangedCallback(DependencyObject d,DependencyPropertyChangedEventArgs e);
object CoerceValueCallback(DependencyObject d,255);">object baseValue);
6: bool ValidateValueCallback(object value);
8: class PropertyMetadata
9: {
private object defaultValue;
11: bool isSealed;
12: private PropertyChangedCallback propertyChangedCallback;
13: private CoerceValueCallback coerceValueCallback;
15: //返回该元数据是否已密封
16: bool IsSealed
17: {
18: get { return isSealed; }
19: }
20:
21: //获取和设置元数据默认值
22: 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: 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: "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: "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)
63: {
64: }
65:
66: public PropertyMetadata(object defaultValue)
67: : this(defaultValue,255);">null)
68: {
69: }
70:
71: public PropertyMetadata(PropertyChangedCallback propertyChangedCallback)
72: : null)
73: {
74: }
75:
76: object defaultValue,PropertyChangedCallback propertyChangedCallback)
77: : null)
78: {
79: }
80:
81: 82: {
83: if (defaultValue == DependencyProperty.UnsetValue)
84: "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:
@H_528_1301@ 92: //合并元数据
93: virtual 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: 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类。
原文链接:https://www.f2er.com/javaschema/287439.html