码迷,mamicode.com
首页 > Windows程序 > 详细

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

时间:2016-04-07 09:27:53      阅读:656      评论:0      收藏:0      [点我收藏+]

标签:

一. 摘要

  首先圣殿骑士非常高兴这个系列能得到大家的关注和支持。这个系列从七月份開始到如今才第七篇,上一篇公布是在8月2日,掐指一算有二十多天没有继续更新了,最主要原因一来是想把它写好,二来是由于近期几个月在筹备“云计算之旅”系列,所以一再推迟了公布进度。

之前一直都没有想过要录制视频。基本的原因还是怕自己知识有限,从而误导他人,所曾经几次浪曦和51CTO邀请录制视频,我都以工作忙、公司内部培训须要时间和自己有待提高等理由委婉的拒绝了,说实在的。自己也知道自己还有非常多地方有待提高。还须要向各位学习,所以这几年都会一直努力,相信总有一天自己头上也会长出两仅仅角的。录制视频的事也就这样不了了之了。直到前一段时间MSDN WebCast的再三邀请。我才决定努力试一试,同一时候也希望各位可以支持。如今都把社区当成自己的坚强后盾了,所以我打算先以博客的形式公布。这样就行先和大家一起讨论,纠正自己的某些错误认识,这样在录制视频的时候就不会误导他人了。

  前几篇我们讲了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等技术,在用这些技术做项目的同一时候积累了较丰富的经验,那么大家就能够形成自己的一套开发知识库。知道这些技术怎么能高速搭建企业所须要的应用、知道这些技术在使用中会出现什么样的问题以及怎样解决。那么在这个时候你就可能已经在团队中起到比較核心的作用,假设项目经理给你一个任务。你也能够非常轻松且高效的胜任。同一时候在项目其中因为你也比較清楚业务逻辑,所以当机会来临的时候。你非常快会成为团队的骨干,逐渐你就会带几个0基础一点的project师一起做项目;假设你不喜欢带团队,你就会成为资深的高级开发或者架构师。

那么在向外方面我个人觉得最重要的是积累经验,对常见的应用要比較熟悉且有自己总结的一套开发库——比方对普通的站点、电子商务系统、ERP、OA、client应用等等有比較丰富的经验。

(二)向内:

  在前面你使用了这些技术开发项目之后。你会遇到非常多问题。为了解决这些问题,你会逐渐研究一些比較底层次的东西。比方对于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那么单纯,而且每一个人的基础都不一样,其中有还没有毕业的实习生、刚毕业不久的毕业生、工作了数年的project师及技术大牛们,所以怎样把这些知识非常好的插入到每一个人的知识树上面成了我考虑的重点。

同一时候我的心里也比較矛盾,一方面希望參加培训的同事多一些。还有一方面希望人越少越好。

前者则是依照常理来考虑的,毕竟培训者都希望自己培训。越受欢迎越好,这样才干使自己的思想得到很多其它人的认可。自己也能实现分享知识的目的。

后者则是操心怕讲不好。少一点人就少一点罪过。但是恰巧这一次是历次培训中最多的一次,来參加培训的同事有一百多人,只是幸好由于会议室坐不下。才分成了两批,这样就能够让我具备了更充分的时间和更好的心态。总之培训是向内和向外的提炼与升华,正所谓“自己理解的知识未必能使人家理解”。这不仅考验的是技术。还考验了一个人的综合能力。

(四)结论:

  前面从向内和向外以及扩展三个方面进行了简单阐述,用一句话概括就是:向内深不可測、向外漫无边际、扩展才干超越极限。

由于这里仅仅是对本文及以下的三篇文章做一些铺垫工作,所以我们也不细细分解。那么我也得略微推荐一点资料才对得起大家:第一还是研究微软的类库,对我们常见的应用进行研究,能够结合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 Web Role

云计算之旅8—ASP.NET MVC Web Role

云计算之旅9—WCF Service Web Role

云计算之旅10—Work Role Castle

云计算之旅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拥有了依赖属性后。代码写起来就比較得心应手,功能实现上也变得很easy了。

假设没有依赖属性,我们将不得不编写大量的代码。关于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!";
        }
    }
}

属性是我们再熟悉只是的了。那么究竟依赖属性怎么写呢?依赖属性和属性究竟有什么差别和联系呢?事实上依赖属性的实现非常easy。仅仅要做下面步骤就能够实现: 
第一步: 让所在类型继承自 DependencyObject基类,在WPF中,我们细致观察框架的类图结构,你会发现差点儿全部的 WPF 控件都间接继承自DependencyObject类型。 
第二步:使用 public static 声明一个 DependencyProperty的变量,该变量才是真正的依赖属性 ,看源代码就知道这里其有用了简单的单例模式的原理进行了封装(构造函数私有),仅仅暴露Register方法给外部调用。

 
第三步:在静态构造函数中完毕依赖属性的元数据注冊,并获取对象引用。看代码就知道是把刚才声明的依赖属性放入到一个类似于容器的地方,没有讲实现原理之前,请容许我先这么陈述。 
第四步:在前面的三步中,我们完毕了一个依赖属性的注冊。那么我们如何才干对这个依赖属性进行读写呢?答案就是提供一个依赖属性的实例化包装属性,通过这个属性来实现详细的读写操作。

依据前面的四步操作,我们就能够写出以下的代码:

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 同意我们能够在多个地方设置依赖属性的值,所以我们就必需要用一个标准来保证值的优先级别。比方以下的样例中,我们在三个地方设置了button的背景颜色,那么哪一个设置才会是终于的结果呢?是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、有些Dependency属性在用注冊的时候时指定Inherits为不可继承,这样继承就会失效了。

