WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)

来源:转载

2010-08-27 03:21 by 圣殿骑士

一. 摘要

前几篇我们讲了WPF的一些基本知识,但是始终没有接触最核心的概念,那么从这篇文章开始的下面几篇文章中,我们会分别深入讨论一下依赖属性、路由事件、命令和绑定等相关概念,希望这几篇文章对大家能有所帮助。由于自己才疏学浅且是对这些技术的使用总结和心得体会,错误之处在所难免,怀着技术交流的心态,在这里发表出来,所以也希望大家能够多多指点,这样在使一部分人受益的同时也能纠正我的错误观点,以便和各位共同提高。

依赖属性是WPF的核心概念,所以我们花费了大量的时间和篇幅进行论述,首先从依赖属性基本介绍讲起,然后过渡到依赖属性的优先级、附加属性、只读依赖属性、依赖属性元数据、依赖属性回调、验证及强制值、依赖属性监听、代码段(自动生成) 等相关知识,最后我们会模拟一个WPF依赖属性的实现,来看看它里面的内部究竟是怎样处理的,这样就可以帮助我们更好的认清它的本质,出现问题的时候我们也可以根据原理快速找到原因了。

二. 本文提纲

· 1.摘要

· 2.本文提纲

· 3.比这篇文章更重要的东西

· 4.云计算广告插播

· 5.依赖属性基本介绍

· 6.依赖属性的优先级

· 7.依赖属性的继承

· 8.只读依赖属性

· 9.附加属性

· 10.清除本地值

· 11.依赖属性元数据

· 12.依赖属性回调、验证及强制值

· 13.依赖属性监听

· 14.代码段(自动生成)

· 15.模拟依赖属性实现

· 16.本文总结

· 17.相关代码下载

· 18.系列进度

三. 比这篇文章更重要的东西

在讲这篇文章之前,我们先来聊一点更重要的东西,正所谓“授人与鱼不如授人以渔”,那么我们这个“渔”究竟是什么呢?大家做软件也有不少年了,对自己擅长的一门或多门技术都有自己的经验和心得,但总的来说可以分为向内和向外以及扩展三个方面(这里只针对.NET平台):

(一)向外:

会使用ASP.NET、WinForm、ASP.NET MVC、WPF、Silverlight、WF、WCF等技术,在用这些技术做项目的同时积累了较丰富的经验,那么大家就可以形成自己的一套开发知识库,知道这些技术怎么能快速搭建企业所需要的应用、知道这些技术在使用中会出现什么样的问题以及如何解决。那么在这个时候你就可能已经在团队中起到比较核心的作用,如果项目经理给你一个任务,你也可以很轻松且高效的胜任,同时在项目当中由于你也比较清楚业务逻辑,所以当机会来临的时候,你很快会成为团队的骨干,逐渐你就会带几个初级一点的工程师一起做项目;如果你不喜欢带团队,你就会成为资深的高级开发或者架构师。那么在向外方面我个人认为最重要的是积累经验,对常见的应用要比较熟悉且有自己总结的一套开发库——比如对普通的网站、电子商务系统、ERP、OA、客户端应用等等有比较丰富的经验。

(二)向内:

在前面你使用了这些技术开发项目之后,你会遇到很多问题,为了解决这些问题,你会逐渐研究一些比较底层次的东西。比如对于ASP.NET,你会逐渐的去深入理解ASP.NET的整个处理过程、页面的生命周期、自定义控件的开发等等,由于自己最初是由C、C++、Java这样过渡到.NET的,所以对一些细节总喜欢钻牛角尖,这也浪费了不少时间,但同时也得到了很多意外之喜。

对于C#语言上,你也会逐渐去刨根问底,想看看这些语法背后到底隐藏着什么秘密,很多对.NET技术比较痴迷的人都会选择对C# 1.0 语言通过IL代码来深层次认识,然后对C#2.0、C#3.0、C#4.0都编译为C# 1.0 来学习,这样他们就能认识到语言的内部到底是怎么执行的,正所谓知道的同时也知道其所以然。

对于WF,你不仅要知道各 Activity的使用,你也得知道其内部的原理,比如WF 内部就是依靠依赖属性来在工作流中的各 Activity 间传递属性值的,如果你细心,你还原WF的依赖属性源码,你会发现它和WPF、Silverlight中的依赖属性大同小异,原理基本一样、只是针对特定技术进行了适当的调整。

