作者:周永恒
出处:http://www.cnblogs.com/Zhouyongh
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能,这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。
这段是MSDN上对依赖属性(DependencyProperty)的描述。主要介绍了两个方面,WPF中提供了可用于扩展CLR属性的服务;被这个服务支持的属性称为依赖属性。
单看描述,云里雾里的,了解一个知识,首先要知道它产生的背景和为什么要有它,那么WPF引入依赖属性是为了解决什么问题呢?
从属性说起
属性是我们很熟悉的,封装类的字段,表示类的状态,编译后被转化为get_,set_方法,可以被类或结构等使用。 一个常见的属性如下:
1: public class normalObject
2: {
3: private string _unUsedFIEld;
4:
5: private string _name;
6: public string name
7: {
8: get
9: {
10: return _name;
11: }
12: set
13: {
14: _name = value;
15: }
16: }
17: }
在面向对象的世界里,属性大量存在,比如button,就大约定义了70-80个属性来描述其状态。那么属性的不足又在哪里呢?
当然,所谓的不足,要针对具体环境来说。拿button来讲,它的继承树是button->buttonBase->ContentControl->Control->FrameworkElement->UIElement->Visual->DependencyObject->…
每次继承,父类的私有字段都被继承下来。当然,这个继承是有意思的,不过以button来说,大多数属性并没有被修改,仍然保持着父类定义时的默认值。通常情况,在整个button对象的生命周期里,也只有少部分属性被修改,大多数属性一直保持着初始值。每个字段,都需要占用4K等不等的内存,这里,就出现了期望可以优化的地方:
因继承而带来的对象膨胀。每次继承,父类的字段都被继承,这样,继承树的低端对象不可避免的膨胀。 大多数字段并没有被修改,一直保持着构造时的默认值,可否把这些字段从对象中剥离开来,减少对象的体积。 依赖属性的原型根据前面提出的需求,依赖属性就应运而生了。一个简单的依赖属性的原型如下:
DependencyProperty:
1: public class DependencyProperty3: internal static Dictionary<object,DependencyProperty> RegisteredDps = new Dictionary<object,DependencyProperty>();
4: internal string name;
5: internal object Value;
6: internal object HashCode;
7:
8: private DependencyProperty(string name,Type propertyname,Type ownerType,object defaultValue)
9: {
10: this.name = name;
11: this.Value = defaultValue;
12: this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
13: }
14:
15: public static DependencyProperty Register(string name,Type propertyType,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 16: {
17: DependencyProperty dp = new DependencyProperty(name,propertyType,ownerType,defaultValue);
18: RegisteredDps.Add(dp.HashCode,dp);
19: return dp;
20: }
21: }
22:
DependencyObject:
1: public class DependencyObject5: public static Readonly DependencyProperty nameProperty =
6: DependencyProperty.Register("name",typeof(string),typeof(DependencyObject),string.Empty);
8: public object GetValue(DependencyProperty dp)
10: return DependencyProperty.RegisteredDps[dp.HashCode].Value;
11: }
12:
13: public voID SetValue(DependencyProperty dp,object value)
14: {
15: DependencyProperty.RegisteredDps[dp.HashCode].Value = value;
16: }
17:
18: public string name
19: {
20: get
21: {
22: return (string)GetValue(nameProperty);
@H_227_419@ 23: }
24: set
25: {
26: SetValue(nameProperty,value);
27: }
28: }
29: }
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 sosextension C:\windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded!DumpHeap -stat -type normalObjecttotal 1 objectsStatistics:MT Count TotalSize Class name009e30d0 1 16 DPDemonstration.normalObjectTotal 1 objects!DumpHeap -stat -type DependencyObjecttotal 1 objectsStatistics:MT Count TotalSize Class name009e31a0 1 12 DPDemonstration.DependencyObjectTotal 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>(); 10: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
11: if (effectiveValue.PropertyIndex != 0)
12: {
13: return effectiveValue.Value;
14: }
15: else
16: {
17: return DependencyProperty.RegisteredDps[dp.HashCode].Value;
18: }
19: }
20:
21: public voID SetValue(DependencyProperty dp,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 22: {
@H_227_419@ 23: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
24: if (effectiveValue.PropertyIndex != 0)
26: effectiveValue.Value = value;
28: else
29: {
30: effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index,Value = value };
31: _effectiveValues.Add(effectiveValue);
32: }
33: }
34:
35: public string name
36: {
37: get
38: {
39: return (string)GetValue(nameProperty);
40: }
41: set
42: {
43: SetValue(nameProperty,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 44: }
45: }
46: }
新引进的EffectiveValueEntry:
1: internal struct EffectiveValueEntry3: internal int PropertyIndex { get; set; }
5: internal object Value { get; set; }
6: }
改进的DependencyProperty,加入了ProperyIndex:
3: private static int globalindex = 0;4: internal static Dictionary<object,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 5: internal string name;
6: internal object Value;
7: internal int Index;
8: internal object HashCode;
9:
10: private DependencyProperty(string name,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 11: {
12: this.name = name;
13: this.Value = defaultValue;
14: this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
15: }
16:
17: public static DependencyProperty Register(string name,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 18: {
19: DependencyProperty dp = new DependencyProperty(name,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 20: globalindex++;
21: dp.Index = globalindex;
22: RegisteredDps.Add(dp.HashCode,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px">@H_227_419@ 23: return dp;
24: }
25: }
在DependencyObject加入了一个_effectiveValues,就是把所有修改过的DP都保存在EffectiveValueEntry里,这样,就可以达到只保存修改的属性,未修改过的属性仍然读取DP的默认值,优化了属性的储存。
更进一步的发展
到目前为止,从属性到依赖属性的改造一切顺利。但随着实际的使用,又一个问题暴露出来了。使用继承,子类可以重写父类的字段,换句话说,这个默认值应该是可以子类化的。那么怎么处理,子类重新注册一个DP,传入新的默认值?
当然,不会实现的这么丑陋。同一个DP,要想支持不同的默认值,那么内部就要维护一个对应不同DependencyObjectType的一个List,可以根据传入的DependencyObject的类型来读取它对应的默认值。
DP内需要维护一个自描述的List,按照微软的命名规则,添加新的类型属性元数据(PropertyMetadata):
1: public class PropertyMetadata3: public Type Type { get; set; }
4: public object Value { get; set; }
5:
6: public PropertyMetadata(object defaultValue)
8: this.Value = defaultValue;
9: }
10: }
对应修改DependencyProperty
9: private List<PropertyMetadata> _MetadataMap = new List<PropertyMetadata>();10: private PropertyMetadata _defaultMetadata;
11:
12: private DependencyProperty(string name,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 13: {
14: this.name = name;
15: this.Value = defaultValue;
16: this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
18: PropertyMetadata Metadata = new PropertyMetadata(defaultValue) { Type = ownerType };
19: _MetadataMap.Add(Metadata);
20: _defaultMetadata = Metadata;
21: }
22:
@H_227_419@ 23: public static DependencyProperty Register(string name,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 24: {
25: DependencyProperty dp = new DependencyProperty(name,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 26: globalindex++;
27: dp.Index = globalindex;
28: RegisteredDps.Add(dp.HashCode,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> 29: return dp;
30: }
31:
32: public voID OverrIDeMetadata(Type forType,PropertyMetadata Metadata)
33: {
34: Metadata.Type = forType;
35: _MetadataMap.Add(Metadata);
36: }
37:
38: public PropertyMetadata GetMetadata(Type type)
39: {
40: PropertyMetadata medatata = _MetadataMap.FirstOrDefault((i) => i.Type == type) ??
41: _MetadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type));
42: if (medatata == null)
43: {
44: medatata = _defaultMetadata;
45: }
46: return medatata;
47: }
48: }
修改DenpendencyObject中的GetValue并更改_effectiveValues,为了简洁去掉了nameProperty以及SetValue.
3: private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();5: public object GetValue(DependencyProperty dp)
6: {
7: EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
8: if (effectiveValue.PropertyIndex != 0)
9: {
10: return effectiveValue.Value;
11: }
12: else
13: {
14: PropertyMetadata Metadata;
15: Metadata = DependencyProperty.RegisteredDps[dp.HashCode].GetMetadata(this.GetType());
16: return Metadata.Value;
17: }
18: }
19: }
这样,就可以定义一个SubDependencyObject,调用OverrIDeMedata向DP的_MetadataMap中加入新的Metadata。
1: public class SubDependencyObject : DependencyObject3: static SubDependencyObject()
4: {
5: nameProperty.OverrIDeMetadata(typeof(SubDependencyObject),new PropertyMetadata("Subname"));
6: }
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;5: internal int PropertyIndex { get; set; }
6:
7: internal object Value
8: {
9: get
10: {
11: return _value;
12: }
13: set
14: {
15: _value = value;
16: }
17: }
18:
19: internal ModifIEdValue ModifIEdValue
20: {
21: get
22: {
@H_227_419@ 23: if (this._value != null)
24: {
25: return (this._value as ModifIEdValue);
26: }
27: return null;
28: }
29: }
30: }
对应的ModifIEdValue:
1: internal class ModifIEdValue3: internal object AnimatedValue { get; set; }
4: internal object BaseValue { get; set; }
5: internal object CoercedValue { get; set; }
6: internal object ExpressionValue { get; set; }
7: }
当属性没有被修改过,ModifIEdValue为空,当修改过后,ModifIEdValue被赋值。这里EffectiveValueEntry定义了很多方法如SetExpressionValue(object value),SetAnimatedValue(object value)等来向ModifIEdValue中写入对应值;并且EffectiveValueEntry提供了IsAnimated,IsExpression等属性来表示当前的状态。当然,这个赋值的 *** 作比较复杂,这个优先级分两大类:一 ModifIEdValue中各属性的优先级;二对于ExpressionValue来说,它又有自己的优先级,Local>Style>Template…这里就不详细解释了。
依赖属性的优点
回过头来,总结一下依赖属性的优点:
优化了属性的储存,减少了不必要的内存使用。 加入了属性变化通知,限制、验证等, 可以储存多个值,配合Expression以及Animation等,打造出更灵活的使用方式。 总结借助于依赖属性,WPF提供了强大的属性系统,可以支持数据绑定、样式、动画、附加属性等功能。这篇文章主要是简略的实现了一个从属性到依赖属性的发展过程,当然,具体和WPF的实现还有偏差,希望朋友们都能抓住这个主要的脉络,更好的去玩转它。
除了依赖属性的实现,还有一些很重要的部分,比如借助于依赖属性提出的附加属性,以及如何利用依赖属性来更好的设计实现程序,使用依赖属性有哪些要注意的地方。呵呵,那就,下篇吧。
示例程序下载
一站式WPF--依赖属性(DependencyProperty)二
2009-10-20 11:32 by 周永恒,6736 阅读, 21 评论,收藏,编辑
书接上文,前篇文章介绍了依赖属性的原理和实现了一个简单的DependencyProperty(DP),这篇文章主要探讨一下如何使用DP以及有哪些需要注意的地方。
回顾依赖属性是由DependencyObject来使用的,那么一个典型的使用场景是什么样呢?
使用DependencyProperty一个简单的使用如下:
1: public class SimpleDO : DependencyObject
2: {
3: public static Readonly DependencyProperty IsActiveProperty =
4: DependencyProperty.Register("IsActive",typeof(bool),typeof(SimpleDO),
5: new PropertyMetadata((bool)false));
6:
7: public bool IsActive
8: {
9: get { return (bool)GetValue(IsActiveProperty); }
10 set { SetValue(IsActiveProperty,value); }
11: }
12: }
SimpleDO sDo = new SimpleDO();
sDo.IsActive = true;
这里是使用DependencyProperty.Register来注册DP的,Register函数有很多重载,一个最全的形式如下:
public static DependencyProperty Register(string name,
PropertyMetadata typeMetadata,ValIDateValueCallback valIDateValueCallback);
前4个参数在前篇文章已有介绍,主要是用来确定DP在全局Map中的键值,属性的类型以及内部属性元数据。最后一个参数是一个delegate,用来验证数据的有效性。
抛开验证的过程不说,先来看看PropertyMetadata。前篇提到,这个PropertyMetadata是可以子类化的,子类可以调用OverrIDeMetadata来重写PropertyMetadata。WPF属性系统对于依赖属性支持的策略就封装在Metadata中,那么这个PropertyMetada都有哪些呢?
常见的主要有FrameworkPropertyMetadata,UIPropertyMetadata以及PropertyMetadata。他们的继承关系是F->U->P。以最复杂的来说,FrameworkPropertyMetadata都提供了哪些功能呢?
FrameworkPropertyMetadataFrameworkPropertyMetadata的构造函数提供了很多重载,一个最复杂的构造函数如下:
public FrameworkPropertyMetadata( object defaultValue,
FrameworkPropertyMetadataOptions flags,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> PropertyChangedCallback propertyChangedCallback,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> CoerceValueCallback coerceValueCallback,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> bool isAnimationProhibited,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> UpdateSourceTrigger defaultUpdateSourceTrigger);
其中第一个参数是默认值,最后两个参数分别是是否允许动画,以及绑定时更新的策略,这个不详细解释了。重点看一下里第三、四两个参数,两个CallBack。结合前面提到的ValIDateValueCallback,这三个Callback分别代表ValIDate(验证),PropertyChanged(变化通知)以及Coerce(强制)。当然,作为Metadata,FrameworkPropertyMetadata只是储存了策略信息,WPF属性系统会根据这些信息来提供功能并在适当的时机回调传入的delegate。
那么WPF属性系统确定属性值的规则又是怎样呢?
处理DependencyProperty的规则借用一个常见的图例,介绍一下WPF属性系统对依赖属性 *** 作的基本步骤:
第一步,确定Base Value,对同一个属性的赋值可能发生在很多地方。比如控件的背景(Background),可能在Style或者控件的构造函数中都对它进行了赋值,这个Base Value就要确定这些值中优先级最高的值,把它作为Base Value。 第二步,估值。如果从第一步得到的值是一个表达式值(Expression),比如说一个绑定,WPF属性系统需要把它转化成一个实际值。 第三步,动画。如果当前属性正在作动画,那么因动画而产生的值会优于前面获得的值,这个也就是WPF中常说的动画优先。 第四步,强制。如果我们在FrameworkPropertyMetadata中传入了CoerceValueCallback,WPF属性系统会回调我们传入的的delagate,进行数据的强制赋值。在属性赋值过程中,Coerce拥有最高的优先级,这个优先级要大于动画的优先级别。 第五步,验证。如果在Register的时候传入了ValIDateValueCallback,那么最后WPF会调用我们传入的delegate,来验证数据的有效性。当数据无效时会抛出异常来通知。
那么应该如何使用这些功能呢?
一个简单的例子用一个简单的例子,来描述一下这个过程:
public class SimpleDO : DependencyObject
{
public static Readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",typeof(double),monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> new FrameworkPropertyMetadata((double)0.0,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> FrameworkPropertyMetadataOptions.None,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> new PropertyChangedCallback(OnValueChanged),monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> new CoerceValueCallback(CoerceValue)),monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> new ValIDateValueCallback(IsValIDateValue));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> }
private static voID OnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
Console.Writeline("ValueChanged new value is {0}",e.NewValue);
}
private static object CoerceValue(DependencyObject d,object value)
{
Console.Writeline("CoerceValue value is {0}",value);
return value;
}
private static bool IsValIDateValue(object value)
Console.Writeline("ValIDateValue value is {0}",monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> return true;
}
}
SimpleDO sDo = new SimpleDO();
sDo.Value = 1;
对应的输出:
ValIDateValue value is 0
ValIDateValue value is 0
ValIDateValue value is 1
CoerceValue value is 1
ValueChanged new value is 1
当属性变化后,PropertyChangeCallback最终被调用。这里ValIDate和Coerce的顺序有些乱,并没有完全依照前面谈到的Coerce->ValIDate的顺序。WPF对属性赋值进行了优化,当属性被修改时,首先会调用ValIDate来判断传入的值是否有效,如果无效就不调用后面的 *** 作,以提高性能。从这里也可以看出,CoerceValue后面并没有紧跟着ValIDateValue,而是直接调用PropertyChanged了。这是因为前面已经验证过value,如果在Coerce中没有改变value,那么就不用再验证了。如果在Coerce中改变了value,那么这里还会再次调用ValIDateValue来验证,Valiate在最后一步的意思是指整个Value赋值的过程中,一定会保证最终值得到验证。
当然,如果对Value作用了动画(需要修改SimpleDO继承于UIElement),比如:
DoubleAnimation animation = new DoubleAnimation(1,20,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> new Duration(TimeSpan.FromMilliseconds(5000)),FillBehavior.Stop);
sDo.BeginAnimation(SimpleDO.ValueProperty,animation);
那么在动画过程中,调用sDo.Value=30是不会有作用的,当然,这个值已经被存到LocalValue上了。在动画结束后,根据FillBehavior来决定是保留动画的最后值还是回到LocalValue上。当FillBehavior是Stop,动画结束后Value的值为30;如果是HoldEnd,那么动画结束后会一直保持动画的最后值20。
关于LocalValue(本地值),我们稍后再来细谈,先来回顾一下这个例子。在这个例子中,我们分别传入了三个delagate(PropertyChange,Coerce和ValIDate)。关于PropertyChangeCallback,这个再明显不过了,在属性值变化的时候调用。那么Coerce和ValIDate意义何在呢?
Coerce与ValIDateDependencyObject提供了两个函数以支持调用Coerce和ValIDate,分别是
public voID CoerceValue(DependencyProperty dp);
public voID InvalIDateProperty(DependencyProperty dp);
第一个函数较为常用,比如说SlIDer,它有三个属性相互作用,Value、Minimum和Maximum,这些属性相互作用,一个默认的规则是Minimum≤Value≤Maximum。那么当其中一个变化时,另外两个是如何响应做出调整呢?这里WPF使用的就是CoerceValue,这个实现也很简单,注册Maximum的时候加入CoerceValueCallback,在CoerceMaximum函数中判断,如果Maximum的值小于Minimum,则使Maximum值等于Minimum;同理在Value中也加入了CoerceValueCallback进行相应的强制判断。然后在Minimum的ChangedValueCallback被调用的时候,调用Maximum和Value的CoerceValue。
用一句话来形容这个用法就是,当相互作用的几个依赖属性其中一个发生变化时,在它的PropertyChangeCallback中调用受它影响的依赖属性的CoerceValue。当然,这些依赖属性要实现CoerceValueCallback,在其中保证相互作用关系的正确性。
后一个ValIDate主要是验证一下数据有效性,比如说传入的double参数是否是NaN等等。
相对来说,Coerce和ValIDate并不是特别常用,在WPF属性系统为我们提供的服务中,FrameworkPropertyMetadataOptions应该算有特色,定制性最强的。
FrameworkPropertyMetadataOptions所谓Option(选项),肯定有相应的功能与之对应,那么这些开关都有哪些呢?
public enum FrameworkPropertyMetadataOptions
{
None = 0,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> AffectsMeasure = 1,
AffectsArrange = 2,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> AffectsParentMeasure = 4,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> AffectsParentArrange = 8,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> AffectsRender = 16,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> inherits = 32,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> OverrIDesinheritanceBehavior = 64,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> NotDataBindable = 128,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> BindsTwoWayByDefault = 256,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> Journal = 1024,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> SubPropertIEsDoNotAffectRender = 2048,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px">}
这些选项分为两类,一类是标记着Affect的,表示这个依赖属性变化后,会有哪些影响,包括(要重新测量,重新绘制等等)。另一类是剩下的,表示当选择了该选项后,依赖属性会具备什么功能,包括(默认双向绑定,属性继承等)。这里介绍一下属性继承(inherits)。
继承是我们很熟悉的,子类可以继承父类的方法和属性等。这里是有父子关系的,那么属性继承的父子关系在哪?
WPF的依赖属性可继承性是依附于对象树的,这个对象树,具体来说是逻辑树。比如说,Window内部放置了一个button,那么对象树就是Window—button,Window是button的父节点。在Window上设置字体大小(FontSize),这个值同样会作用在button上,这个就是所谓的属性继承。在一般情况下,属性继承会沿着逻辑树一直传下去,除非对象更改了传递的策略。FrameworkElement对象中提供了属性
protected internal inheritanceBehavior inheritanceBehavior { get; set; }
子类可以重载这个属性传递SkipToAppNow等来截断这个继承,默认情况下,Frame就断开了这个继承链。
回过头来,说说LocalValue,这个配合EffectiveValue,构成了依赖属性中最精彩的故事。
LocalValue与EffectiveValue依赖属性很强大,WPF也在不遗余力的宣扬它的美,就像魔术师一样,千变万化的魔术中总有它的底,让我们来掀一下它的底牌,看看它到底是什么玩意。
在前篇文章里,我们实现了一个EffectiveValue(EffectiveValueEntry),虽然很简陋,不过可以看出它的两大功能。一,只储存变化的值。二,内部储存多个值,根据优先级选择当前值。作为一个属性来说,任何时间,它都应该而且也只应该对外暴露一个值。那么需要解决的问题在哪里呢?第一,从空间上说,同一个依赖属性可能在很多地方被赋值,比如说在构造函数中,Style中,属性继承下来的等等。第二,从时间上说,这些在不同地方的赋值又可能在同一时间发生变化,比如说绑定正在变化的同时又在对该属性作动画。那么就要有一个清晰的规则来界定,为此引入了两个概念,BaseValue和LocalValue。
前面谈到了处理依赖属性 *** 作的第一步就是,确定BaseValue,这个BaseValue,翻译过来叫基本值。这个基本,是针对动画(Animation)和强制(Coerce)来说的。当依赖属性处于动画或者强制中,它显示的是动画值或者强制值,一旦这两个状态失效,那么就会回到基本值来。
我们可以调用DependencyPropertyHelper的GetValueSource方法来获得当前依赖属性的信息:
ValueSource source = DependencyPropertyHelper.GetValueSource(sDo,SimpleDO.ValueProperty);
其中ValueSource如下:
public struct ValueSource
public BaseValueSource BaseValueSource { get; }
public bool IsAnimated { get; }
public bool IsCoerced { get; }
public bool IsExpression { get; }
}
其中的IsAnimated,IsCoerced,IsExpression用来指示当前依赖属性的状态,BaseValueSource指示当前BaseValue的优先级。它有
public enum BaseValueSource
UnkNown = 0,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> Default = 1,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> inherited = 2,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> DefaultStyle = 3,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> DefaultStyleTrigger = 4,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> Style = 5,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> TemplateTrigger = 6,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> StyleTrigger = 7,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> ImplicitStyleReference = 8,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> ParentTemplate = 9,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> ParentTemplateTrigger = 10,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> Local = 11,monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px">}
BaseValueSource的优先级别是从小到大,Local具有最高的优先级,这里的Local指在XAML声明时显式指定的属性值或者在后台手动赋值,如 <button x:name=”btn” WIDth=”12”/>或者在后台代码中btn.WIDth=12。也就是说,当你在后台对一个依赖属性赋值后,这个属性在Style中的值或者Trigger都会因优先级不够高而失去作用。这种情况是很常见的,很多时候,当依赖属性发生问题(绑定没有更新,Trigger没有反应)时,都可以查看当前依赖属性的ValueSource来判断是不是错误设置了DP而导致了优先级不够高才得不到响应。
那么这个LocalValue是从何而来,是指BaseValueSource中的Local么?
是的,DependencyObject提供了ReadLocalValue函数来读取当前的LocalValue
public object ReadLocalValue(DependencyProperty dp);
如果没有在XAML声明时或者在后台为依赖属性赋值,即使在Style中赋值,那么读取出的值都应为DependencyProperty.UnsetValue。如果在声明时使用了绑定,那么读出的值为BindingExpression,其他情况下会读取出当前local中的值。
那么LocalValue和EffctiveValue的区别在哪呢?DependencyObject提供了GetValue方法来取得属性值,这个值就是EffctiveValue,也就相当于魔术千变万化最终看到的结果,而LocalValue是内部设置的值。举一个简单的例子来说明一下:
仍然用SlIDer,它的Minimum,Value以及Maximum
1: SlIDer slIDer = new SlIDer();
2: slIDer.Minimum = 0;
3: slIDer.Maximum = 10;
4: slIDer.Value = 3;
5:
6: slIDer.Minimum = 4; //After set,Value = 4; Value's Local Value = 3;
7: slIDer.Minimum = 1; //After set,Value = Value's Local Value = 3;
8: slIDer.Minimum = 13; //After set,Value = Maximum = 13;
第6行,当设置了Minimum=4后,Value的Coerce会被调用,在Coerce中,因为Value值(3)小于Minmum(4),Value值被强制为4。但Value的Local值仍然被保留,使用ReadLocalValue函数可以查看到Value的LocalValue仍然为3。第7行,Minimum的值为1后,在Value的Coerce中,因为Value的LocalValue(3)大于1,所以最终取得的Value和LocalValue都为3。
关于EffectiveValue和LocalValue,WPF对此的态度一直都是半遮半掩,一方面,在对外的函数或注释中对此有过说明;另一方面,又把它当作内部细节一语带过。但这确实是很多稀奇古怪BUG的根源,希望朋友们都能看透这层画皮,更好的从内部掌握它。
谈过了依赖属性的功能,回过头来看看如何注册依赖属性,以及WPF提出的附加(Attached)这个概念。
附加(Attached)属性在最前面的例子中,我们是使用DependencyProperty.Register来注册DP的,DP也对外提供了DependencyProperty.Registerattached方法来注册DP。这个Registerattached的参数和Register是完全一致的,那么Attached(附加)这个概念又从何而来呢?
其实我们使用依赖属性,一直在Attached(附加)。我们注册(构造)一个DP,然后在DependencyObject中通过GetValue和SetValue来 *** 作DP,也就是把这个DP通过这样的方法粘贴到DependencyObject上,只不过是通过封装CLR属性来达到的。那么Registerattached又是怎样呢,来看一个最简单的应用:
public class AttachedHelperpublic static Readonly DependencyProperty IsAttachedProperty =
DependencyProperty.Registerattached("IsAttached",typeof(AttachedHelper),monospace; direction:ltr; border-top-style:none; color:black; Font-size:9pt; border-left-style:none; overflow:visible; padding-top:0px"> new FrameworkPropertyMetadata((bool)false));
public static bool GetIsAttached(DependencyObject d)
{
return (bool)d.GetValue(IsAttachedProperty);
}
public static voID SetIsAttached(DependencyObject d,bool value)
{
d.SetValue(IsAttachedProperty,value);
}
}
在XAML中使用:
<local:SimpleDO x:name="sDo" local:AttachedHelper.IsAttached="True"/>
在这个AttachedHelper中,并没有使用CLR属性IsAttached来封装,而是使用了SetIsAttached和GetIsAttached来存取IsAttached值,当然内部还是调用了SetValue与GetValue。XAML Parser提供了支持,local:AttachedHelper.IsAttached="True"最终会调用到SetIsAttached函数。
Register和Registerattached只是封装形式不同,内部的实现都是GetValue和SetValue,这个Attach(能力)是由DependencyObject和DependencyProperty这种分离的设计所产生的,并不是你使用Registerattached这个函数产生了多大魔力。当然,因为使用了静态函数封装,Registerattached这种方法更加动态化,我们可以在一个运行中的DependencyObject上对它调用SetIsAttached方法,把这个属性塞到DependencyObject内部的EffectiveValueEntry上去。
因为这种动态附加的能力,使用Registerattached注册的依赖属性也被称为附加属性,有了附加属性,因此也衍生出了一些精彩的设计,这些略过不提,来看看使用依赖属性有哪些需要注意的地方。
使用依赖属性要注意的地方依赖属性,看这个词它应该也是个属性,那么传统CLR属性的一些用法(多态,Set里面Raise一些Event等)还适用么?
江湖险恶,小心。
拿前面SimpleDO的ValuePropety来说。你在后台代码中调用sDo.Value = 2,那么Value的set会被调用,但如果你在XAML赋值或者把它绑定到其他属性,程序运行后,Value的值正常,可是Value的set并没有被调到。这是怎么回事?
WPF对依赖属性进行了优化,在绑定等一些场合并不是调用属性的get,set方法,而是直接读取或设置依赖属性的EffectiveValue,也就是说,为了提高性能,绕开了你封装的方法,直接在DP内部去搞了。当然,不按规矩出牌,按照传统属性设置的多态,set里的逻辑就统统失效了。
WPF建议实现多态的方式是在PropertyChangedCallback中调用虚函数来实现,如:
private static voID OnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
((SimpleDO)d).OnValueChanged(e);
}
protected virtual voID OnValueChanged(DependencyPropertyChangedEventArgs e)
{
}
子类通过重载OnValueChanged来实现多态。
当然,具体使用依赖属性出现的问题还会有很多,就不一一介绍了,接下来看看依赖属性为我们设计程序提供了哪些帮助。
使用依赖属性的新思维依赖属性提供了很强大的功能,附加属性,属性变化通知,可继承,多属性值等等,关于这方面的宣传也到处可见,用武功秘籍来形容,大概是这样子的。
江湖新出现了一门武功,集旧派武功之大成,练到绝处,诡异莫测,绣花针亦可为武器,杀人于无形之中。当千辛万苦夺得秘籍之后,未翻书,体先寒,只见上面四个大字,“葵花宝典”……
确实,不是所有对象都可以使用依赖属性的,只有继承自DependencyObject的对象才可以使用DP,DependencyObject具有这样的能力源自它内部持有一个EffectiveValueEntry的数组,类似于一个百宝囊。这个也是在设计中经常被人遗忘的地方。
比如说,有两个Panel,一个Panel上又有很多Panel、控件,我们希望按照一定条件过滤,把符合条件的控件移到另一个Panel上去,然后点击恢复按钮这些控件又可以回到原位置。这里就可以定义一个附加属性,比如说oldParent,遍历第一个Panel的逻辑树,把符合条件的控件从它的Parent中移除,并且使用附加属性oldParent记录它的Parent,这样在恢复原位置的时候就可以拿到oldParent,然后再Add回去了。
当然,这类应用就是定义一个附加属性,然后附加到对象中去,应用的都比较简单,只是为了说明DependencyObject具有这样支持存取的能力,设计程序的时候不要浪费。
总结关于依赖属性,可写的东西很多,每一个点展开都能有很多故事,像RegisterReadonly,AddOwner等都没有详细介绍。依赖属性中,属性和使用它的对象分离是它的特色,两者之间的粘合和作用是它的难点,希望朋友们都能从内到外的看待依赖属性,更好的玩转它。
作者:周永恒
出处:http://www.cnblogs.com/Zhouyongh
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
以上是内存溢出为你收集整理的Silverlight&WPF依赖属性DependencyProperty讲解全部内容,希望文章能够帮你解决Silverlight&WPF依赖属性DependencyProperty讲解所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)