2、有其它更优先级的设置设置了该值,在前面讲的的“依赖属性的优先级”你能够看到详细的优先级别。

  这里的原因是部分控件如StatusBar、Tooptip和Menu等内部设置它们的字体属性值以匹配当前系统。

这样用户通过操作系统的控制面板来改动它们的外观。这样的方法存在一个问题:StatusBar等截获了从父元素继承来的属性,而且不影响其子元素。比方,假设我们在StatusBar中加入了一个Button。那么这个Button的字体属性会由于StatusBar的截断而没有不论什么改变,将保留其默认值。所以大家在使用的时候要特别注意这些问题。

技术分享

前面我们看了依赖属性的继承,当我们自己定义的依赖属性,应该怎样处理继承的关系呢? 请看以下的代码(凝视非常具体,我就不再费口水了):

public class MyCustomButton : Button
 {
     static MyCustomButton()
     {
         //通过MyStackPanel依赖属性MinDateProperty的AddOwner方式实现继承,注意FrameworkPropertyMetadataOptions的值为Inherits
         MinDateProperty = MyStackPanel.MinDateProperty.AddOwner(typeof(MyCustomButton),
         new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
     }

     public static readonly DependencyProperty MinDateProperty;

     public DateTime MinDate
     {
         get { return (DateTime)GetValue(MinDateProperty); }
         set { SetValue(MinDateProperty, value); }
     }
 }


 public class MyStackPanel : StackPanel
 {
     static MyStackPanel()
     {
         //我们在MyStackPanel里面注冊了MinDate,注意FrameworkPropertyMetadataOptions的值为Inherits
         MinDateProperty = DependencyProperty.Register("MinDate",
         typeof(DateTime),
         typeof(MyStackPanel),
         new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
     }

     public static readonly DependencyProperty MinDateProperty;

     public DateTime MinDate
     {
         get { return (DateTime)GetValue(MinDateProperty); }
         set { SetValue(MinDateProperty, value); }
     }
 }

那么就能够在XAML中进行使用了

<Window x:Class="Custom_Inherited_DPs.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Custom_Inherited_DPs" 
    xmlns:sys="clr-namespace:System;assembly=mscorlib"   
    WindowStartupLocation="CenterScreen" 
    Title="使用自己主动以依赖属性继承" Height="300" Width="300">
    <Grid>
        <local:MyStackPanel x:Name="myStackPanel" MinDate="{x:Static sys:DateTime.Now}">
            <!-- myStackPanel的依赖属性 -->
            <ContentPresenter Content="{Binding Path=MinDate, ElementName=myStackPanel}"/>
            <!-- 继承自myStackPanel的依赖属性 -->
            <local:MyCustomButton 
                    Content="{Binding RelativeSource={x:Static RelativeSource.Self},
                    Path=MinDate}" 
                Height="20"/>
        </local:MyStackPanel>
   </Grid>
</Window>    

最后的效果例如以下:

技术分享

八. 仅仅读依赖属性

  我们曾经在对简单属性的封装中。常常会对那些希望暴露给外界仅仅读操作的字段封装成仅仅读属性,相同在WPF中也提供了仅仅读属性的概念。如一些WPF控件的依赖属性是仅仅读的,它们常常常使用于报告控件的状态和信息。像IsMouseOver等属性, 那么在这个时候对它赋值就没有意义了。 也许你也会有这种疑问:为什么不使用一般的.Net属性提供出来呢?一般的属性也能够绑定到元素上呀?这个是由于有些地方必需要用到仅仅读依赖属性,比方Trigger等,同一时候也由于内部可能有多个提供者改动其值。所以用.Net属性就不能完毕天之大任了。 
  那么一个仅仅读依赖属性怎么创建呢?事实上创建一个仅仅读的依赖属性和创建一个一般的依赖属性大同小异(研究源代码你会发现。其内部都是调用的同一个Register方法)。仅仅是用DependencyProperty.RegisterReadonly替换了DependencyProperty.DependencyProperty而已。和前面的普通依赖属性一样。它将返回一个DependencyPropertyKey。该键值在类的内部暴露一个赋值的入口,同一时候仅仅提供一个GetValue给外部,这样便能够像一般属性一样使用了,仅仅是不能在外部设置它的值罢了。

以下我们就用一个简单的样例来概括一下:

public partial class Window1 : Window
 {
     public Window1()
     {
         InitializeComponent();

         //内部用SetValue的方式来设置值
         DispatcherTimer timer =
             new DispatcherTimer(TimeSpan.FromSeconds(1),
                                 DispatcherPriority.Normal,
                                 (object sender, EventArgs e)=>
                                 {
                                     int newValue = Counter == int.MaxValue ?

0 : Counter + 1; SetValue(counterKey, newValue); }, Dispatcher); } //属性包装器。仅仅提供GetValue,这里你也能够设置一个private的SetValue进行限制 public int Counter { get { return (int)GetValue(counterKey.DependencyProperty); } } //用RegisterReadOnly来取代Register来注冊一个仅仅读的依赖属性 private static readonly DependencyPropertyKey counterKey = DependencyProperty.RegisterReadOnly("Counter", typeof(int), typeof(Window1), new PropertyMetadata(0)); }

   
XAML中代码:

<Window x:Name="winThis" x:Class="WpfApplication1.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Read-Only Dependency Property" Height="300" Width="300">
    <Grid>
        <Viewbox>
            <TextBlock Text="{Binding ElementName=winThis, Path=Counter}" />
        </Viewbox>
    </Grid>
</Window>

效果例如以下图所看到的: 

技术分享

九. 附加属性

  前面我们讲了依赖属性。如今我们再继续探讨第二种特殊的Dependency属性——附加属性。附加属性是一种特殊的依赖属性。他同意给一个对象加入一个值。而该对象可能对此值一无所知。

  最好的样例就是布局面板。每个布局面板都须要自己特有的方式来组织它的子元素。如Canvas须要Top和left来布局,DockPanel须要Dock来布局。当然你也能够写自己的布局面板(在上一篇文章中我们对布局进行了比較仔细的探讨。假设有不清楚的朋友也能够再回想一下)。

以下代码中的Button 就是用了CanvasCanvas.TopCanvas.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这种外部可訪问的属性。

上面讲了这么多。如今我们就简单用一个样例来说明上面的原理(样例非常直观,相信大家能非常easy看懂)

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); }
     }
 }