对于数据底层操作也一样,不管你是用的拼接SQL、存储过程、IBATIS.NET、Nhibernate,Active Record,Linq to sql、Entity framework还是自己开发的ORM组件,你得明白它内部的原理,你要知道这些开源的代码还是很值得研究的,我的经验是先熟练使用这些功能,然后再剖析它的源码,然后自己写一套自己的框架,现在我也在精简自己的ORM框架,因为之前把重点放在了实现尽可能多的功能,所以对性能等细节没有做过多优化,后面也会向大家慢慢学习。

对WPF和Silverlight一样,你不仅要知道怎么用这些技术,你要知道它的原理,比如对依赖属性,你知道它的内部原理,就可以对平时出现的诸如我设置的值怎么没有起作用、我Binding的元素怎么没有出现等等问题;对路由事件,你也会经常遇到我的事件怎么没有执行、我的自定义控件事件怎么处理不对、路由传递怎么没有起作用等等,这个时候你如果深入理解了路由事件的内部处理,这些问题就迎刃而解了;对WPF和Silverlight新多出来的命令特性,大家很多时候也是比较疑惑,也会遇到命令失效等等问题,其实如果你深入的了解了它的原理,你就会知道,它其实在内部也是事件,只不过微软在里面做了很多封装而已;对Binding就更是如此,我们也不想在这篇文章谈开去,毕竟在下面的几篇文章会详细的对这些技术进行涉及。

(三)扩展:

通过前面的向内和向外的修炼以后,接下来要做的就是不断实践,不断总结经验,在这个过程中更重要的是要懂得分享,有分享才会使自己和他人共同提高,有分享才能让自己摆脱狂妄的井底之蛙思想,还记得自己刚做技术的一两年里,天天喜欢提及大型架构、大型数据处理、操作系统底层代码如何如何,甚至把AOP、IOC、SSH、OO及设计模式、SOA等词语时常挂在嘴边,生怕别人不知道自己不懂。但随着自己技术实质上的提高以及经验的积累,自己也就逐渐成熟起来,对这些技术逐渐深入理解且理解了其内部实现原理,这样反而自己变得谦虚起来了,对之前的那些思想感到无比的羞愧。同时也明白自己在慢慢成长了,现在都习惯戏称自己为打杂工,其实更多时候用打字员会合理一些,所以希望大家能多多指教,这样我才能更快地摆脱打字员的生活。我在这里也对扩展做一点小的总结:

记录学习:这是学习很重要的一步,你不一定要写技术博客,你也可以做一些例子来记录,你也可以在学习之后写一个总结,毕竟人的精力十分有限,在很多时候,它并不能像硬盘一样存储起来就不会丢失,更多的时候它更像一块内存。

同道交流:在这一层里我觉得最重要的就是和一些技术较好的人成为朋友,和他们经常探讨一些技术,这样可以缩短学习的周期,同时也能快速的解决问题,毕竟人的精力十分有限,你没有遇到过的问题,说不定其他人遇到过。在这方面自己也体会颇深,也很感谢之前几个公司及现在公司的同事、社区朋友以及一些志同道合之士,感谢你们的指点,没有你们的指点,我也不可能从小鸟进化成逐鹿程序界的菜鸟,我也为自己能成为一只老菜鸟感到自豪!

少考证、多务实:在扩展的这一层里,我们要谨记不要为了考证而去考证,那样是没有任何实际作用的。对MVP也一样,一切顺其自然为好,记得大学时候身边就有人连续四次荣获MVP称号,这叫我在当时是相当的佩服,在佩服之余我们要切记务实,没有务实的东西都是很虚拟飘渺的。还记得自己当初在大学里面受到考证风气的影响,神经兮兮的去考过了什么国家计算机四级和MCP等一大堆证件,后来到公司面试兴高采烈拿着20多张证书,才知道那些东西根本就没有什么价值,反而让自己去学习了最不喜欢的技术,同时也给自己挂上了考证族的名号。所以后来总结就是劳民伤财、徒添伤悲!

