依赖属性(DependencyProperty)

前端之家收集整理的这篇文章主要介绍了依赖属性(DependencyProperty)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

一站式WPF--依赖属性(DependencyProperty)一

Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性功能,这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持属性称为依赖项属性

  这段是MSDN上对依赖属性(DependencyProperty)的描述。主要介绍了两个方面,WPF中提供了可用于扩展CLR属性的服务;被这个服务支持属性称为依赖属性

  单看描述,云里雾里的,了解一个知识,首先要知道它产生的背景和为什么要有它,那么WPF引入依赖属性是为了解决什么问题呢?

属性说起

  属性是我们很熟悉的,封装类的字段,表示类的状态,编译后被转化为get_,set_方法,可以被类或结构等使用。 一个常见的属性如下:

@H_404_24@ 1: public class NormalObject @H_404_24@ 2: { @H_404_24@ 3: private string _unUsedField; @H_404_24@ 4: @H_404_24@ 5: private string _name; @H_404_24@ 6: public string Name @H_404_24@ 7: { @H_404_24@ 8: get @H_404_24@ 9: { @H_404_24@ 10: return _name; @H_404_24@ 11: } @H_404_24@ 12: set @H_404_24@ 13: { @H_404_24@ 14: _name = value; @H_404_24@ 15: } @H_404_24@ 16: } @H_404_24@ 17: }

  在面向对象的世界里,属性大量存在,比如Button,就大约定义了70-80个属性来描述其状态。那么属性的不足又在哪里呢?

  当然,所谓的不足,要针对具体环境来说。拿Button来讲,它的继承树是 Button->ButtonBase->ContentControl->Control->FrameworkElement->UIElement->Visual->DependencyObject->…

  每次继承,父类的私有字段都被继承下来。当然,这个继承是有意思的,不过以Button来说,大多数属性并没有被修改,仍然保持着父类定义时的 默认值。通常情况,在整个Button对象的生命周期里,也只有少部分属性修改,大多数属性一直保持着初始值。每个字段,都需要占用4K等不等的内存, 这里,就出现了期望可以优化的地方:

  • 因继承而带来的对象膨胀。每次继承,父类的字段都被继承,这样,继承树的低端对象不可避免的膨胀。
  • 大多数字段并没有被修改,一直保持着构造时的默认值,可否把这些字段从对象中剥离开来,减少对象的体积。

依赖属性的原型

  根据前面提出的需求,依赖属性就应运而生了。一个简单的依赖属性的原型如下:

DependencyProperty:

1: public class DependencyProperty @H_404_24@ 3: internal static Dictionary<object,DependencyProperty> RegisteredDps = new Dictionary<object,DependencyProperty>(); @H_404_24@ 4: internal string Name; @H_404_24@ 5: internal object Value; @H_404_24@ 6: internal object HashCode; @H_404_24@ 7: @H_404_24@ 8: private DependencyProperty(string name,Type propertyName,Type ownerType,object defaultValue) @H_404_24@ 9: { @H_404_24@ 10: this.Name = name; @H_404_24@ 11: this.Value = defaultValue; @H_404_24@ 12: this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode(); @H_404_24@ 13: } @H_404_24@ 14: @H_404_24@ 15: public static DependencyProperty Register(string name,Type propertyType,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 16: { @H_404_24@ 17: DependencyProperty dp = new DependencyProperty(name,propertyType,ownerType,defaultValue); @H_404_24@ 18: RegisteredDps.Add(dp.HashCode,dp); @H_404_24@ 19: return dp; @H_404_24@ 20: } @H_404_24@ 21: } @H_404_24@ 22:

DependencyObject:

1: public class DependencyObject @H_404_24@ 5: public static readonly DependencyProperty NameProperty = @H_404_24@ 6: DependencyProperty.Register("Name",typeof(string),typeof(DependencyObject),string.Empty); @H_404_24@ 8: public object GetValue(DependencyProperty dp) @H_404_24@ 10: return DependencyProperty.RegisteredDps[dp.HashCode].Value; @H_404_24@ 11: } @H_404_24@ 12: @H_404_24@ 13: public void SetValue(DependencyProperty dp,object value) @H_404_24@ 14: { @H_404_24@ 15: DependencyProperty.RegisteredDps[dp.HashCode].Value = value; @H_404_24@ 16: } @H_404_24@ 17: @H_404_24@ 18: public string Name @H_404_24@ 19: { @H_404_24@ 20: get @H_404_24@ 21: { @H_404_24@ 22: return (string)GetValue(NameProperty); @H_404_24@ 23: } @H_404_24@ 24: set @H_404_24@ 25: { @H_404_24@ 26: SetValue(NameProperty,value); @H_404_24@ 27: } @H_404_24@ 28: } @H_404_24@ 29: } @H_404_24@ 30:

  这里,首先定义了依赖属性DependencyProperty,它里面存储前面我们提到希望抽出来的字段。DP内部维护了一个全局的Map用 来储存所有的DP,对外暴露了一个Register方法用来注册新的DP。当然,为了保证在Map中键值唯一,注册时需要根据传入的名字和注册类的的 HashCode取异或来生成Key。这里最关键的就是最后一个参数,设置了这个DP的默认值。

  然后定义了DependencyObject来使用DP。首先使用DependencyProperty.Register方法注册了一个新的 DP(NameProperty),然后提供了GetValue和SetValue两个方法来操作DP。最后,类似前面例子中的 NormalObject,同样定义了一个属性Name,和NormalObject的区别是,实际的值不是用字段来保存在 DependencyObject中的,而是保存在NameProperty这个DP中,通过GetValue和SetValue来完成属性的赋值取值操 作。

  当然,作为一个例子,为了简洁,很多情况没有考虑,现在来测试一下是否解决了前面的问题。

  新建两个对象,NormalObject和DependencyObject,在VS下打开SOS查看:

.load sos extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded !DumpHeap -stat -type NormalObject total 1 objects Statistics: MT    Count    TotalSize Class Name 009e30d0        1           16 DPDemonstration.NormalObject Total 1 objects !DumpHeap -stat -type DependencyObject total 1 objects Statistics: MT    Count    TotalSize Class Name 009e31a0        1           12 DPDemonstration.DependencyObject Total 1 objects

  这里在对象中分别建立了一个_unUsedField的字段,.Net的GC要求对象的最小Size为12字节。如果对象的Size不足12字 节,则会自动补齐。默认的Object对象占用8字节,Syncblk(4字节)以及TypeHandle(4字节),为了演示方便,加入了一个 _unUsedField(4字节)来补齐。

  这里,DependencyObject相比NormalObject,减少了_name的储存空间4字节。

再进一步

  万里长征第一步,这个想法可以解决我们希望的问题,这个做法还不能让人接受。在这个实现中,所有DependencyObject共用一个DP,这个可以理解,但修改一个对象的属性后,所有对象的属性相当于都被修改了,这个就太可笑了。

  所以对象属性一旦被修改,这个还是要维护在自己当中的,修改一下前面的DependencyObject,引入一个有效(Effective)的概念。

改进的DependencyObject,加入了_effectiveValues:

3: private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>(); @H_404_24@ 10: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index); @H_404_24@ 11: if (effectiveValue.PropertyIndex != 0) @H_404_24@ 12: { @H_404_24@ 13: return effectiveValue.Value; @H_404_24@ 14: } @H_404_24@ 15: else @H_404_24@ 16: { @H_404_24@ 17: return DependencyProperty.RegisteredDps[dp.HashCode].Value; @H_404_24@ 18: } @H_404_24@ 19: } @H_404_24@ 20: @H_404_24@ 21: public void SetValue(DependencyProperty dp,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 22: { @H_404_24@ 23: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index); @H_404_24@ 24: if (effectiveValue.PropertyIndex != 0) @H_404_24@ 26: effectiveValue.Value = value; @H_404_24@ 28: else @H_404_24@ 29: { @H_404_24@ 30: effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index,Value = value }; @H_404_24@ 31: _effectiveValues.Add(effectiveValue); @H_404_24@ 32: } @H_404_24@ 33: } @H_404_24@ 34: @H_404_24@ 35: public string Name @H_404_24@ 36: { @H_404_24@ 37: get @H_404_24@ 38: { @H_404_24@ 39: return (string)GetValue(NameProperty); @H_404_24@ 40: } @H_404_24@ 41: set @H_404_24@ 42: { @H_404_24@ 43: SetValue(NameProperty,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 44: } @H_404_24@ 45: } @H_404_24@ 46: }