当按下”改变全部的值“button的时候。就会把之前的值都进行改动了。这个时候按下”清除本地值“就会使原来的全部默认值生效。

技术分享

十一. 依赖属性元数据

前面我们看到一个依赖属性的注冊最全的形式是以下这样子的:

public static DependencyProperty Register(string name, 
                                          Type propertyType,
                                          Type ownerType, 
                                          PropertyMetadata typeMetadata,
                                          ValidateValueCallback validateValueCallback);

  第一个參数是该依赖属性的名字,第二个參数是依赖属性的类型,第三个參数是该依赖属性的全部者的类型,第五个參数就是一个验证值的回调托付,那么最使我们感兴趣的还是这个可爱的 PropertyMetadata ,也就是我们接下来要讲的元数据。 提到WPF属性元数据,大家可能第一想到的是刚才的PropertyMetadata,那么这个类究竟是如何的呢?我们应该如何使用它呢?首先我们看它的构造函数(我们选參数最多的来讲):

public PropertyMetadata(object defaultValue,
                        PropertyChangedCallback propertyChangedCallback, 
                        CoerceValueCallback coerceValueCallback);

  当中的第一个參数是默认值,最后两个各自是PropertyChanged(变化通知)以及Coerce(强制)的两个托付变量,我们在实例化的时候,仅仅须要把这两个托付变量关联到详细的方法上就可以。

  其实。除了PropertyMetadata以外,常见的还有FrameworkPropertyMetadata,UIPropertyMetadata。他们的继承关系是F->U->P。

当中以FrameworkPropertyMetadata參数最多,亦最为复杂。

FrameworkPropertyMetadata的构造函数提供了非常多重载,我们挑选最为复杂的重载来看它究竟有哪些參数以及提供了哪些功能:

public FrameworkPropertyMetadata(object defaultValue,
                                 FrameworkPropertyMetadataOptions flags,
                                 PropertyChangedCallback propertyChangedCallback, 
                                 CoerceValueCallback coerceValueCallback,
                                 bool isAnimationProhibited,
                                 UpdateSourceTrigger defaultUpdateSourceTrigger);

  其中第一个參数是默认值,最后两个參数各自是是否同意动画。以及绑定时更新的策略(在Binding其中相信大家并不陌生)。这个不详解了。重点看一下里第三、四两个參数,两个 CallBack的托付。

结合前面Register的时候提到的ValidateValueCallback共组成三大”金刚“,这三个Callback分别代表Validate(验证),PropertyChanged(变化通知)以及Coerce(强制)。当然,作为 Metadata,FrameworkPropertyMetadata仅仅是储存了该依赖属性的策略信息。WPF属性系统会依据这些信息来提供功能并在适当的时机回调传入的delegate,所以最重要的还是我们定义的这些方法,通过他们传入托付才干起到真正的作用。

上面讲了元数据暴露给我们的构造函数,事实上在其内部还提供了两个方法,这个在做自己定义控件的时候,也非常值得注意:

protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
    // 实现元数据继承之间的合并
}

protected virtual void OnApply(DependencyProperty dependencyProperty, Type targetType)
{
    // 当元数据被这个属性应用。OnApply就会被触发,在此时元数据也将被密封起来。
}

前面讲了这么多,那么我们如今就来看看依赖属性回调、验证及强制值究竟是怎么使用的呢?大家千万要坚持住,后面内容更加精彩!

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

我们通过以下的这幅图,简介一下WPF属性系统对依赖属性操作的基本步骤:

技术分享

  • 第一步。基础值就是上面“六.依赖属性的优先级”提供的那些显示设置。所以它的优先级比較好确定。但有些不会按常规出牌,所以也须要注意总结。
  • 第二步。假设依赖属性值是计算表达式 (如前面演示样例中的绑定等语法特性),这个时候就会计算表达式的结果作为第二步的值。
  • 第三步。动画是一种优先级非常高的特殊行为,非常多时候,我们都会听到动画优先的声音。所以它的优先级高于其它基础设置。
  • 第四步,强制则是注冊时提供的 CoerceValueCallback 托付,它负责验证属性值是否在同意的限制范围之内,和我们对属性的验证一样,比方强制设置该值必须大于于0小于10等等。
  • 第五步,验证是指我们注冊依赖属性所提供的 ValidateValueCallback 托付方法。它终于决定了属性值设置是否有效。当数据无效时会抛出异常来通知。

前面我们讲了主要的流程,以下我们就用一个小的样例来进行说明:

namespace SampleProcess_DPs
{
    class Program
    {
        static void Main(string[] args)
        {
            SimpleDPClass sDPClass = new SimpleDPClass();
            sDPClass.SimpleDP = 8;
            Console.ReadLine();
        }
    }

