依赖属性之“风云再起”四
发布时间:2020-05-23 07:08:58 所属栏目:程序设计 来源:互联网
导读:十. PropertyMetadata测试代码 前面我们看到一个依赖属性的注册最全的形式是下面这样子的: public static DependencyProperty Register(string name, Type propertyType,
十. PropertyMetadata测试代码前面我们看到一个依赖属性的注册最全的形式是下面这样子的:public static DependencyProperty Register(string name,第一个参数是该依赖属性的名字,第二个参数是依赖属性的类型,第三个参数是该依赖属性的所有者的类型,第五个参数就是一个验证值的回调委托,那么最使我们感兴趣的还是这个可爱的 PropertyMetadata ,也就是我们接下来要讲的元数据。 提到WPF属性元数据,大家可能第一想到的是刚才的PropertyMetadata,那么这个类到底是怎样的呢?我们应该怎样使用它呢?首先我们看它的构造函数(我们选参数最多的来讲): public PropertyMetadata(object defaultValue,其中的第一个参数是默认值,最后两个分别是PropertyChanged(变化通知)以及Coerce(强制)的两个委托变量,我们在实例化的时候,只需要把这两个委托变量关联到具体的方法上即可。 事实上,除了PropertyMetadata以外,常见的还有 FrameworkPropertyMetadata,UIPropertyMetadata。他们的继承关系是F->U->P。其中以 FrameworkPropertyMetadata参数最多,亦最为复杂。 FrameworkPropertyMetadata的构造函数提供了很多重载,我们挑选最为复杂的重载来看它到底有哪些参数以及提供了哪些功能: public FrameworkPropertyMetadata(object defaultValue,其中第一个参数是默认值,最后两个参数分别是是否允许动画,以及绑定时更新的策略(在Binding当中相信大家并不陌生),这个不详细解释 了。重点看一下里第三、四两个参数,两个 CallBack的委托。结合前面Register的时候提到的ValidateValueCallback共组成三大”金刚“,这三个Callback 分别代表Validate(验证),PropertyChanged(变化通知)以及Coerce(强制)。当然,作为 Metadata,FrameworkPropertyMetadata只是储存了该依赖属性的策略信息,WPF属性系统会根据这些信息来提供功能并在适 当的时机回调传入的delegate,所以最重要的还是我们定义的这些方法,通过他们传入委托才能起到真正的作用。 具体PropertyMetadata包含哪些成员呢?我们先看微软的PropertyMetadata类
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: }大家看到我们在创建PropertyMetadata的时候对某些功能并没有实现,这里我们就通过子类来具体实现,MONO的这种做法想沿袭微 软PropertyMetadata、FrameworkPropertyMetadata和UIPropertyMetadata的做法,但是个人觉得 它实现得并不是太好,很多地方感觉很别扭。 //首先我们自定义一个元数据类,继承自我们刚创建的PropertyMetadata类 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方法进行密封;第二段代码则是通 过OverrideMetadata操作后内部调用的CallApply;第三段代码是通过AddOwner操作中调用的CallApply;最后一段代 码通过调用DependencyProperty.Register时传入元数据,在其内部调用CallApply。 void IsSealed()4: //测试元数据是否密封,这个很重要,因为封闭之后就不能修改了,除非用OverrideMetadata或者AddOwner5: 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);// 直接 OverrideMetadata16: m = new PropertyMetadataPoker();17: TestDepObj.TestProp1.OverrideMetadata(typeof(TestSubclass),m);18: Assert.IsTrue(m.BaseIsSealed);20: Console.WriteLine(3);21: // 调用 DependencyProperty.AddOwner,通过这种方式 OverrideMetadata22: m = new PropertyMetadataPoker();23: TestDepObj.TestProp2.AddOwner(24: Assert.IsTrue(m.BaseIsSealed);26: Console.WriteLine(4);27: // 最后,调用DependencyProperty.Register时传入元数据28: m = new PropertyMetadataPoker(); 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这个方法,它在什么时候调用呢?其实它在OverrideMetadata和AddOwner操作中都会调用,在 DependencyProperty中的Register也会显示调用一次。我们需要注意的是:在元数据密封了以后就会抛出错误。 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( //依赖属性三大回调委托: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: |