技术分享:在自己公司及其他公司进行一些技术培训或者讨论,其实重要的不是什么荣誉,而是在把这个培训看成是一些技术交流和分享,因为在这个过程中,你可能会重新认识你所掌握的技术、你可能会遇到一些志同道合的人、你可能会在分享过程中纠正以前的错误认识、你可能会在各方面得到提高从而完善自己的知识体系,但是最重要的是你要认真对待每一次培训,知之为知之不知为不知,不要不能教导他人反而误导了他人。记得有一次在公司培训OO与设计模式,我知道这个专题想在一下午的时间把它讲清楚是非常困难的,这个不像之后培训的WPF、WCF和Silverlight那么单纯,并且每个人的基础都不一样,当中有还没有毕业的实习生、刚毕业不久的毕业生、工作了数年的工程师及技术大牛们,所以如何把这些知识很好的插入到每个人的知识树上面成了我考虑的重点。同时我的心里也比较矛盾,一方面希望参加培训的同事多一些,另一方面希望人越少越好。前者则是按照常理来考虑的,毕竟培训者都希望自己培训,越受欢迎越好,这样才能使自己的思想得到更多人的认可,自己也能实现分享知识的目的。后者则是担心怕讲不好,少一点人就少一点罪过。可是恰巧这一次是历次培训中最多的一次,来参加培训的同事有一百多人,不过幸好由于会议室坐不下,才分成了两批,这样就可以让我具备了更充分的时间和更好的心态。总之培训是向内和向外的提炼与升华,正所谓“自己理解的知识未必能使人家理解”,这不仅考验的是技术,还考验了一个人的综合能力。

(四)结论:

前面从向内和向外以及扩展三个方面进行了简单阐述,用一句话概括就是:向内深不可测、向外漫无边际、扩展才能超越极限。由于这里只是对本文及下面的三篇文章做一些铺垫工作,所以我们也不细细分解,那么我也得稍微推荐一点资料才对得起大家:

第一还是研究微软的类库,对我们常见的应用进行研究,可以结合Reflector+VS调试内部代码功能一起研究(IL能帮我们看清楚一些内部原理,但是不推荐细究,因为它会浪费我们很多时间,毕竟是微软搞出来的这么一套东西,说不定微软哪天就换了)。

其次就是研究MONO源码(www.mono-project.com),这个是个非常好的东西,对.NET的功能大部分都进行了实现,我之前研究它不是因为它的跨平台,是感兴趣它的源码,大家也可以在线查看它的源码(www.java2s.com),说到java2s这个网站,也是我平时去得比较多的网站,因为它比较全面和方便,同时也会给我们带来意想不到的收获。

再其次就是研究一些开源的框架和项目,比如