    public class SimpleDPClass : DependencyObject
    {
        public static readonly DependencyProperty SimpleDPProperty =
            DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
                new FrameworkPropertyMetadata((double)0.0,
                    FrameworkPropertyMetadataOptions.None,
                    new PropertyChangedCallback(OnValueChanged),
                    new CoerceValueCallback(CoerceValue)),
                    new ValidateValueCallback(IsValidValue));

        public double SimpleDP
        {
            get { return (double)GetValue(SimpleDPProperty); }
            set { SetValue(SimpleDPProperty, value); }
        }

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine("当值改变时,我们能够做的一些操作,详细能够在这里定义: {0}", e.NewValue);
        }

        private static object CoerceValue(DependencyObject d, object value)
        {
            Console.WriteLine("对值进行限定。强制值: {0}", value);
            return value;
        }

        private static bool IsValidValue(object value)
        {
            Console.WriteLine("验证值是否通过,返回bool值,假设返回True表示严重通过,否则会以异常的形式暴露: {0}", value);
            return true;
        }

    }
}

结果例如以下:

技术分享

  当SimpleDP属性变化之后,PropertyChangeCallback就会被调用。

能够看到结果并没有全然依照我们先前的流程先Coerce后Validate的顺序运行,有可能是WPF内部做了什么特殊处理,当属性被改动时,首先会调用Validate来推断传入的value是否有效,假设无效就不继续兴许的操作,这样能够更好的优化性能。从上面的结果上看出。CoerceValue后面并没有马上ValidateValue,而是直接调用了PropertyChanged。

这是由于前面已经验证过了value,假设在Coerce中没有改变value,那么就不用再验证了。

假设在 Coerce中改变了value。那么这里还会再次调用ValidateValue操作,和前面的流程图运行的顺序一样,在最后我们会调用ValidateValue来进行最后的验证,这就保证最后的结果是我们希望的那样了(正如打游戏一样,打了小怪。在最后过总关的时候还是须要打大怪才干闯关的)。

  上面简介了处理流程,以下我们就以一个案例来详细看一看上面的流程究竟有没有出入。这个样例改编于Sacha Barber 的Dependency Properties代码演示样例。我相信通过这段代码你会对这个上面讲的概念有更清晰地认识。

  UI非常easy,黄色部分显示当前值,我们在初始化的时候把它设置为100。然后它的最小值和最大值分别设置为0和500。button”设置为-100“企图把当前值设为-100。button”设置为1000“试图把当前值设为1000。详细大家看代码(我都写了凝视,非常容易理解的).

技术分享

依赖属性代码文件例如以下:

namespace Callback_Validation_DPs
{
    public class Gauge : Control
    {
        public Gauge() : base() { }
        //注冊CurrentReading依赖属性,并加入PropertyChanged、CoerceValue、ValidateValue的回调托付
        public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
            "CurrentReading",
            typeof(double),
            typeof(Gauge),
            new FrameworkPropertyMetadata(
                Double.NaN,
                FrameworkPropertyMetadataOptions.None,
                new PropertyChangedCallback(OnCurrentReadingChanged),
                new CoerceValueCallback(CoerceCurrentReading)
            ),
            new ValidateValueCallback(IsValidReading)
        );

        //属性包装器,通过它来暴露CurrentReading的值
        public double CurrentReading
        {
            get { return (double)GetValue(CurrentReadingProperty); }
            set { SetValue(CurrentReadingProperty, value); }
        }

        //注冊MinReading依赖属性,并加入PropertyChanged、CoerceValue、ValidateValue的回调托付
        public static readonly DependencyProperty MinReadingProperty = DependencyProperty.Register(
        "MinReading",
        typeof(double),
        typeof(Gauge),
        new FrameworkPropertyMetadata(
            double.NaN,
            FrameworkPropertyMetadataOptions.None,
            new PropertyChangedCallback(OnMinReadingChanged),
            new CoerceValueCallback(CoerceMinReading)
        ),
        new ValidateValueCallback(IsValidReading));

        //属性包装器。通过它来暴露MinReading的值
        public double MinReading
        {
            get { return (double)GetValue(MinReadingProperty); }
            set { SetValue(MinReadingProperty, value); }
        }

        //注冊MaxReading依赖属性,并加入PropertyChanged、CoerceValue、ValidateValue的回调托付
        public static readonly DependencyProperty MaxReadingProperty = DependencyProperty.Register(
            "MaxReading",
            typeof(double),
            typeof(Gauge),
            new FrameworkPropertyMetadata(
                double.NaN,
                FrameworkPropertyMetadataOptions.None,
                new PropertyChangedCallback(OnMaxReadingChanged),
                new CoerceValueCallback(CoerceMaxReading)
            ),
            new ValidateValueCallback(IsValidReading)
        );

        //属性包装器,通过它来暴露MaxReading的值
        public double MaxReading
        {
            get { return (double)GetValue(MaxReadingProperty); }
            set { SetValue(MaxReadingProperty, value); }
        }

        //在CoerceCurrentReading加入强制推断赋值
        private static object CoerceCurrentReading(DependencyObject d, object value)
        {
            Gauge g = (Gauge)d;
            double current = (double)value;
            if (current < g.MinReading) current = g.MinReading;
            if (current > g.MaxReading) current = g.MaxReading;
            return current;
        }


        //当CurrentReading值改变的时候。调用MinReading和MaxReading的CoerceValue回调托付
        private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            d.CoerceValue(MinReadingProperty);  
            d.CoerceValue(MaxReadingProperty); 
        }

        //当OnMinReading值改变的时候,调用CurrentReading和MaxReading的CoerceValue回调托付
        private static void OnMinReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            d.CoerceValue(MaxReadingProperty);
            d.CoerceValue(CurrentReadingProperty);
        }

        //在CoerceMinReading加入强制推断赋值
        private static object CoerceMinReading(DependencyObject d, object value)
        {
            Gauge g = (Gauge)d;
            double min = (double)value;
            if (min > g.MaxReading) min = g.MaxReading;
            return min;
        }

        //在CoerceMaxReading加入强制推断赋值
        private static object CoerceMaxReading(DependencyObject d, object value)
        {
            Gauge g = (Gauge)d;
            double max = (double)value;
            if (max < g.MinReading) max = g.MinReading;
            return max;
        }

        //当MaxReading值改变的时候,调用MinReading和CurrentReading的CoerceValue回调托付
        private static void OnMaxReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            d.CoerceValue(MinReadingProperty);  
            d.CoerceValue(CurrentReadingProperty); 
        }

        //验证value是否有效,假设返回True表示验证通过,否则会提示异常
        public static bool IsValidReading(object value)
        {
            Double v = (Double)value;
            return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
       }
    }
}

XAML代码例如以下:

<Window x:Class="Callback_Validation_DPs.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Callback_Validation_DPs" 
    WindowStartupLocation="CenterScreen" 
    Title="Callback_Validation_DPs" Height="400" Width="400">
    <StackPanel Orientation="Vertical">
        <local:Gauge x:Name="gauge1" MaxReading="100" MinReading="0" />
        <Label Content="能够设置最小值为0和最小大值为500" Height="30"/>
        <StackPanel Orientation="Horizontal" Height="60">
            <Label Content="当前值为 : "/>
            <Label Background="Yellow" BorderBrush="Black" BorderThickness="1" 
                   IsEnabled="False" Content="{Binding ElementName=gauge1, Path=CurrentReading}" Height="25" VerticalAlignment="Top" />
        </StackPanel>
        <Button x:Name="btnSetBelowMin" Content="设置为 -100" 
                Click="btnSetBelowMin_Click"/>
        <Button x:Name="btnSetAboveMax" Content="设置为 1000" 
                Click="btnSetAboveMax_Click"/>
    </StackPanel>
</Window>

XAML的后台代码例如以下:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        //设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!
        gauge1.CurrentReading = 100;
    }

    private void btnSetBelowMin_Click(object sender, RoutedEventArgs e)
    {
        //设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!

gauge1.CurrentReading = -100; } private void btnSetAboveMax_Click(object sender, RoutedEventArgs e) { //设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧! gauge1.CurrentReading = 10000; } }

  在上面的样例中,一共同拥有三个依赖属性相互作用——CurrentReading、MinReading和MaxReading。这些属性相互作用。但它们的规则是MinReading≤CurrentReading≤MaxReading。

依据这个规则。当当中一个依赖属性变化时。另外两个依赖属性必须进行适当的调整。这里我们要用到的就是CoerceValue这个回调托付。那么实现起来也很的简单,注冊MaxReading的时候增加CoerceValueCallback,在CoerceMaxReading函数中做处理:假设Maximum的值小于MinReading。则使MaxReading值等于MinReading。同理在CurrentReading中也增加了CoerceValueCallback进行对应的强制处理。

然后在MinReading的ChangedValueCallback被调用的时候,调用CurrentReading和MaxReading的CoerceValue回调托付。这样就能够达到相互作用的依赖属性一变应万变的”千机变“。

  换句话说,当相互作用的几个依赖属性当中一个发生变化时,在它的PropertyChangeCallback中调用受它影响的依赖属性的CoerceValue。这样才干保证相互作用关系的正确性。

前面也提高ValidateValue主要是验证该数据的有效性,最设置了值以后都会调用它来进行验证,假设验证不成功,则抛出异常。

十三. 依赖属性监听

假设想监听依赖属性的改变,能够用两种方法实现,在非常多时候。我们两种方法都会用到: 
用DependencyPropertyDescriptor 比較简便。在代码里面写起来也比較便捷; 
用OverrideMetadata的方式主要在自己定义控件以及处理一些类间关系的时候。 
第一种方法:派生自这个类。然后定义它的属性,重写属性的原数据并传递一个PropertyChangedCallBack參数就可以,例如以下代码:

public class MyTextBox : TextBox
{
    public MyTextBox(): base()
    {
    }

    static MyTextBox()
    {
        //第一种方法。通过OverrideMetadata
        FlowDirectionProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(FlowDirectionPropertyChanged)));
    }

    private static void FlowDirectionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        ((MyTextBox)sender).FontWeight = (((MyTextBox)sender).FlowDirection == FlowDirection.LeftToRight) ?

FontWeights.Bold : FontWeights.Normal; } }

另外一种方法:这种方法更加简单,获取DependencyPropertyDescriptor并调用AddValueChange()为其挂接一个回调函数,例如以下代码:

private void Window1_Loaded(object sender, RoutedEventArgs e)
 {
     //另外一种方法,通过OverrideMetadata
     DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
     descriptor.AddValueChanged(tbxEditMe, tbxEditMe_TextChanged);
 }

 private void tbxEditMe_TextChanged(object sender, EventArgs e)
 {
     MessageBox.Show("", "Changed");
 }

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

  代码段能够说是一个很普遍且有用的功能,我们能够利用它来简化和规范我们的代码。在项目其中我们一般会定义大量的代码段。如如何写一个类、如何定义一个方法、公用代码库等等都能够定义成代码段。今天不着重讲这一块,以下就来看看在默认的VS中有哪些代码段

技术分享

  上面看到的是visual basic的代码段(一不小心截图截错了。呵呵)。但不幸的是针对C#的代码段却非常少。只是没关系,既然默认没有提供那么多代码段,我们能够自己动手嘛,正所谓自己动手丰衣足食嘛!

相信大家都有自己定义代码段的经历,同一时候在网上也有非常多好的代码段下载,我用得最多的是DrWPFSnippets,因为接触WPF和Silverlight是在07年,所以当时自己也定义过一些代码段,因为自己主要精力还是在技术架构、ASP.NET、WCF、OO等方面。所以在08年以后就開始使用网上的代码段资源了,当然因为之前项目也自己写了一些代码段,所以非常多时候都是混合起来使用。大家能够到http://drwpf.com/blog/2010/04/30/updated-code-snippets/去下载,这个代码段包最早从2007年11月就提供下载了,在今年四月份进行了升级,同一时候支持VS2005/VS2008/VS2010,所以大家能够下载下来体验一下。非常不错的哦!下载以后点击DrWPFSnippets.vsi就会自己主动安装,安装完毕以后。你会看到例如以下界面。图中的Shortcut就是你要按的快捷键。只是生成的代码会出现有些帮助类找不到的情况,如RoutedEvent会生成一个RoutedEventHelper的类,这个是没有关系的,你到网上一搜就能够把这个类增加到你的代码其中。

那么执行就十分正常了。

在安装的时候提醒一下,最好一次成功安装,否则你会为众多的弹窗体感到十分厌恶。呵呵。

技术分享

那么如今你就能够在项目其中使用了,如按下re+TAB键两次,你就会看到例如以下界面,然后选择你的选项就可以生成须要的代码(这里re就是Routed event的简写)。

技术分享

例如以下是生成的代码,你能够直接使用或者经过适当改动使用。

    #region Swindle

/// <summary>
/// Swindle Routed Event
/// </summary>
public static readonly RoutedEvent SwindleEvent = EventManager.RegisterRoutedEvent("Swindle", 
    RoutingStrategy.Bubble, typeof(TrioEventHandler), typeof(Window1));

/// <summary>
/// Occurs when ...
/// </summary>
public event TrioEventHandler Swindle

{
    add { AddHandler(SwindleEvent, value); }
    remove { RemoveHandler(SwindleEvent, value); }
}

/// <summary>
/// A helper method to raise the Swindle event.
/// </summary>
/// <param name="arg"> </param>
/// <param name="arg2"> </param>
/// <param name="arg3"> </param>
protected TrioEventArgs RaiseSwindleEvent(bool arg, bool arg2, bool arg3)
{
    return RaiseSwindleEvent(this, arg, arg2, arg3);
}

/// <summary>
/// A static helper method to raise the Swindle event on a target element.
/// </summary>
/// <param name="target">UIElement or ContentElement on which to raise the event</param>
/// <param name="arg"> </param>
/// <param name="arg2"> </param>
/// <param name="arg3"> </param>
internal static TrioEventArgs RaiseSwindleEvent(DependencyObject target, bool arg, bool arg2, bool arg3)
{
    if (target == null) return null;
    
    TrioEventArgs args = new TrioEventArgs(arg, arg2, arg3);
    args.RoutedEvent = SwindleEvent;
    RoutedEventHelper.RaiseEvent(target, args);
    return args;
}

#endregion

十五. 模拟依赖属性实现

  古人有”不入虎穴焉得虎子“的名句。我们今天也试着入一入虎穴。探探依赖属性里面究竟藏着什么不可告人的秘密,在往下讲之前,我们先来看一下DependencyObject DependencyProperty 以及PropertyMetadata究竟包括哪些功能,如以下三幅图

技术分享

 

技术分享

 

技术分享

  通过前面三幅图。我们就能够了解WPF依赖属性系统的大体结构以及主要功能。再者通过前面我们对它的使用,对它的内部实现也有一个相对照较清晰的认识。那么接下来要做的就是:借助Reflector+VS调试内部代码功能一起来研究其内部的实现原理。 本来想具体写清晰开发的过程,可是有点多。所以我打算直接讲这几个类。大家也能够通过这个思路来试一试,同一时候还能够參考Mono的源代码、WF的依赖属性源代码等。这里要推荐的是周永恒的博客。此人对技术的理解非常是透彻,博文虽少。但每篇都堪称经典,所以他的文章,我都通读三遍。尽管大多概念都懂。而且读到深处也能产生共鸣,其最主要目的还是学习他这样的”阐述问题的思路“。后来也和此人MSN聊过几次。所以这个依赖属性的框架在某些程度上也借鉴了他的一些写法。

  有了前面的思路,首先定义DependencyProperty这个类。它里面存储前面我们提到希望抽出来的字段。

DependencyProperty内部维护了一个全局的Map用来储存全部的DependencyProperty。对外暴露了一个Register方法用来注冊新的DependencyProperty

当然,为了保证在Map中键值唯一,注冊时须要依据传入的名字和注冊类的的 HashCode取异或来生成Key。 所以我们就能够完毕DependencyProperty类了。代码例如以下,介绍详见代码凝视。

public sealed class DependencyProperty
 {
     //全局的IDictionary用来储存全部的DependencyProperty
     internal static IDictionary<int, DependencyProperty> properties = new Dictionary<int, DependencyProperty>();
     //存储元数据的集合
     private List<PropertyMetadata> _metadataMap = new List<PropertyMetadata>();
     private static int globalIndex = 0;
     private PropertyMetadata def_metadata;
     private bool attached;
     private string name;
     private int _index;
     private Type owner_type;
     private Type property_type;
     private Type validator_type;

     // 构造函数
     private DependencyProperty()
     {

     }

     //构造函数私有,保证外界不会对它进行实例化
     private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata)
     {
         this.name = name;
         property_type = propertyType;
         owner_type = ownerType;
         def_metadata = defaultMetadata;
     }

     // 经常使用属性
     public PropertyMetadata DefaultMetadata
     {
         get { return def_metadata; }
     }

     public bool IsAttached
     {
         get { return attached; }
     }

     public int Index
     {
         get { return _index; }
         set { _index = value; }
     }

     public string Name
     {
         get { return name; }
     }

     public Type OwnerType
     {
         get { return owner_type; }
     }

     public Type PropertyType
     {
         get { return property_type; }
     }

     public Type ValidatorType
     {
         get { return validator_type; }
     }


     public override int GetHashCode()
     {
         return name.GetHashCode() ^ owner_type.GetHashCode();
     }

     //注冊依赖属性
     public static DependencyProperty Register(string name, Type propertyType, Type ownerType)
     {
         return Register(name, propertyType, ownerType, new PropertyMetadata());
     }

     //注冊的公用方法,把这个依赖属性加入到IDictionary的键值集合中。Key为name和owner_type的GetHashCode取异,Value就是我们注冊的DependencyProperty
     public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata)
     {
         DependencyProperty property = new DependencyProperty(name, propertyType, ownerType, defaultMetadata);
         globalIndex++;
         property.Index = globalIndex;

         if (properties.ContainsKey(property.GetHashCode()))
         {
             throw new InvalidOperationException("A property with the same name already exists");
         }

         //把刚实例化的DependencyProperty加入到这个全局的IDictionary种
         properties.Add(property.GetHashCode(), property);
         return property;
     }

     //注冊仅仅读依赖属性
     public static DependencyProperty RegisterReadOnly(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata)
     {
         DependencyProperty property = Register(name, propertyType, ownerType, typeMetadata);
         return property;
     }

     //注冊附加依赖属性
     public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType)
     {
         return RegisterAttached(name, propertyType, ownerType, new PropertyMetadata(), null);
     }

     public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata)
     {
         return RegisterAttached(name, propertyType, ownerType, defaultMetadata, null);
     }

     public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, Type validatorType)
     {
         DependencyProperty property = Register(name, propertyType, ownerType, defaultMetadata);
         property.attached = true;
         property.validator_type = validatorType;
         return property;
     }

     //子类继承重写以及其它须要重写Metadata的时候使用
     public void OverrideMetadata(Type forType, PropertyMetadata metadata)
     {
         metadata.Type = forType;
         _metadataMap.Add(metadata);
     }

     //获取元数据信息
     public PropertyMetadata GetMetadata(Type type)
     {
         PropertyMetadata medatata = _metadataMap.FirstOrDefault((i) => i.Type == type) ??

_metadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type)); if (medatata == null) { medatata = def_metadata; } return medatata; } }

  有了DependencyProperty 。那么接下来就须要定义DependencyObject 来使用这个DependencyProperty 。首先使用DependencyProperty .Register方法注冊了一个新的DependencyProperty 。然后提供了GetValue和SetValue两个方法来操作刚刚构造的DependencyProperty 

这个时候我们看到一个简单的依赖属性系统已初见端倪了,详见代码凝视。

namespace Realize_DPs
{
    public abstract class DependencyObject :  IDisposable
    {
        //加入一个List来记录修改信息
        private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();

        //属性包装器,通过它来訪问依赖属性
        public object GetValue(DependencyProperty dp)
        {
            //首先通过推断是否修改过,以此来决定是读元数据的默认值还是修改了的值
            EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
            if (effectiveValue.PropertyIndex != 0)
            {
                return effectiveValue.Value;
            }
            else
            {
                PropertyMetadata metadata;
                metadata = DependencyProperty.properties[dp.GetHashCode()].DefaultMetadata;
                return metadata.DefaultValue;
            }
        }

        //属性包装器,通过它来设置依赖属性的值
        public void SetValue(DependencyProperty dp, object value)
        {
            //首先通过推断是否修改过,以及修改过,则继续对修改过的元素赋值。否则对_effectiveValues添加元素
            EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
            if (effectiveValue.PropertyIndex != 0)
            {
                effectiveValue.Value = value;
            }
            else
            {
                effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index, Value = value };
                _effectiveValues.Add(effectiveValue);
            }
        }

        public void Dispose()
        {
            //临时还没有处理
        }
    }

    internal struct EffectiveValueEntry
    {
        internal int PropertyIndex { get; set; }
        internal object Value { get; set; }
    }
}

  前面有了DependencyProperty DependencyObject 类,那我们如今来新建一个比較重要的类 PropertyMetadata ,它的作用和功能非常强大,我们这里仅仅是简单进行了构建,例如以下代码:

namespace Realize_DPs
{
    public delegate void SetValueOverride(DependencyObject d, object value);

    public delegate object GetValueOverride(DependencyObject d);

    public class PropertyMetadata
    {
        private object default_value;
        private DependencyPropertyOptions options = DependencyPropertyOptions.Default;
        private bool _sealed = false;
        private SetValueOverride set_value;
        private GetValueOverride get_value;
        private Attribute[] attributes;
        private Type type;

        // 构造函数重载
        public PropertyMetadata()
        {

        }

        public PropertyMetadata(object defaultValue)
        {
            default_value = defaultValue;
        }

        public PropertyMetadata(DependencyPropertyOptions options)
        {
            this.options = options;
        }

        public PropertyMetadata(params Attribute[] attributes)
        {
            this.attributes = attributes;
        }

        public PropertyMetadata(object defaultValue, params Attribute[] attributes)
        {
            default_value = defaultValue;
            this.attributes = attributes;
        }

        public PropertyMetadata(object defaultValue, DependencyPropertyOptions options)
        {
            default_value = defaultValue;
            this.options = options;
        }

        public PropertyMetadata(DependencyPropertyOptions options, params Attribute[] attributes)
        {
            this.options = options;
            this.attributes = attributes;
        }

        public PropertyMetadata(object defaultValue, DependencyPropertyOptions options, params Attribute[] attributes)
        {
            this.options = options;
            default_value = defaultValue;
            this.attributes = attributes;
        }

        public PropertyMetadata(object defaultValue, DependencyPropertyOptions options, GetValueOverride getValueOverride, SetValueOverride setValueOverride)
        {
            this.options = options;
            default_value = defaultValue;
            set_value = setValueOverride;
            get_value = getValueOverride;
        }

        public PropertyMetadata(object defaultValue, DependencyPropertyOptions options, GetValueOverride getValueOverride, SetValueOverride setValueOverride, params Attribute[] attributes)
        {
            this.options = options;
            default_value = defaultValue;
            set_value = setValueOverride;
            get_value = getValueOverride;
            this.attributes = attributes;
        }

        // 经常使用属性
        public object DefaultValue
        {
            get { return default_value; }
            set { default_value = value; }
        }

        public GetValueOverride GetValueOverride
        {
            get { return get_value; }
            set { get_value = value; }
        }

        public bool IsMetaProperty
        {
            get { return (options & DependencyPropertyOptions.Metadata) == DependencyPropertyOptions.Metadata; }
        }

        public bool IsNonSerialized
        {
            get { return (options & DependencyPropertyOptions.NonSerialized) == DependencyPropertyOptions.NonSerialized; }
        }

        public bool IsReadOnly
        {
            get { return (options & DependencyPropertyOptions.Readonly) == DependencyPropertyOptions.Readonly; }
        }

        protected bool IsSealed
        {
            get { return _sealed; }
        }

        public DependencyPropertyOptions Options
        {
            get { return options; }
            set { options = value; }
        }

        public SetValueOverride SetValueOverride
        {
            get { return set_value; }
            set { set_value = value; }
        }

        public Type Type
        {
            get { return type; }
            set { type = value; }
        }

        protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
        {
            // 实现元数据继承之间的合并
        }

        protected virtual void OnApply(DependencyProperty dependencyProperty, Type targetType)
        {
            // 当元数据被这个属性应用,OnApply就会被触发,在此时元数据也将被密封起来。
        }
    }
}

前面我们实现了一个简单的依赖属性系统,如今就得先測试一下其功能,代码例如以下:

class Program : DependencyObject
{
    public static readonly DependencyProperty CounterProperty;
    static Program()
    {
        //注冊依赖属性Counter
        CounterProperty = DependencyProperty.Register("Counter",
                                            typeof(double),
                                            typeof(Program),
                                            new PropertyMetadata(8.0));
    }

    //属性包装器,暴露读写接口
    public double Counter
    {
        get { return (double)GetValue(CounterProperty); }
        set {SetValue(CounterProperty, value); }
    }

    static void Main(string[] args)
    {
        Program pro = new Program();
        Console.WriteLine("读取元数据设置的默认值: "+pro.Counter.ToString());

        Program pro2 = new Program();
        pro2.Counter = 22.5;
        Console.WriteLine("通过SetValue设置改变了的值: " + pro2.Counter.ToString());
        Console.ReadLine();
    }
}

那么測试结果为:

技术分享

利用VS自带的类图,能够看到刚才我们实现的这个依赖属性类及类之间的关系图: 技术分享

因为上面的代码在非常多方面都非常粗糙,所以希望大家能下载代码进行改造,同一时候也希望给出反馈。

十六. 本文总结

  这篇文章洋洋洒洒写了非常多,我们如今简单回想一下:在开篇之前我们会先介绍比本篇更重要的一些东西。然后插播了一段”云计算之旅“的广告(广告费非常昂贵技术分享,所以格外小心),作为近期几个月执着研究的东西,最终能够在下周和大家见面了,所以心中甚是喜悦。

在前面的两个内容之后我们正式进入本篇的主题——依赖属性。依赖属性是WPF的核心概念。所以我们花费了大量的时间和篇幅进行论述,首先从依赖属性基本介绍讲起,然后过渡到依赖属性的优先级、附加属性、仅仅读依赖属性、依赖属性元数据、依赖属性回调、验证及强制值、依赖属性监听、代码段(自己主动生成) 等相关知识,最后我们模拟了一个WPF依赖属性的实现,对内部实现原理进行了一些研究。

在接下来的三篇”剖析路由事件”、”剖析命令”、”剖析绑定”也会採用这篇文章的风格。希望能尽量说透,假设有误之处还希望各位可以批评指正!

十七. 相关代码下载

  在文章的最后。我们提供代码的下载,这几篇文章最重要的就是下载代码来细细研究,代码里面也加入了比較具体的凝视。假设大家有什么问题,也能够和我联系,假设有不对的地方也希望多多海涵并能给我及时反馈,我将感激不尽!

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

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

标签:

原文地址:http://www.cnblogs.com/bhlsheji/p/5362070.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!