新引进的EffectiveValueEntry:

1: internal struct EffectiveValueEntry @H_404_24@ 3: internal int PropertyIndex { get; set; } @H_404_24@ 5: internal object Value { get; set; } @H_404_24@ 6: }

改进的DependencyProperty,加入了ProperyIndex:

3: private static int globalIndex = 0; @H_404_24@ 4: internal static Dictionary<object,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 5: internal string Name; @H_404_24@ 6: internal object Value; @H_404_24@ 7: internal int Index; @H_404_24@ 8: internal object HashCode; @H_404_24@ 9: @H_404_24@ 10: private DependencyProperty(string name,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 11: { @H_404_24@ 12: this.Name = name; @H_404_24@ 13: this.Value = defaultValue; @H_404_24@ 14: this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode(); @H_404_24@ 15: } @H_404_24@ 16: @H_404_24@ 17: public static DependencyProperty Register(string name,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 18: { @H_404_24@ 19: DependencyProperty dp = new DependencyProperty(name,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 20: globalIndex++; @H_404_24@ 21: dp.Index = globalIndex; @H_404_24@ 22: RegisteredDps.Add(dp.HashCode,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 23: return dp; @H_404_24@ 24: } @H_404_24@ 25: }

  在DependencyObject加入了一个_effectiveValues,就是把所有修改过的DP都保存在EffectiveValueEntry里,这样,就可以达到只保存修改属性,未修改过的属性仍然读取DP的默认值,优化了属性的储存。

更进一步的发展

  到目前为止,从属性到依赖属性的改造一切顺利。但随着实际的使用,又一个问题暴露出来了。使用继承,子类可以重写父类的字段,换句话说,这个默认值应该是可以子类化的。那么怎么处理,子类重新注册一个DP,传入新的默认值?

  当然,不会实现的这么丑陋。同一个DP,要想支持不同的默认值,那么内部就要维护一个对应不同DependencyObjectType的一个List,可以根据传入的DependencyObject的类型来读取它对应的默认值。

  DP内需要维护一个自描述的List,按照微软的命名规则,添加新的类型属性元数据(PropertyMetadata):

1: public class PropertyMetadata @H_404_24@ 3: public Type Type { get; set; } @H_404_24@ 4: public object Value { get; set; } @H_404_24@ 5: @H_404_24@ 6: public PropertyMetadata(object defaultValue) @H_404_24@ 8: this.Value = defaultValue; @H_404_24@ 9: } @H_404_24@ 10: }

对应修改DependencyProperty

9: private List<PropertyMetadata> _MetadataMap = new List<PropertyMetadata>(); @H_404_24@ 10: private PropertyMetadata _defaultMetadata; @H_404_24@ 11: @H_404_24@ 12: private DependencyProperty(string name,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 13: { @H_404_24@ 14: this.Name = name; @H_404_24@ 15: this.Value = defaultValue; @H_404_24@ 16: this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode(); @H_404_24@ 18: PropertyMetadata Metadata = new PropertyMetadata(defaultValue) { Type = ownerType }; @H_404_24@ 19: _MetadataMap.Add(Metadata); @H_404_24@ 20: _defaultMetadata = Metadata; @H_404_24@ 21: } @H_404_24@ 22: @H_404_24@ 23: public static DependencyProperty Register(string name,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 24: { @H_404_24@ 25: DependencyProperty dp = new DependencyProperty(name,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 26: globalIndex++; @H_404_24@ 27: dp.Index = globalIndex; @H_404_24@ 28: RegisteredDps.Add(dp.HashCode,monospace; border-right-style:none; border-left-style:none; background-color:#f4f4f4; text-align:left; border-bottom-style:none"> 29: return dp; @H_404_24@ 30: } @H_404_24@ 31: @H_404_24@ 32: public void OverrideMetadata(Type forType,PropertyMetadata Metadata) @H_404_24@ 33: { @H_404_24@ 34: Metadata.Type = forType; @H_404_24@ 35: _MetadataMap.Add(Metadata); @H_404_24@ 36: } @H_404_24@ 37: @H_404_24@ 38: public PropertyMetadata GetMetadata(Type type) @H_404_24@ 39: { @H_404_24@ 40: PropertyMetadata medatata = _MetadataMap.FirstOrDefault((i) => i.Type == type) ?? @H_404_24@ 41: _MetadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type)); @H_404_24@ 42: if (medatata == null) @H_404_24@ 43: { @H_404_24@ 44: medatata = _defaultMetadata; @H_404_24@ 45: } @H_404_24@ 46: return medatata; @H_404_24@ 47: } @H_404_24@ 48: }

修改DenpendencyObject中的GetValue并更改_effectiveValues,为了简洁去掉了NameProperty以及SetValue.

3: private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>(); @H_404_24@ 5: public object GetValue(DependencyProperty dp) @H_404_24@ 6: { @H_404_24@ 7: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index); @H_404_24@ 8: if (effectiveValue.PropertyIndex != 0) @H_404_24@ 9: { @H_404_24@ 10: return effectiveValue.Value; @H_404_24@ 11: } @H_404_24@ 12: else @H_404_24@ 13: { @H_404_24@ 14: PropertyMetadata Metadata; @H_404_24@ 15: Metadata = DependencyProperty.RegisteredDps[dp.HashCode].GetMetadata(this.GetType()); @H_404_24@ 16: return Metadata.Value; @H_404_24@ 17: } @H_404_24@ 18: } @H_404_24@ 19: }

这样,就可以定义一个SubDependencyObject,调用OverrideMedata向DP的_MetadataMap中加入新的Metadata。

1: public class SubDependencyObject : DependencyObject @H_404_24@ 3: static SubDependencyObject() @H_404_24@ 4: { @H_404_24@ 5: NameProperty.OverrideMetadata(typeof(SubDependencyObject),new PropertyMetadata("SubName")); @H_404_24@ 6: } @H_404_24@ 7: }

  创建一个DependencyObject以及SubDependencyObject,可以发现,Name的值已经被改为”SubName” 了。当然,实际DP中对Metadata的操作比较繁琐,当子类调用OverrideMetadata时会涉及到Merge操作,把新的Metadata 与父类的合二为一。并且在GetMetadata中,要取得自己或者是与它最近的父类Metadata,为了可以获得最近的父类,WPF引入了一个 DependencyObjectType的类,在构造时传入BaseType=this.base.GetType(),这里为了简单,忽略不计。

WPF对依赖属性的扩展

  前面的例子里,依据优化储存的思想,我们打造了一个DependencyProperty。当然,有了这样一门利器,不好好打磨打磨真是对不起它,WPF在这个基础上对DP进行了扩展,使其更加的强大。

  对通常的CLR属性来说,在Set中加入一些逻辑判断是很正常的,当然也可以在Set中发出一些事件或者更改其他一些属性。那么依赖属性,它对此又有什么支持呢?

  顺水推舟,WPF在DP的PropertyMedata中加入了PropertyChangedCallback以及 CoerceValueCallback等。这些Delegate可以在构造PropertyMetadata时传入,在SetValue过程中,会取得 对应的PropertyMetadata,然后回调PropertyChangedCallback。这个PropertyMetadata可以在构建 DP时传入,也可以在子类调用OverrideMetadata时传入,这就保证了同一个DP不同的DependencyObject可以有不同的应用。 WPF对此进行了很多扩展,定义了一套属性赋值的规则,包括计算(calculate)、限制(Coerce)、验证(Validate)等等。

  当然,这些扩展说开了会很多,WPF对此也进行了精巧的设计,这也就是我们开篇提到的WPF提供了一组服务,用于扩展CLR属性

属性

  发展都是由需求来推动的,在WPF的实现过程中,又产生了这样一个需要:

  WPF是原生支持动画的,一个DP属性,比如Button的Width,你可以加入动画使他在1秒内由100变为200,在动画结束后,又希望 它能恢复原来的属性值。同理,你可以在XAML表达式中对属性进行赋值,当表达式失效时同样期望他恢复成原来的属性值。这个需求来自于,对同一个属性的赋 值可能发生在不同的场合,当对象状态改变时属性也要发生相应的变化,这里就产生了两个需要:

  同一个属性有多个值,这个对CLR属性来说有些难为它了。但是对DP来说却很简单,本来DP的值就是保存在我们定义的EffectiveValueEntry中的,以前是保存一个Value,现在定义多个值就可以了。

3: private object _value; @H_404_24@ 5: internal int PropertyIndex { get; set; } @H_404_24@ 6: @H_404_24@ 7: internal object Value @H_404_24@ 8: { @H_404_24@ 9: get @H_404_24@ 10: { @H_404_24@ 11: return _value; @H_404_24@ 12: } @H_404_24@ 13: set @H_404_24@ 14: { @H_404_24@ 15: _value = value; @H_404_24@ 16: } @H_404_24@ 17: } @H_404_24@ 18: @H_404_24@ 19: internal ModifiedValue ModifiedValue @H_404_24@ 20: { @H_404_24@ 21: get @H_404_24@ 22: { @H_404_24@ 23: if (this._value != null) @H_404_24@ 24: { @H_404_24@ 25: return (this._value as ModifiedValue); @H_404_24@ 26: } @H_404_24@ 27: return null; @H_404_24@ 28: } @H_404_24@ 29: } @H_404_24@ 30: }

对应的ModifiedValue:

1: internal class ModifiedValue @H_404_24@ 3: internal object AnimatedValue { get; set; } @H_404_24@ 4: internal object BaseValue { get; set; } @H_404_24@ 5: internal object CoercedValue { get; set; } @H_404_24@ 6: internal object ExpressionValue { get; set; } @H_404_24@ 7: }

  当属性没有被修改过,ModifiedValue为空,当修改过后,ModifiedValue被赋值。这里EffectiveValueEntry定义了很多方法如SetExpressionValue(object value),SetAnimatedValue(object value)等来向ModifiedValue中写入对应值;并且EffectiveValueEntry提供了IsAnimated,IsExpression等属性来表示当前的状态。当然,这个赋值的操作比较复杂,这个优先级分两大类:一 ModifiedValue中各属性的优先级;二 对于ExpressionValue来说,它又有自己的优先级,Local>Style>Template…这里就不详细解释了。

依赖属性的优点

  回过头来,总结一下依赖属性的优点:

总结

  借助于依赖属性,WPF提供了强大的属性系统,可以支持数据绑定、样式、动画、附加属性功能。这篇文章主要是简略的实现了一个从属性到依赖属性的发展过程,当然,具体和WPF的实现还有偏差,希望朋友们都能抓住这个主要的脉络,更好的去玩转它。

  除了依赖属性的实现,还有一些很重要的部分,比如借助于依赖属性提出的附加属性,以及如何利用依赖属性来更好的设计实现程序,使用依赖属性有哪些要注意的地方。呵呵,那就,下篇吧。


示例程序下载

链接地址http://www.cnblogs.com/mrchenzh/archive/2012/05/09/2491269.html

原文链接:https://www.f2er.com/javaschema/286043.html

猜你在找的设计模式相关文章