pet shop 4.0(http://software.informer.com/getfree-net-pet-shop-4.0-download/)、

BlogEngine.NET(http://www.dotnetblogengine.net/)、

Spring.NET(http://www.springframework.net/)、

Castle(http://www.castleproject.org)、

log4net(http://logging.apache.org/log4net/)、

NHibernate(http://www.hibernate.org/343.html)、

iBATIS.NET(http://ibatis.apache.org)、

Caliburn(http://caliburn.codeplex.com/)、

MVVM Light Toolkit(http://mvvmlight.codeplex.com/)、

Prism(http://compositewpf.codeplex.com/)等等。

这里要注意的是:在研究的过程中一定要先熟悉功能,再研究它内部的源码和实现,然后再创造出自己的框架。这样才能激发我们研究的欲望,才会产生作用和反作用力,从而才会使我们真正受益。

四. 云计算广告插播

由于这段时间白天要研究云计算专题(公司项目原因,最主要还是自己的兴趣使然),晚上和闲暇时间又要写WPF,所以感觉有点心猿意马。原打算写完WPF这个系列以后才继续”云计算之旅“这个系列,但是经过慎重的思考,同时也考虑到录制MSDN WebCast视频,所以决定两个系列同时进行,经过几个月的筹备(期间包括折腾公司的云计算项目、研究相关云计算的电子书、国外技术视频和国外各技术社区和博客等),自己也颇有收获。

期间最重要的还是自己写例子,写完了以后再分析它的原理直至最后总结,这样才能把它变成自己的东西,现在回过头来感觉云计算终于在自己心目中走下了神坛,逐渐揭开了那一层神秘面纱,所以才有下面这个系列的分享,也希望大家能给出建议,从而达到技术交流、共同提高的目的。

云计算之旅1—开篇有益

云计算之旅2—云计算总览

云计算之旅3—云计算提供商综合对比

云计算之旅4—Windows Azure总览

云计算之旅5—第一个Windows Azure程序

云计算之旅6—剖析Windows Azure程序内部原理

云计算之旅7—ASP.NET WebRole

云计算之旅8—ASP.NET MVCWeb Role

云计算之旅9—WCF ServiceWeb Role

云计算之旅10—Work RoleCastle

云计算之旅11—CGI Web Role

云计算之旅12—云存储之Blob

云计算之旅13—云存储之Table

云计算之旅14—云存储之Quee

云计算之旅15—云存储之Dive

云计算之旅16—SQL Azure(一)

云计算之旅17—SQL Azure(二)

云计算之旅18—SQL Azure(三)

云计算之旅19—AppFabric(一)

云计算之旅20—AppFabric(二)

云计算之旅21—AppFabric(三)

云计算之旅22—云平台安全问题

云计算之旅23—老技术兼容问题

云计算之旅24—ASP.NET+SQL项目移植到云平台

云计算之旅25—WinForm/WPF项目移植到云平台(云/端模式)

云计算之旅26—ASP.NET+Silverlight项目移植到云平台

云计算之旅27—Amazon云计算

云计算之旅28—Google云计算

云计算之旅29—SalesForce云计算

云计算之旅30—云计算开发总结

上面的分类是按照最近学习的总结归类的,在这几个月中也先后写了一些文章和代码示例,同时有些知识没有罗列上去,在后面可能会有一些小的修改。总之,我们坚决抵制”忽悠“,争取以实际代码说话,在此过程中希望大家能够积极踊跃的加入进来,如果有什么不对的地方,也希望向大家学习,最重要的是大家有所收获就好!

五. 依赖属性基本介绍

大家都知道WPF带来了很多新的特性,它的一大亮点是引入了一种新的属性机制——依赖属性。依赖属性基本应用在了WPF的所有需要设置属性的元素。依赖属性根据多个提供对象来决定它的值(可以是动画、父类元素、绑定、样式和模板等),同时这个值也能及时响应变化。所以WPF拥有了依赖属性后,代码写起来就比较得心应手,功能实现上也变得非常容易了。如果没有依赖属性,我们将不得不编写大量的代码。关于WPF的依赖属性,主要有下面三个优点,我们的研究也重点放在这三点上: 
1、新功能的引入:加入了属性变化通知,限制、验证等等功能,这样就可以使我们更方便的实现我们的应用,同时也使代码量大大减少了,许多之前不可能的功能都可以轻松的实现了。
2、节约内存:在WinForm等项目开发中,你会发现UI控件的属性通常都是赋予的初始值,为每一个属性存储一个字段将是对内存的巨大浪费。WPF依赖属性解决了这个问题,它内部使用高效的稀疏存储系统,仅仅存储改变了的属性,即默认值在依赖属性中只存储一次。 
3、支持多个提供对象:我们可以通过多种方式来设置依赖属性的值。同时其内部可以储存多个值,配合Expression、Style、Animation等可以给我们带来很强的开发体验。 
在.NET当中,属性是我们很熟悉的,封装类的字段,表示类的状态,编译后被转化为对应的get和set方法(在JAVA里面没有属性的概念,通常都是写相应的方法来对字段进行封装)。属性可以被类或结构等使用。一个简单的属性如下,也是我们常用的写法:

private string sampleProperty;public string SampleProperty{ get { return sampleProperty; } set { if (value != null) { sampleProperty = value; } else { sampleProperty = "Knights Warrior!"; } }}

属性是我们再熟悉不过的了,那么究竟依赖属性怎么写呢?依赖属性和属性到底有什么区别和联系呢?

其实依赖属性的实现很简单,只要做以下步骤就可以实现: 

  1.  让所在类型继承自 DependencyObject基类,在WPF中,我们仔细观察框架的类图结构,你会发现几乎所有的 WPF 控件都间接继承自DependencyObject类型。 
  2. 使用 public static 声明一个DependencyProperty的变量,该变量才是真正的依赖属性,看源码就知道这里其实用了简单的单例模式的原理进行了封装(构造函数私有),只暴露Register方法给外部调用。 
  3. 在静态构造函数中完成依赖属性的元数据注册,并获取对象引用,看代码就知道是把刚才声明的依赖属性放入到一个类似于容器的地方,没有讲实现原理之前,请容许我先这么陈述。 
  4. 在前面的三步中,我们完成了一个依赖属性的注册,那么我们怎样才能对这个依赖属性进行读写呢?答案就是提供一个依赖属性的实例化包装属性,通过这个属性来实现具体的读写操作。

根据前面的四步操作,我们就可以写出下面的代码:

public class SampleDPClass : DependencyObject{ //声明一个静态只读的DependencyProperty字段 public static readonly DependencyProperty SampleProperty; static SampleDPClass() { //注册我们定义的依赖属性Sample SampleProperty = DependencyProperty.Register("Sample", typeof(string), typeof(SampleDPClass), new PropertyMetadata("Knights Warrior!", OnValueChanged)); } private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { //当值改变时,我们可以在此做一些逻辑处理 } //属性包装器,通过它来读取和设置我们刚才注册的依赖属性 public string Sample { get { return (string)GetValue(SampleProperty); } set { SetValue(SampleProperty, value); } }}

总结:我们一般.NET属性是直接对类的一个私有属性进行封装,所以读取值的时候,也就是直接读取这个字段;而依赖属性则是通过调用继承自DependencyObject的GetValue()和SetValue来进行操作,它实际存储在DependencyProperty的一个IDictionary的键-值配对字典中,所以一条记录中的键(Key)就是该属性的HashCode值,而值(Value)则是我们注册的DependencyProperty。

六. 依赖属性的优先级

由于WPF 允许我们可以在多个地方设置依赖属性的值,所以我们就必须要用一个标准来保证值的优先级别。比如下面的例子中,我们在三个地方设置了按钮的背景颜色,那么哪一个设置才会是最终的结果呢?是Black、Red还是Azure呢?

<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Grid> <Button x:Name="myButton" Background="Azure"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="Background" Value="Black"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Red" /> </Trigger> </Style.Triggers> </Style> </Button.Style> Click </Button> </Grid></Window>

通过前面的简单介绍,我们了解了简单的依赖属性,每次访问一个依赖属性,它内部会按照下面的顺序由高到底处理该值。详细见下图

  

由于这个流程图偏理想化,很多时候我们会遇到各种各样的问题,这里也不可能一句话、两句话就能够把它彻底说清楚,所以我们就不过多纠缠。等遇到问题之后要仔细分析,在找到原因之后也要不断总结、举一反三,只有这样才能逐渐提高。

七. 依赖属性的继承

依赖属性继承的最初意愿是父元素的相关设置会自动传递给所有层次的子元素,即元素可以从其在树中的父级继承依赖项属性的值。这个我们在编程当中接触得比较多,如当我们修改窗体父容器控件的字体设置时,所有级别的子控件都将自动使用该字体设置 (前提是该子控件未做自定义设置),如下面的代码:

<Window x:Class="Using_Inherited_Dps.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowStartupLocation="CenterScreen" FontSize="20" Title="依赖属性的继承" Height="400" Width="578"> <StackPanel > <Label Content="继承自Window的FontSize" /> <Label Content="重写了继承" TextElement.FontSize="36"/> <StatusBar>没有继承自Window的FontSize,Statusbar</StatusBar> </StackPanel></Window>

Window.FontSize 设置会影响所有的内部元素字体大小,这就是所谓的属性值继承,如上面代码中的第一个Label没有定义FontSize ,所以它继承了Window.FontSize的值。一旦子元素提供了显式设置,这种继承就会被打断,如第二个Label定义了自己的FontSize,所以这个时候继承的值就不会再起作用了。

这个时候你会发现一个很奇怪的问题:虽然StatusBar没有重写FontSize,同时它也是Window的子元素,但是它的字体大小却没有变化,保持了系统默认值。那这是什么原因呢?作为初学者可能都很纳闷,官方不是说了原则是这样的,为什么会出现表里不一的情况呢?其实仔细研究才发现并不是所有的元素都支持属性值继承。还会存在一些意外的情况,那么总的来说是由于以下两个方面:

1、有些依赖属性在注册的时候 指定Inherits为不可继承,这样继承就会失效了。

2、有其他更优先级的设置设置了该值,在前面讲的的“依赖属性的优先级”你可以看到具体的优先级别。

这里的原因是部分控件如StatusBar、Tooptip和Menu等内部设置它们的字体属性值以匹配当前系统。这样用户通过操作系统的控制面板来修改它们的外观。

这种方法存在一个问题:StatusBar等截获了从父元素继承来的属性,并且不影响其子元素。比如,如果我们在StatusBar中添加了一个Button。那么这个Button的字体属性会因为StatusBar的截断而没有任何改变,将保留其默认值。所以大家在使用的时候要特别注意这些问题。

八. 只读依赖属性

我们以前在对简单属性的封装中,经常会对那些希望暴露给外界只读操作的字段封装成只读属性,同样在WPF中也提供了只读属性的概念,如一些WPF控件的依赖属性是只读的,它们经常用于报告控件的状态和信息,像IsMouseOver等属性,那么在这个时候对它赋值就没有意义了。

或许你也会有这样的疑问:为什么不使用一般的.Net属性提供出来呢?一般的属性也可以绑定到元素上呀?这个是由于有些地方必须要用到只读依赖属性,比如Trigger等,同时也因为内部可能有多个提供者修改其值,所以用.Net属性就不能完成天之大任了。 
那么一个只读依赖属性怎么创建呢?其实创建一个只读的依赖属性和创建一个一般的依赖属性大同小异(研究源码你会发现,其内部都是调用的同一个Register方法)。仅仅是用DependencyProperty.RegisterReadonly替换了DependencyProperty.DependencyProperty而已。和前面的普通依赖属性一样,它将返回一个DependencyPropertyKey。该键值在类的内部暴露一个赋值的入口,同时只提供一个GetValue给外部,这样便可以像一般属性一样使用了,只是不能在外部设置它的值罢了。

九. 附加属性

前面我们讲了依赖属性。现在我们再继续探讨另外一种特殊的Dependency属性——附加属性。附加属性是一种特殊的依赖属性。他允许给一个对象添加一个值,而该对象可能对此值一无所知。

最好的例子就是布局面板。每一个布局面板都需要自己特有的方式来组织它的子元素。如Canvas需要Top和left来布局,DockPanel需要Dock来布局。

下面代码中的Button 就是用了Canvas的Canvas.Top和Canvas.Left="20" 来进行布局定位,那么这两个就是传说中的附加属性。

<Canvas> <Button Canvas.Top="20" Canvas.Left="20" Content="Knights Warrior!"/></Canvas>

在最前面的小节中,我们是使用DependencyProperty.Register来注册一个依赖属性,同时依赖属性本身也对外提供了 DependencyProperty.RegisterAttached方法来注册附加属性。这个RegisterAttached的参数和 Register是完全一致的,那么Attached(附加)这个概念又从何而来呢?

其实我们使用依赖属性,一直在Attached(附加)。我们注册(构造)一个依赖属性,然后在DependencyObject中通过 GetValue和SetValue来操作这个依赖属性,也就是把这个依赖属性通过这样的方法关联到了这个DependencyObject上,只不过是通过封装CLR属性来达到的。那么RegisterAttached又是怎样的呢?

下面我们来看一个最简单的应用:首先我们注册(构造)一个附加属性

public class AttachedPropertyChildAdder{ //通过使用RegisterAttached来注册一个附加属性 public static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyChildAdder), new FrameworkPropertyMetadata((bool)false)); //通过静态方法的形式暴露读的操作 public static bool GetIsAttached(DependencyObject dpo) { return (bool)dpo.GetValue(IsAttachedProperty); } //通过静态方法的形式暴露写的操作 public static void SetIsAttached(DependencyObject dpo, bool value) { dpo.SetValue(IsAttachedProperty,value); }}

在XAML中就可以使用刚才注册(构造)的附加属性了:

  

在上面的例子中,AttachedPropertyChildAdder 中并没有对IsAttached采用CLR属性形式进行封装,而是使用了静态SetIsAttached方法和GetIsAttached方法来存取IsAttached值,当然如果你了解它内部原理,你就会看到实际上还是调用的SetValue与GetValue来进行操作(只不过拥有者不同而已)。这里我们不继续深入下去,详细在后面的内容会揭开谜底。

十. 清除本地值

在很多时候,由于我们的业务逻辑和UI操作比较复杂,所以一个庞大的页面会进行很多诸如动画、3D、多模板及样式的操作,这个时候页面的值已经都被改变了,如果我们想让它返回默认值,可以用ClearValue 来清除本地值,但是遗憾的是,很多时候由于WPF依赖属性本身的设计,它往往会不尽如人意(依赖属性的优先级以及依赖属性EffectiveValueEntry 的影响)。

ClearValue ()为在元素上设置的依赖项属性中清除任何本地应用的值提供了一个接口。但是,调用 ClearValue 并不能保证注册属性时在元数据中指定的默认值就是新的有效值。值优先级中的所有其他参与者仍然有效。只有在本地设置的值才会从优先级序列中移除。

例如,如果您对同时也由主题样式设置的属性调用 ClearValue,主题值将作为新值而不是基于元数据的默认值进行应用。如果您希望取消过程中的所有属性值,而将值设置为注册的元数据默认值,则可以通过查询依赖项属性的元数据来最终获得默认值,然后使用该默认值在本地设置属性并调用 SetValue来实现,这里我们得感得PropertyMetadata类为我们提供了诸如DefaultValue这样的外部可访问的属性。

上面讲了这么多,现在我们就简单用一个例子来说明上面的原理(例子很直观,相信大家能很容易看懂)

XAML中代码如下:

<Window x:Class="WpfApplication1.DPClearValue" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="400" Width="400"> <StackPanel Name="root"> <StackPanel.Resources> <Style TargetType="Button"> <Setter Property="Height" Value="20"/> <Setter Property="Width" Value="250"/> <Setter Property="HorizontalAlignment" Value="Left"/> </Style> <Style TargetType="Ellipse"> <Setter Property="Height" Value="50"/> <Setter Property="Width" Value="50"/> <Setter Property="Fill" Value="LightBlue"/> </Style> <Style TargetType="Rectangle"> <Setter Property="Height" Value="50"/> <Setter Property="Width" Value="50"/> <Setter Property="Fill" Value="MediumBlue"/> </Style> <Style x:Key="ShapeStyle" TargetType="Shape"> <Setter Property="Fill" Value="Azure"/> </Style> </StackPanel.Resources> <DockPanel Name="myDockPanel"> <Ellipse Height="100" Width="100" Style="{StaticResource ShapeStyle}"/> <Rectangle Height="100" Width="100" Style="{StaticResource ShapeStyle}" /> </DockPanel> <Button Name="RedButton" Click="MakeEverythingAzure"Height="39" Width="193">改变所有的值</Button> <Button Name="ClearButton" Click="RestoreDefaultProperties"Height="34" Width="192"> 清除本地值</Button> </StackPanel></Window>

后台代码:

public partial class DPClearValue { //清除本地值,还原到默认值 void RestoreDefaultProperties(object sender, RoutedEventArgs e) { UIElementCollection uic = myDockPanel.Children; foreach (Shape uie in uic) { LocalValueEnumerator locallySetProperties =uie.GetLocalValueEnumerator(); while (locallySetProperties.MoveNext()) { DependencyProperty propertyToClear = (DependencyProperty)locallySetProperties.Current.Property; if (!propertyToClear.ReadOnly) { uie.ClearValue(propertyToClear); } } } } //修改本地值 void MakeEverythingAzure(object sender, RoutedEventArgs e) { UIElementCollection uic = myDockPanel.Children; foreach (Shape uie in uic) { uie.Fill = new SolidColorBrush(Colors.Azure); } } }

当按下”改变所有的值“按钮的时候,就会把之前的值都进行修改了,这个时候按下”清除本地值“就会使原来的所有默认值生效。

十一. 依赖属性元数据

十二. 依赖属性回调、验证及强制值

十三. 依赖属性监听

十四. 代码段(自动生成)

见原文


十五. 模拟依赖属性实现

古人有”不入虎穴焉得虎子“的名句,我们今天也试着入一入虎穴,探探依赖属性里面到底藏着什么不可告人的秘密。

详见《深入浅出WPF》

十六. 本文总结

依赖属性是WPF的核心概念,首先从依赖属性基本介绍讲起,然后过渡到依赖属性的优先级、附加属性、只读依赖属性、依赖属性元数据、依赖属性回调、验证及强制值、依赖属性监听、代码段(自动生成) 等相关知识,最后我们模拟了一个WPF依赖属性的实现,对内部实现原理进行了一些研究。在接下来的三篇”剖析路由事件”、”剖析命令”、”剖析绑定”也会采用这篇文章的风格,希望能尽量说透,如果有误之处还希望各位能够批评指正!

十七. 相关代码下载

在文章的最后,我们提供代码的下载,这几篇文章最重要的就是下载代码来细细研究,代码里面也添加了比较详细的注释,如果大家有什么问题,也可以和我联系,如果有不正确的地方也希望多多海涵并能给我及时反馈,我将感激不尽!

  上图就是整个代码包的结构图,下载链接:DependencyPropertiesDemo.rar

 

分享给朋友:
您可能感兴趣的文章:
随机阅读: