|
一. 摘要
首先圣殿騎士很高興這個(gè)系列能得到大家的關(guān)注和支持,這個(gè)系列從七月份開始到現(xiàn)在才第七篇,上一篇發(fā)布是在8月2日,掐指一算有二十多天沒有繼續(xù)更新了,最主要原因一來是想把它寫好,二來是因?yàn)樽罱鼛讉€(gè)月在籌備“云計(jì)算之旅”系列,所以一再推遲了發(fā)布進(jìn)度。之前一直都沒有想過要錄制視頻,主要的原因還是怕自己知識(shí)有限,從而誤導(dǎo)他人,所以前幾次浪曦和51CTO邀請(qǐng)錄制視頻,我都以工作忙、公司內(nèi)部培訓(xùn)需要時(shí)間和自己有待提高等理由委婉的拒絕了,說實(shí)在的,自己也知道自己還有很多地方有待提高,還需要向各位學(xué)習(xí),所以這幾年都會(huì)一直努力,相信總有一天自己頭上也會(huì)長(zhǎng)出兩只角的。錄制視頻的事也就這樣不了了之了,直到前一段時(shí)間MSDN WebCast的再三邀請(qǐng),我才決定努力試一試,同時(shí)也希望各位能夠支持,現(xiàn)在都把社區(qū)當(dāng)成自己的堅(jiān)強(qiáng)后盾了,所以我打算先以博客的形式發(fā)布,這樣就可以先和大家一起討論,糾正自己的某些錯(cuò)誤認(rèn)識(shí),這樣在錄制視頻的時(shí)候就不會(huì)誤導(dǎo)他人了。
前幾篇我們講了WPF的一些基本知識(shí),但是始終沒有接觸最核心的概念,那么從這篇文章開始的下面幾篇文章中,我們會(huì)分別深入討論一下依賴屬性、路由事件、命令和綁定等相關(guān)概念,希望這幾篇文章對(duì)大家能有所幫助。由于自己才疏學(xué)淺且是對(duì)這些技術(shù)的使用總結(jié)和心得體會(huì),錯(cuò)誤之處在所難免,懷著技術(shù)交流的心態(tài),在這里發(fā)表出來,所以也希望大家能夠多多指點(diǎn),這樣在使一部分人受益的同時(shí)也能糾正我的錯(cuò)誤觀點(diǎn),以便和各位共同提高。
這篇文章比較多,在開篇之前我們會(huì)先介紹比本篇更重要的一些東西,然后插播一段“云計(jì)算之旅”的廣告( 這里廣告費(fèi)比較貴喲!),作為最近幾個(gè)月執(zhí)著研究的東西,終于可以和大家見面了,希望自己能從實(shí)踐中深入淺出的講明白。在前面的兩個(gè)內(nèi)容之后我們正式進(jìn)入本篇的主題——依賴屬性。依賴屬性是WPF的核心概念,所以我們花費(fèi)了大量的時(shí)間和篇幅進(jìn)行論述,首先從依賴屬性基本介紹講起,然后過渡到依賴屬性的優(yōu)先級(jí)、附加屬性、只讀依賴屬性、依賴屬性元數(shù)據(jù)、依賴屬性回調(diào)、驗(yàn)證及強(qiáng)制值、依賴屬性監(jiān)聽、代碼段(自動(dòng)生成) 等相關(guān)知識(shí),最后我們會(huì)模擬一個(gè)WPF依賴屬性的實(shí)現(xiàn),來看看它里面的內(nèi)部究竟是怎樣處理的,這樣就可以幫助我們更好的認(rèn)清它的本質(zhì),出現(xiàn)問題的時(shí)候我們也可以根據(jù)原理快速找到原因了。
二. 本文提綱
· 1.摘要
· 2.本文提綱
· 3.比這篇文章更重要的東西
· 4.云計(jì)算廣告插播
· 5.依賴屬性基本介紹
· 6.依賴屬性的優(yōu)先級(jí)
· 7.依賴屬性的繼承
· 8.只讀依賴屬性
· 9.附加屬性
· 10.清除本地值
· 11.依賴屬性元數(shù)據(jù)
· 12.依賴屬性回調(diào)、驗(yàn)證及強(qiáng)制值
· 13.依賴屬性監(jiān)聽
· 14.代碼段(自動(dòng)生成)
· 15.模擬依賴屬性實(shí)現(xiàn)
· 16.本文總結(jié)
· 17.相關(guān)代碼下載
· 18.系列進(jìn)度
三. 比這篇文章更重要的東西
在講這篇文章之前,我們先來聊一點(diǎn)更重要的東西,正所謂“授人與魚不如授人以漁”,那么我們這個(gè)“漁”究竟是什么呢?大家做軟件也有不少年了,對(duì)自己擅長(zhǎng)的一門或多門技術(shù)都有自己的經(jīng)驗(yàn)和心得,但總的來說可以分為向內(nèi)和向外以及擴(kuò)展三個(gè)方面(這里只針對(duì).NET平臺(tái)):
(一)向外:
會(huì)使用ASP.NET、WinForm、ASP.NET MVC、WPF、Silverlight、WF、WCF等技術(shù),在用這些技術(shù)做項(xiàng)目的同時(shí)積累了較豐富的經(jīng)驗(yàn),那么大家就可以形成自己的一套開發(fā)知識(shí)庫,知道這些技術(shù)怎么能快速搭建企業(yè)所需要的應(yīng)用、知道這些技術(shù)在使用中會(huì)出現(xiàn)什么樣的問題以及如何解決。那么在這個(gè)時(shí)候你就可能已經(jīng)在團(tuán)隊(duì)中起到比較核心的作用,如果項(xiàng)目經(jīng)理給你一個(gè)任務(wù),你也可以很輕松且高效的勝任,同時(shí)在項(xiàng)目當(dāng)中由于你也比較清楚業(yè)務(wù)邏輯,所以當(dāng)機(jī)會(huì)來臨的時(shí)候,你很快會(huì)成為團(tuán)隊(duì)的骨干,逐漸你就會(huì)帶幾個(gè)初級(jí)一點(diǎn)的工程師一起做項(xiàng)目;如果你不喜歡帶團(tuán)隊(duì),你就會(huì)成為資深的高級(jí)開發(fā)或者架構(gòu)師。那么在向外方面我個(gè)人認(rèn)為最重要的是積累經(jīng)驗(yàn),對(duì)常見的應(yīng)用要比較熟悉且有自己總結(jié)的一套開發(fā)庫——比如對(duì)普通的網(wǎng)站、電子商務(wù)系統(tǒng)、ERP、OA、客戶端應(yīng)用等等有比較豐富的經(jīng)驗(yàn)。
(二)向內(nèi):
在前面你使用了這些技術(shù)開發(fā)項(xiàng)目之后,你會(huì)遇到很多問題,為了解決這些問題,你會(huì)逐漸研究一些比較底層次的東西。比如對(duì)于ASP.NET,你會(huì)逐漸的去深入理解ASP.NET的整個(gè)處理過程、頁面的生命周期、自定義控件的開發(fā)等等,由于自己最初是由C、C++、Java這樣過渡到.NET的,所以對(duì)一些細(xì)節(jié)總喜歡鉆牛角尖,這也浪費(fèi)了不少時(shí)間,但同時(shí)也得到了很多意外之喜。
對(duì)于C#語言上,你也會(huì)逐漸去刨根問底,想看看這些語法糖背后到底隱藏著什么秘密,很多對(duì).NET技術(shù)比較癡迷的人都會(huì)選擇對(duì)C# 1.0 語言通過IL代碼來深層次認(rèn)識(shí),然后對(duì)C#2.0、C#3.0、C#4.0都編譯為C# 1.0 來學(xué)習(xí),這樣他們就能認(rèn)識(shí)到語言的內(nèi)部到底是怎么執(zhí)行的,正所謂知道的同時(shí)也知道其所以然。
對(duì)于WF,你不僅要知道各 Activity的使用,你也得知道其內(nèi)部的原理,比如WF 內(nèi)部就是依靠依賴屬性來在工作流中的各 Activity 間傳遞屬性值的,如果你細(xì)心,你還原WF的依賴屬性源碼,你會(huì)發(fā)現(xiàn)它和WPF、Silverlight中的依賴屬性大同小異,原理基本一樣、只是針對(duì)特定技術(shù)進(jìn)行了適當(dāng)?shù)恼{(diào)整。
對(duì)于數(shù)據(jù)底層操作也一樣,不管你是用的拼接SQL、存儲(chǔ)過程、IBATIS.NET、Nhibernate,Active Record,Linq to sql、Entity framework還是自己開發(fā)的ORM組件,你得明白它內(nèi)部的原理,你要知道這些開源的代碼還是很值得研究的,我的經(jīng)驗(yàn)是先熟練使用這些功能,然后再剖析它的源碼,然后自己寫一套自己的框架,現(xiàn)在我也在精簡(jiǎn)自己的ORM框架,因?yàn)橹鞍阎攸c(diǎn)放在了實(shí)現(xiàn)盡可能多的功能,所以對(duì)性能等細(xì)節(jié)沒有做過多優(yōu)化,后面也會(huì)向大家慢慢學(xué)習(xí)。
對(duì)WPF和Silverlight一樣,你不僅要知道怎么用這些技術(shù),你要知道它的原理,比如對(duì)依賴屬性,你知道它的內(nèi)部原理,就可以對(duì)平時(shí)出現(xiàn)的諸如我設(shè)置的值怎么沒有起作用、我Binding的元素怎么沒有出現(xiàn)等等問題; 對(duì)路由事件,你也會(huì)經(jīng)常遇到我的事件怎么沒有執(zhí)行、我的自定義控件事件怎么處理不對(duì)、路由傳遞怎么沒有起作用等等,這個(gè)時(shí)候你如果深入理解了路由事件的內(nèi)部處理,這些問題就迎刃而解了;對(duì)WPF和Silverlight新多出來的命令特性,大家很多時(shí)候也是比較疑惑,也會(huì)遇到命令失效等等問題,其實(shí)如果你深入的了解了它的原理,你就會(huì)知道,它其實(shí)在內(nèi)部也是事件,只不過微軟在里面做了很多封裝而已;對(duì)Binding就更是如此,我們也不想在這篇文章談開去,畢竟在下面的幾篇文章會(huì)詳細(xì)的對(duì)這些技術(shù)進(jìn)行涉及。
(三)擴(kuò)展:
通過前面的向內(nèi)和向外的修煉以后,接下來要做的就是不斷實(shí)踐,不斷總結(jié)經(jīng)驗(yàn),在這個(gè)過程中更重要的是要懂得分享,有分享才會(huì)使自己和他人共同提高,有分享才能讓自己擺脫狂妄的井底之蛙思想,還記得自己剛做技術(shù)的一兩年里,天天喜歡提及大型架構(gòu)、大型數(shù)據(jù)處理、操作系統(tǒng)底層代碼如何如何,甚至把AOP、IOC、SSH、OO及設(shè)計(jì)模式、SOA等詞語時(shí)常掛在嘴邊,生怕別人不知道自己不懂。但隨著自己技術(shù)實(shí)質(zhì)上的提高以及經(jīng)驗(yàn)的積累,自己也就逐漸成熟起來,對(duì)這些技術(shù)逐漸深入理解且理解了其內(nèi)部實(shí)現(xiàn)原理,這樣反而自己變得謙虛起來了,對(duì)之前的那些思想感到無比的羞愧。同時(shí)也明白自己在慢慢成長(zhǎng)了,現(xiàn)在都習(xí)慣戲稱自己為打雜工,其實(shí)更多時(shí)候用打字員會(huì)合理一些,所以希望大家能多多指教,這樣我才能更快地?cái)[脫打字員的生活。我在這里也對(duì)擴(kuò)展做一點(diǎn)小的總結(jié):
記錄學(xué)習(xí):這是學(xué)習(xí)很重要的一步,你不一定要寫技術(shù)博客,你也可以做一些例子來記錄,你也可以在學(xué)習(xí)之后寫一個(gè)總結(jié),畢竟人的精力十分有限,在很多時(shí)候,它并不能像硬盤一樣存儲(chǔ)起來就不會(huì)丟失,更多的時(shí)候它更像一塊內(nèi)存。
同道交流:在這一層里我覺得最重要的就是和一些技術(shù)較好的人成為朋友,和他們經(jīng)常探討一些技術(shù),這樣可以縮短學(xué)習(xí)的周期,同時(shí)也能快速的解決問題,畢竟人的精力十分有限,你沒有遇到過的問題,說不定其他人遇到過。在這方面自己也體會(huì)頗深,也很感謝之前幾個(gè)公司及現(xiàn)在公司的同事、社區(qū)朋友以及一些志同道合之士,感謝你們的指點(diǎn),沒有你們的指點(diǎn),我也不可能從小鳥進(jìn)化成逐鹿程序界的菜鳥,我也為自己能成為一只老菜鳥感到自豪!
少考證、多務(wù)實(shí):在擴(kuò)展的這一層里,我們要謹(jǐn)記不要為了考證而去考證,那樣是沒有任何實(shí)際作用的。對(duì)MVP也一樣,一切順其自然為好,記得大學(xué)時(shí)候身邊就有人連續(xù)四次榮獲MVP稱號(hào),這叫我在當(dāng)時(shí)是相當(dāng)?shù)呐宸谂宸辔覀円杏泟?wù)實(shí),沒有務(wù)實(shí)的東西都是很虛擬飄渺的。還記得自己當(dāng)初在大學(xué)里面受到考證風(fēng)氣的影響,神經(jīng)兮兮的去考過了什么國(guó)家計(jì)算機(jī)四級(jí)和MCP等一大堆證件,后來到公司面試興高采烈拿著20多張證書,才知道那些東西根本就沒有什么價(jià)值,反而讓自己去學(xué)習(xí)了最不喜歡的技術(shù),同時(shí)也給自己掛上了考證族的名號(hào)。所以后來總結(jié)就是勞民傷財(cái)、徒添傷悲!
技術(shù)分享:在自己公司及其他公司進(jìn)行一些技術(shù)培訓(xùn)或者討論,其實(shí)重要的不是什么榮譽(yù),而是在把這個(gè)培訓(xùn)看成是一些技術(shù)交流和分享,因?yàn)樵谶@個(gè)過程中,你可能會(huì)重新認(rèn)識(shí)你所掌握的技術(shù)、你可能會(huì)遇到一些志同道合的人、你可能會(huì)在分享過程中糾正以前的錯(cuò)誤認(rèn)識(shí)、你可能會(huì)在各方面得到提高從而完善自己的知識(shí)體系,但是最重要的是你要認(rèn)真對(duì)待每一次培訓(xùn),知之為知之不知為不知,不要不能教導(dǎo)他人反而誤導(dǎo)了他人。記得有一次在公司培訓(xùn)OO與設(shè)計(jì)模式,我知道這個(gè)專題想在一下午的時(shí)間把它講清楚是非常困難的,這個(gè)不像之后培訓(xùn)的WPF、WCF和Silverlight那么單純,并且每個(gè)人的基礎(chǔ)都不一樣,當(dāng)中有還沒有畢業(yè)的實(shí)習(xí)生、剛畢業(yè)不久的畢業(yè)生、工作了數(shù)年的工程師及技術(shù)大牛們,所以如何把這些知識(shí)很好的插入到每個(gè)人的知識(shí)樹上面成了我考慮的重點(diǎn)。同時(shí)我的心里也比較矛盾,一方面希望參加培訓(xùn)的同事多一些,另一方面希望人越少越好。前者則是按照常理來考慮的,畢竟培訓(xùn)者都希望自己培訓(xùn),越受歡迎越好,這樣才能使自己的思想得到更多人的認(rèn)可,自己也能實(shí)現(xiàn)分享知識(shí)的目的。后者則是擔(dān)心怕講不好,少一點(diǎn)人就少一點(diǎn)罪過。可是恰巧這一次是歷次培訓(xùn)中最多的一次,來參加培訓(xùn)的同事有一百多人,不過幸好由于會(huì)議室坐不下,才分成了兩批,這樣就可以讓我具備了更充分的時(shí)間和更好的心態(tài)。總之培訓(xùn)是向內(nèi)和向外的提煉與升華,正所謂“自己理解的知識(shí)未必能使人家理解”,這不僅考驗(yàn)的是技術(shù),還考驗(yàn)了一個(gè)人的綜合能力。
(四)結(jié)論:
前面從向內(nèi)和向外以及擴(kuò)展三個(gè)方面進(jìn)行了簡(jiǎn)單闡述,用一句話概括就是:向內(nèi)深不可測(cè)、向外漫無邊際、擴(kuò)展才能超越極限。由于這里只是對(duì)本文及下面的三篇文章做一些鋪墊工作,所以我們也不細(xì)細(xì)分解,那么我也得稍微推薦一點(diǎn)資料才對(duì)得起大家:第一還是研究微軟的類庫,對(duì)我們常見的應(yīng)用進(jìn)行研究,可以結(jié)合Reflector+VS調(diào)試內(nèi)部代碼功能一起研究(IL能幫我們看清楚一些內(nèi)部原理,但是不推薦細(xì)究,因?yàn)樗鼤?huì)浪費(fèi)我們很多時(shí)間,畢竟是微軟搞出來的這么一套東西,說不定微軟哪天就換了)。其次就是研究MONO源碼(www.mono-project.com),這個(gè)是個(gè)非常好的東西,對(duì).NET的功能大部分都進(jìn)行了實(shí)現(xiàn),我之前研究它不是因?yàn)樗目缙脚_(tái),是感興趣它的源碼,大家也可以在線查看它的源碼(www.Java2s.com),說到Java2s這個(gè)網(wǎng)站,也是我平時(shí)去得比較多的網(wǎng)站,因?yàn)樗容^全面和方便,同時(shí)也會(huì)給我們帶來意想不到的收獲。再其次就是研究一些開源的框架和項(xiàng)目,比如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/)等等。這里要注意的是:在研究的過程中一定要先熟悉功能,再研究它內(nèi)部的源碼和實(shí)現(xiàn),然后再創(chuàng)造出自己的框架。這樣才能激發(fā)我們研究的欲望,才會(huì)產(chǎn)生作用和反作用力,從而才會(huì)使我們真正受益。
四. 云計(jì)算廣告插播
由于這段時(shí)間白天要研究云計(jì)算專題(公司項(xiàng)目原因,最主要還是自己的興趣使然),晚上和閑暇時(shí)間又要寫WPF,所以感覺有點(diǎn)心猿意馬。原打算寫完WPF這個(gè)系列以后才繼續(xù)”云計(jì)算之旅“這個(gè)系列,但是經(jīng)過慎重的思考,同時(shí)也考慮到錄制MSDN WebCast視頻,所以決定兩個(gè)系列同時(shí)進(jìn)行,經(jīng)過幾個(gè)月的籌備(期間包括折騰公司的云計(jì)算項(xiàng)目、研究相關(guān)云計(jì)算的電子書、國(guó)外技術(shù)視頻和國(guó)外各技術(shù)社區(qū)和博客等),自己也頗有收獲。期間最重要的還是自己寫例子,寫完了以后再分析它的原理直至最后總結(jié),這樣才能把它變成自己的東西,現(xiàn)在回過頭來感覺云計(jì)算終于在自己心目中走下了神壇,逐漸揭開了那一層神秘面紗,所以才有下面這個(gè)系列的分享,也希望大家能給出建議,從而達(dá)到技術(shù)交流、共同提高的目的。
云計(jì)算之旅1—開篇有益
云計(jì)算之旅2—云計(jì)算總覽
云計(jì)算之旅3—云計(jì)算提供商綜合對(duì)比
云計(jì)算之旅4—Windows Azure總覽
云計(jì)算之旅5—第一個(gè)Windows Azure程序
云計(jì)算之旅6—剖析Windows Azure程序內(nèi)部原理
云計(jì)算之旅8—ASP.NET MVC Web Role
云計(jì)算之旅9—WCF Service Web Role
云計(jì)算之旅10—Work Role Castle
云計(jì)算之旅11—CGI Web Role
云計(jì)算之旅12—云存儲(chǔ)之Blob
云計(jì)算之旅13—云存儲(chǔ)之Table
云計(jì)算之旅14—云存儲(chǔ)之Quee
云計(jì)算之旅15—云存儲(chǔ)之Dive
云計(jì)算之旅16—SQL Azure(一)
云計(jì)算之旅17—SQL Azure(二)
云計(jì)算之旅18—SQL Azure(三)
云計(jì)算之旅19—AppFabric(一)
云計(jì)算之旅20—AppFabric(二)
云計(jì)算之旅21—AppFabric(三)
云計(jì)算之旅22—云平臺(tái)安全問題
云計(jì)算之旅23—老技術(shù)兼容問題
云計(jì)算之旅24—ASP.NET+SQL項(xiàng)目移植到云平臺(tái)
云計(jì)算之旅25—WinForm/WPF項(xiàng)目移植到云平臺(tái)(云/端模式)
云計(jì)算之旅26—ASP.NET+Silverlight項(xiàng)目移植到云平臺(tái)
云計(jì)算之旅27—Amazon云計(jì)算
云計(jì)算之旅28—Google云計(jì)算
云計(jì)算之旅29—SalesForce云計(jì)算
云計(jì)算之旅30—云計(jì)算開發(fā)總結(jié)
上面的分類是按照最近學(xué)習(xí)的總結(jié)歸類的,在這幾個(gè)月中也先后寫了一些文章和代碼示例,同時(shí)有些知識(shí)沒有羅列上去,在后面可能會(huì)有一些小的修改。總之,我們堅(jiān)決抵制”忽悠“,爭(zhēng)取以實(shí)際代碼說話,在此過程中希望大家能夠積極踴躍的加入進(jìn)來,如果有什么不對(duì)的地方,也希望向大家學(xué)習(xí),最重要的是大家有所收獲就好!
五. 依賴屬性基本介紹
前面廢話了這么久,到現(xiàn)在才真正進(jìn)入今天的主題,對(duì)此感到非常抱歉,如果各位不喜歡,可以直接跳到這里閱讀。大家都知道WPF帶來了很多新的特性,它的一大亮點(diǎn)是引入了一種新的屬性機(jī)制——依賴屬性。依賴屬性基本應(yīng)用在了WPF的所有需要設(shè)置屬性的元素。依賴屬性根據(jù)多個(gè)提供對(duì)象來決定它的值(可以是動(dòng)畫、父類元素、綁定、樣式和模板等),同時(shí)這個(gè)值也能及時(shí)響應(yīng)變化。所以WPF擁有了依賴屬性后,代碼寫起來就比較得心應(yīng)手,功能實(shí)現(xiàn)上也變得非常容易了。如果沒有依賴屬性,我們將不得不編寫大量的代碼。關(guān)于WPF的依賴屬性,主要有下面三個(gè)優(yōu)點(diǎn),我們的研究也重點(diǎn)放在這三點(diǎn)上:
1、新功能的引入:加入了屬性變化通知,限制、驗(yàn)證等等功能,這樣就可以使我們更方便的實(shí)現(xiàn)我們的應(yīng)用,同時(shí)也使代碼量大大減少了,許多之前不可能的功能都可以輕松的實(shí)現(xiàn)了。
2、節(jié)約內(nèi)存:在WinForm等項(xiàng)目開發(fā)中,你會(huì)發(fā)現(xiàn)UI控件的屬性通常都是賦予的初始值,為每一個(gè)屬性存儲(chǔ)一個(gè)字段將是對(duì)內(nèi)存的巨大浪費(fèi)。WPF依賴屬性解決了這個(gè)問題,它內(nèi)部使用高效的稀疏存儲(chǔ)系統(tǒng),僅僅存儲(chǔ)改變了的屬性,即默認(rèn)值在依賴屬性中只存儲(chǔ)一次。
3、支持多個(gè)提供對(duì)象:我們可以通過多種方式來設(shè)置依賴屬性的值。同時(shí)其內(nèi)部可以儲(chǔ)存多個(gè)值,配合Expression、Style、Animation等可以給我們帶來很強(qiáng)的開發(fā)體驗(yàn)。
在.NET當(dāng)中,屬性是我們很熟悉的,封裝類的字段,表示類的狀態(tài),編譯后被轉(zhuǎn)化為對(duì)應(yīng)的get和set方法(在Java里面沒有屬性的概念,通常都是寫相應(yīng)的方法來對(duì)字段進(jìn)行封裝)。屬性可以被類或結(jié)構(gòu)等使用。 一個(gè)簡(jiǎn)單的屬性如下,也是我們常用的寫法:
private string sampleProperty;
public string SampleProperty
{
get
{
return sampleProperty;
}
set
{
if (value != null)
{
sampleProperty = value;
}
else
{
sampleProperty = "Knights Warrior!";
}
}
}
屬性是我們?cè)偈煜げ贿^的了,那么究竟依賴屬性怎么寫呢?依賴屬性和屬性到底有什么區(qū)別和聯(lián)系呢?其實(shí)依賴屬性的實(shí)現(xiàn)很簡(jiǎn)單,只要做以下步驟就可以實(shí)現(xiàn):
第一步: 讓所在類型繼承自 DependencyObject基類,在WPF中,我們仔細(xì)觀察框架的類圖結(jié)構(gòu),你會(huì)發(fā)現(xiàn)幾乎所有的 WPF 控件都間接繼承自DependencyObject類型。
第二步:使用 public static 聲明一個(gè) DependencyProperty的變量,該變量才是真正的依賴屬性 ,看源碼就知道這里其實(shí)用了簡(jiǎn)單的單例模式的原理進(jìn)行了封裝(構(gòu)造函數(shù)私有),只暴露Register方法給外部調(diào)用。
第三步:在靜態(tài)構(gòu)造函數(shù)中完成依賴屬性的元數(shù)據(jù)注冊(cè),并獲取對(duì)象引用,看代碼就知道是把剛才聲明的依賴屬性放入到一個(gè)類似于容器的地方,沒有講實(shí)現(xiàn)原理之前,請(qǐng)容許我先這么陳述。
第四步:在前面的三步中,我們完成了一個(gè)依賴屬性的注冊(cè),那么我們?cè)鯓硬拍軐?duì)這個(gè)依賴屬性進(jìn)行讀寫呢?答案就是提供一個(gè)依賴屬性的實(shí)例化包裝屬性,通過這個(gè)屬性來實(shí)現(xiàn)具體的讀寫操作。
根據(jù)前面的四步操作,我們就可以寫出下面的代碼:
public class SampleDPClass : DependencyObject
{
//聲明一個(gè)靜態(tài)只讀的DependencyProperty字段
public static readonly DependencyProperty SampleProperty;
static SampleDPClass()
{
//注冊(cè)我們定義的依賴屬性Sample
SampleProperty = DependencyProperty.Register("Sample", typeof(string), typeof(SampleDPClass),
new PropertyMetadata("Knights Warrior!", OnValueChanged));
}
private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
//當(dāng)值改變時(shí),我們可以在此做一些邏輯處理
}
//屬性包裝器,通過它來讀取和設(shè)置我們剛才注冊(cè)的依賴屬性
public string Sample
{
get { return (string)GetValue(SampleProperty); }
set { SetValue(SampleProperty, value); }
}
}
總結(jié):我們一般.NET屬性是直接對(duì)類的一個(gè)私有屬性進(jìn)行封裝,所以讀取值的時(shí)候,也就是直接讀取這個(gè)字段;而依賴屬性則是通過調(diào)用繼承自DependencyObject的GetValue()和SetValue來進(jìn)行操作,它實(shí)際存儲(chǔ)在DependencyProperty的一個(gè)IDictionary的鍵-值配對(duì)字典中,所以一條記錄中的鍵(Key)就是該屬性的HashCode值,而值(Value)則是我們注冊(cè)的DependencyProperty。
六. 依賴屬性的優(yōu)先級(jí)
由于WPF 允許我們可以在多個(gè)地方設(shè)置依賴屬性的值,所以我們就必須要用一個(gè)標(biāo)準(zhǔn)來保證值的優(yōu)先級(jí)別。比如下面的例子中,我們?cè)谌齻€(gè)地方設(shè)置了按鈕的背景顏色,那么哪一個(gè)設(shè)置才會(huì)是最終的結(jié)果呢?是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>
通過前面的簡(jiǎn)單介紹,我們了解了簡(jiǎn)單的依賴屬性,每次訪問一個(gè)依賴屬性,它內(nèi)部會(huì)按照下面的順序由高到底處理該值。詳細(xì)見下圖
由于這個(gè)流程圖偏理想化,很多時(shí)候我們會(huì)遇到各種各樣的問題,這里也不可能一句話、兩句話就能夠把它徹底說清楚,所以我們就不過多糾纏。等遇到問題之后要仔細(xì)分析,在找到原因之后也要不斷總結(jié)、舉一反三,只有這樣才能逐漸提高。
七. 依賴屬性的繼承
依賴屬性繼承的最初意愿是父元素的相關(guān)設(shè)置會(huì)自動(dòng)傳遞給所有層次的子元素 ,即元素可以從其在樹中的父級(jí)繼承依賴項(xiàng)屬性的值。這個(gè)我們?cè)诰幊坍?dāng)中接觸得比較多,如當(dāng)我們修改窗體父容器控件的字體設(shè)置時(shí),所有級(jí)別的子控件都將自動(dòng)使用該字體設(shè)置 (前提是該子控件未做自定義設(shè)置),如下面的代碼:
<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 設(shè)置會(huì)影響所有的內(nèi)部元素字體大小,這就是所謂的屬性值繼承,如上面代碼中的第一個(gè)Label沒有定義FontSize ,所以它繼承了Window.FontSize的值。但一旦子元素提供了顯式設(shè)置,這種繼承就會(huì)被打斷,如第二個(gè)Label定義了自己的FontSize,所以這個(gè)時(shí)候繼承的值就不會(huì)再起作用了。
這個(gè)時(shí)候你會(huì)發(fā)現(xiàn)一個(gè)很奇怪的問題:雖然StatusBar沒有重寫FontSize,同時(shí)它也是Window的子元素,但是它的字體大小卻沒有變化,保持了系統(tǒng)默認(rèn)值。那這是什么原因呢?作為初學(xué)者可能都很納悶,官方不是說了原則是這樣的,為什么會(huì)出現(xiàn)表里不一的情況呢?其實(shí)仔細(xì)研究才發(fā)現(xiàn)并不是所有的元素都支持屬性值繼承。還會(huì)存在一些意外的情況,那么總的來說是由于以下兩個(gè)方面:
1、有些Dependency屬性在用注冊(cè)的時(shí)候時(shí)指定Inherits為不可繼承,這樣繼承就會(huì)失效了。
2、有其他更優(yōu)先級(jí)的設(shè)置設(shè)置了該值,在前面講的的“依賴屬性的優(yōu)先級(jí)”你可以看到具體的優(yōu)先級(jí)別。
這里的原因是部分控件如StatusBar、Tooptip和Menu等內(nèi)部設(shè)置它們的字體屬性值以匹配當(dāng)前系統(tǒng)。這樣用戶通過操作系統(tǒng)的控制面板來修改它們的外觀。這種方法存在一個(gè)問題:StatusBar等截獲了從父元素繼承來的屬性,并且不影響其子元素。比如,如果我們?cè)赟tatusBar中添加了一個(gè)Button。那么這個(gè)Button的字體屬性會(huì)因?yàn)镾tatusBar的截?cái)喽鴽]有任何改變,將保留其默認(rèn)值。所以大家在使用的時(shí)候要特別注意這些問題。
前面我們看了依賴屬性的繼承,當(dāng)我們自定義的依賴屬性,應(yīng)該如何處理繼承的關(guān)系呢? 請(qǐng)看下面的代碼(注釋很詳細(xì),我就不再費(fèi)口水了):
public class MyCustomButton : Button
{
static MyCustomButton()
{
//通過MyStackPanel依賴屬性MinDateProperty的AddOwner方式實(shí)現(xiàn)繼承,注意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()
{
//我們?cè)贛yStackPanel里面注冊(cè)了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中進(jìn)行使用了:
<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="使用自動(dòng)以依賴屬性繼承" 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>
最后的效果如下:
八. 只讀依賴屬性
我們以前在對(duì)簡(jiǎn)單屬性的封裝中,經(jīng)常會(huì)對(duì)那些希望暴露給外界只讀操作的字段封裝成只讀屬性,同樣在WPF中也提供了只讀屬性的概念,如一些WPF控件的依賴屬性是只讀的,它們經(jīng)常用于報(bào)告控件的狀態(tài)和信息,像IsMouseOver等屬性, 那么在這個(gè)時(shí)候?qū)λx值就沒有意義了。 或許你也會(huì)有這樣的疑問:為什么不使用一般的.NET屬性提供出來呢?一般的屬性也可以綁定到元素上呀?這個(gè)是由于有些地方必須要用到只讀依賴屬性,比如Trigger等,同時(shí)也因?yàn)閮?nèi)部可能有多個(gè)提供者修改其值,所以用.NET屬性就不能完成天之大任了。
那么一個(gè)只讀依賴屬性怎么創(chuàng)建呢?其實(shí)創(chuàng)建一個(gè)只讀的依賴屬性和創(chuàng)建一個(gè)一般的依賴屬性大同小異(研究源碼你會(huì)發(fā)現(xiàn),其內(nèi)部都是調(diào)用的同一個(gè)Register方法)。僅僅是用DependencyProperty.RegisterReadonly替換了DependencyProperty.DependencyProperty而已。和前面的普通依賴屬性一樣,它將返回一個(gè)DependencyPropertyKey。該鍵值在類的內(nèi)部暴露一個(gè)賦值的入口,同時(shí)只提供一個(gè)GetValue給外部,這樣便可以像一般屬性一樣使用了,只是不能在外部設(shè)置它的值罷了。
下面我們就用一個(gè)簡(jiǎn)單的例子來概括一下:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
//內(nèi)部用SetValue的方式來設(shè)置值
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,這里你也可以設(shè)置一個(gè)private的SetValue進(jìn)行限制
public int Counter
{
get { return (int)GetValue(counterKey.DependencyProperty); }
}
//用RegisterReadOnly來代替Register來注冊(cè)一個(gè)只讀的依賴屬性
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>
效果如下圖所示:
前面我們講了依賴屬性。現(xiàn)在我們?cè)倮^續(xù)探討另外一種特殊的Dependency屬性——附加屬性。附加屬性是一種特殊的依賴屬性。他允許給一個(gè)對(duì)象添加一個(gè)值,而該對(duì)象可能對(duì)此值一無所知。
最好的例子就是布局面板。每一個(gè)布局面板都需要自己特有的方式來組織它的子元素。如Canvas需要Top和left來布局,DockPanel需要Dock來布局。當(dāng)然你也可以寫自己的布局面板(在上一篇文章中我們對(duì)布局進(jìn)行了比較細(xì)致的探討,如果有不清楚的朋友也可以再回顧一下)。
下面代碼中的Button 就是用了Canvas的Canvas.Top和Canvas.Left="20" 來進(jìn)行布局定位,那么這兩個(gè)就是傳說中的附加屬性。
<Canvas>
<Button Canvas.Top="20" Canvas.Left="20" Content="Knights Warrior!"/>
</Canvas>
在最前面的小節(jié)中,我們是使用DependencyProperty.Register來注冊(cè)一個(gè)依賴屬性,同時(shí)依賴屬性本身也對(duì)外提供了 DependencyProperty.RegisterAttached方法來注冊(cè)附加屬性。這個(gè)RegisterAttached的參數(shù)和 Register是完全一致的,那么Attached(附加)這個(gè)概念又從何而來呢?
其實(shí)我們使用依賴屬性,一直在Attached(附加)。我們注冊(cè)(構(gòu)造)一個(gè)依賴屬性,然后在DependencyObject中通過 GetValue和SetValue來操作這個(gè)依賴屬性,也就是把這個(gè)依賴屬性通過這樣的方法關(guān)聯(lián)到了這個(gè)DependencyObject上,只不過是通過封裝CLR屬性來達(dá)到的。那么RegisterAttached又是怎樣的呢?
下面我們來看一個(gè)最簡(jiǎn)單的應(yīng)用:首先我們注冊(cè)(構(gòu)造)一個(gè)附加屬性:
public class AttachedPropertyChildAdder
{
//通過使用RegisterAttached來注冊(cè)一個(gè)附加屬性
public static readonly DependencyProperty IsAttachedProperty =
DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyChildAdder),
new FrameworkPropertyMetadata((bool)false));
//通過靜態(tài)方法的形式暴露讀的操作
public static bool GetIsAttached(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsAttachedProperty);
}
//通過靜態(tài)方法的形式暴露寫的操作
public static void SetIsAttached(DependencyObject dpo, bool value)
{
dpo.SetValue(IsAttachedProperty, value);
}
}
在XAML中就可以使用剛才注冊(cè)(構(gòu)造)的附加屬性了:
在上面的例子中,AttachedPropertyChildAdder 中并沒有對(duì)IsAttached采用CLR屬性形式進(jìn)行封裝,而是使用了靜態(tài)SetIsAttached方法和GetIsAttached方法來存取IsAttached值,當(dāng)然如果你了解它內(nèi)部原理,你就會(huì)看到實(shí)際上還是調(diào)用的SetValue與GetValue來進(jìn)行操作(只不過擁有者不同而已)。這里我們不繼續(xù)深入下去,詳細(xì)在后面的內(nèi)容會(huì)揭開謎底。
十. 清除本地值
在很多時(shí)候,由于我們的業(yè)務(wù)邏輯和UI操作比較復(fù)雜,所以一個(gè)龐大的頁面會(huì)進(jìn)行很多諸如動(dòng)畫、3D、多模板及樣式的操作,這個(gè)時(shí)候頁面的值已經(jīng)都被改變了,如果我們想讓它返回默認(rèn)值,可以用ClearValue 來清除本地值,但是遺憾的是,很多時(shí)候由于WPF依賴屬性本身的設(shè)計(jì),它往往會(huì)不盡如人意(詳細(xì)就是依賴屬性的優(yōu)先級(jí)以及依賴屬性EffectiveValueEntry 的影響)。ClearValue 方法為在元素上設(shè)置的依賴項(xiàng)屬性中清除任何本地應(yīng)用的值提供了一個(gè)接口。但是,調(diào)用 ClearValue 并不能保證注冊(cè)屬性時(shí)在元數(shù)據(jù)中指定的默認(rèn)值就是新的有效值。值優(yōu)先級(jí)中的所有其他參與者仍然有效。只有在本地設(shè)置的值才會(huì)從優(yōu)先級(jí)序列中移除。例如,如果您對(duì)同時(shí)也由主題樣式設(shè)置的屬性調(diào)用 ClearValue,主題值將作為新值而不是基于元數(shù)據(jù)的默認(rèn)值進(jìn)行應(yīng)用。如果您希望取消過程中的所有屬性值,而將值設(shè)置為注冊(cè)的元數(shù)據(jù)默認(rèn)值,則可以通過查詢依賴項(xiàng)屬性的元數(shù)據(jù)來最終獲得默認(rèn)值,然后使用該默認(rèn)值在本地設(shè)置屬性并調(diào)用 SetValue來實(shí)現(xiàn),這里我們得感得PropertyMetadata類為我們提供了諸如DefaultValue這樣的外部可訪問的屬性。
上面講了這么多,現(xiàn)在我們就簡(jiǎn)單用一個(gè)例子來說明上面的原理(例子很直觀,相信大家能很容易看懂)
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>
后臺(tái)代碼:
public partial class DPClearValue
{
//清除本地值,還原到默認(rèn)值
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); }
}
}
當(dāng)按下”改變所有的值“按鈕的時(shí)候,就會(huì)把之前的值都進(jìn)行修改了,這個(gè)時(shí)候按下”清除本地值“就會(huì)使原來的所有默認(rèn)值生效。
十一. 依賴屬性元數(shù)據(jù)
前面我們看到一個(gè)依賴屬性的注冊(cè)最全的形式是下面這樣子的:
public static DependencyProperty Register(string name,
Type propertyType,
Type ownerType,
PropertyMetadata typeMetadata,
ValidateValueCallback validateValueCallback);
第一個(gè)參數(shù)是該依賴屬性的名字,第二個(gè)參數(shù)是依賴屬性的類型,第三個(gè)參數(shù)是該依賴屬性的所有者的類型,第五個(gè)參數(shù)就是一個(gè)驗(yàn)證值的回調(diào)委托,那么最使我們感興趣的還是這個(gè)可愛的 PropertyMetadata ,也就是我們接下來要講的元數(shù)據(jù)。 提到WPF屬性元數(shù)據(jù),大家可能第一想到的是剛才的PropertyMetadata,那么這個(gè)類到底是怎樣的呢?我們應(yīng)該怎樣使用它呢?首先我們看它的構(gòu)造函數(shù)(我們選參數(shù)最多的來講):
public PropertyMetadata(object defaultValue,
PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback);
其中的第一個(gè)參數(shù)是默認(rèn)值,最后兩個(gè)分別是PropertyChanged(變化通知)以及Coerce(強(qiáng)制)的兩個(gè)委托變量,我們?cè)趯?shí)例化的時(shí)候,只需要把這兩個(gè)委托變量關(guān)聯(lián)到具體的方法上即可。
事實(shí)上,除了PropertyMetadata以外,常見的還有FrameworkPropertyMetadata,UIPropertyMetadata。他們的繼承關(guān)系是F->U->P。其中以FrameworkPropertyMetadata參數(shù)最多,亦最為復(fù)雜。
FrameworkPropertyMetadata的構(gòu)造函數(shù)提供了很多重載,我們挑選最為復(fù)雜的重載來看它到底有哪些參數(shù)以及提供了哪些功能:
public FrameworkPropertyMetadata(object defaultValue,
FrameworkPropertyMetadataOptions flags,
PropertyChangedCallback propertyChangedCallback,
CoerceValueCallback coerceValueCallback,
bool isAnimationProhibited,
UpdateSourceTrigger defaultUpdateSourceTrigger);
其中第一個(gè)參數(shù)是默認(rèn)值,最后兩個(gè)參數(shù)分別是是否允許動(dòng)畫,以及綁定時(shí)更新的策略(在Binding當(dāng)中相信大家并不陌生),這個(gè)不詳細(xì)解釋了。重點(diǎn)看一下里第三、四兩個(gè)參數(shù),兩個(gè) CallBack的委托。結(jié)合前面Register的時(shí)候提到的ValidateValueCallback共組成三大”金剛“,這三個(gè)Callback分別代表Validate(驗(yàn)證),PropertyChanged(變化通知)以及Coerce(強(qiáng)制)。當(dāng)然,作為 Metadata,F(xiàn)rameworkPropertyMetadata只是儲(chǔ)存了該依賴屬性的策略信息,WPF屬性系統(tǒng)會(huì)根據(jù)這些信息來提供功能并在適當(dāng)?shù)臅r(shí)機(jī)回調(diào)傳入的delegate,所以最重要的還是我們定義的這些方法,通過他們傳入委托才能起到真正的作用。
上面講了元數(shù)據(jù)暴露給我們的構(gòu)造函數(shù),其實(shí)在其內(nèi)部還提供了兩個(gè)方法,這個(gè)在做自定義控件的時(shí)候,也很值得注意:
protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
// 實(shí)現(xiàn)元數(shù)據(jù)繼承之間的合并
}
protected virtual void OnApply(DependencyProperty dependencyProperty, Type targetType)
{
// 當(dāng)元數(shù)據(jù)被這個(gè)屬性應(yīng)用,OnApply就會(huì)被觸發(fā),在此時(shí)元數(shù)據(jù)也將被密封起來。
}
前面講了這么多,那么我們現(xiàn)在就來看看依賴屬性回調(diào)、驗(yàn)證及強(qiáng)制值到底是怎么使用的呢?大家千萬要堅(jiān)持住,后面內(nèi)容更加精彩!
十二. 依賴屬性回調(diào)、驗(yàn)證及強(qiáng)制值
我們通過下面的這幅圖,簡(jiǎn)單介紹一下WPF屬性系統(tǒng)對(duì)依賴屬性操作的基本步驟:
- 第一步,基礎(chǔ)值就是上面“六.依賴屬性的優(yōu)先級(jí)”提供的那些顯示設(shè)置,所以它的優(yōu)先級(jí)比較好確定,但有些不會(huì)按常規(guī)出牌,所以也需要注意總結(jié)。
- 第二步,如果依賴屬性值是計(jì)算表達(dá)式 (如前面示例中的綁定等語法特性),這個(gè)時(shí)候就會(huì)計(jì)算表達(dá)式的結(jié)果作為第二步的值。
- 第三步,動(dòng)畫是一種優(yōu)先級(jí)很高的特殊行為,很多時(shí)候,我們都會(huì)聽到動(dòng)畫優(yōu)先的聲音,所以它的優(yōu)先級(jí)高于其他基礎(chǔ)設(shè)置;
- 第四步,強(qiáng)制則是注冊(cè)時(shí)提供的 CoerceValueCallback 委托,它負(fù)責(zé)驗(yàn)證屬性值是否在允許的限制范圍之內(nèi),和我們對(duì)屬性的驗(yàn)證一樣,比如強(qiáng)制設(shè)置該值必須大于于0小于10等等;
- 第五步,驗(yàn)證是指我們注冊(cè)依賴屬性所提供的 ValidateValueCallback 委托方法,它最終決定了屬性值設(shè)置是否有效,當(dāng)數(shù)據(jù)無效時(shí)會(huì)拋出異常來通知。
前面我們講了基本的流程,下面我們就用一個(gè)小的例子來進(jìn)行說明:
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("當(dāng)值改變時(shí),我們可以做的一些操作,具體可以在這里定義: {0}", e.NewValue);
}
private static object CoerceValue(DependencyObject d, object value)
{
Console.WriteLine("對(duì)值進(jìn)行限定,強(qiáng)制值: {0}", value);
return value;
}
private static bool IsValidValue(object value)
{
Console.WriteLine("驗(yàn)證值是否通過,返回bool值,如果返回True表示嚴(yán)重通過,否則會(huì)以異常的形式暴露: {0}", value);
return true;
}
}
}
結(jié)果如下:
當(dāng)SimpleDP屬性變化之后,PropertyChangeCallback就會(huì)被調(diào)用。可以看到結(jié)果并沒有完全按照我們先前的流程先Coerce后Validate的順序執(zhí)行,有可能是WPF內(nèi)部做了什么特殊處理,當(dāng)屬性被修改時(shí),首先會(huì)調(diào)用Validate來判斷傳入的value是否有效,如果無效就不繼續(xù)后續(xù)的操作,這樣可以更好的優(yōu)化性能。從上面的結(jié)果上看出,CoerceValue后面并沒有立即ValidateValue,而是直接調(diào)用了PropertyChanged。這是因?yàn)榍懊嬉呀?jīng)驗(yàn)證過了value,如果在Coerce中沒有改變value,那么就不用再驗(yàn)證了。如果在 Coerce中改變了value,那么這里還會(huì)再次調(diào)用ValidateValue操作,和前面的流程圖執(zhí)行的順序一樣,在最后我們會(huì)調(diào)用ValidateValue來進(jìn)行最后的驗(yàn)證,這就保證最后的結(jié)果是我們希望的那樣了(正如打游戲一樣,打了小怪,在最后過總關(guān)的時(shí)候還是需要打大怪才能闖關(guān)的)。
上面簡(jiǎn)單介紹了處理流程,下面我們就以一個(gè)案例來具體看一看上面的流程到底有沒有出入,這個(gè)例子改編于Sacha Barber 的Dependency Properties代碼示例,我相信通過這段代碼你會(huì)對(duì)這個(gè)上面講的概念有更清晰地認(rèn)識(shí)。
UI很簡(jiǎn)單,黃色部分顯示當(dāng)前值,我們?cè)诔跏蓟臅r(shí)候把它設(shè)置為100,然后它的最小值和最大值分別設(shè)置為0和500,按鈕”設(shè)置為-100“企圖把當(dāng)前值設(shè)為-100,按鈕”設(shè)置為1000“試圖把當(dāng)前值設(shè)為1000。具體大家看代碼(我都寫了注釋,很容易理解的).
依賴屬性代碼文件如下:
namespace Callback_Validation_DPs
{
public class Gauge : Control
{
public Gauge() : base() { }
//注冊(cè)CurrentReading依賴屬性,并添加PropertyChanged、CoerceValue、ValidateValue的回調(diào)委托
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); }
}
//注冊(cè)MinReading依賴屬性,并添加PropertyChanged、CoerceValue、ValidateValue的回調(diào)委托
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); }
}
//注冊(cè)MaxReading依賴屬性,并添加PropertyChanged、CoerceValue、ValidateValue的回調(diào)委托
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加入強(qiáng)制判斷賦值
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;
}
//當(dāng)CurrentReading值改變的時(shí)候,調(diào)用MinReading和MaxReading的CoerceValue回調(diào)委托
private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinReadingProperty);
d.CoerceValue(MaxReadingProperty);
}
//當(dāng)OnMinReading值改變的時(shí)候,調(diào)用CurrentReading和MaxReading的CoerceValue回調(diào)委托
private static void OnMinReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MaxReadingProperty);
d.CoerceValue(CurrentReadingProperty);
}
//在CoerceMinReading加入強(qiáng)制判斷賦值
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加入強(qiáng)制判斷賦值
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;
}
//當(dāng)MaxReading值改變的時(shí)候,調(diào)用MinReading和CurrentReading的CoerceValue回調(diào)委托
private static void OnMaxReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinReadingProperty);
d.CoerceValue(CurrentReadingProperty);
}
//驗(yàn)證value是否有效,如果返回True表示驗(yàn)證通過,否則會(huì)提示異常
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="可以設(shè)置最小值為0和最小大值為500" Height="30"/>
<StackPanel Orientation="Horizontal" Height="60">
<Label Content="當(dāng)前值為 : "/>
<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="設(shè)置為 -100"
Click="btnSetBelowMin_Click"/>
<Button x:Name="btnSetAboveMax" Content="設(shè)置為 1000"
Click="btnSetAboveMax_Click"/>
</StackPanel>
</Window>
XAML的后臺(tái)代碼如下:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
//設(shè)置CurrentReading的值,這個(gè)時(shí)候會(huì)觸發(fā)哪些變化?調(diào)試代碼吧!
gauge1.CurrentReading = 100;
}
private void btnSetBelowMin_Click(object sender, RoutedEventArgs e)
{
//設(shè)置CurrentReading的值,這個(gè)時(shí)候會(huì)觸發(fā)哪些變化?調(diào)試代碼吧!
gauge1.CurrentReading = -100;
}
private void btnSetAboveMax_Click(object sender, RoutedEventArgs e)
{
//設(shè)置CurrentReading的值,這個(gè)時(shí)候會(huì)觸發(fā)哪些變化?調(diào)試代碼吧!
gauge1.CurrentReading = 10000;
}
}
在上面的例子中,一共有三個(gè)依賴屬性相互作用——CurrentReading、MinReading和MaxReading,這些屬性相互作用,但它們的規(guī)則是MinReading≤CurrentReading≤MaxReading。根據(jù)這個(gè)規(guī)則,當(dāng)其中一個(gè)依賴屬性變化時(shí),另外兩個(gè)依賴屬性必須進(jìn)行適當(dāng)?shù)恼{(diào)整,這里我們要用到的就是CoerceValue這個(gè)回調(diào)委托,那么實(shí)現(xiàn)起來也非常的簡(jiǎn)單,注冊(cè)MaxReading的時(shí)候加入CoerceValueCallback,在CoerceMaxReading函數(shù)中做處理:如果Maximum的值小于MinReading,則使MaxReading值等于MinReading;同理在CurrentReading中也加入了CoerceValueCallback進(jìn)行相應(yīng)的強(qiáng)制處理。然后在MinReading的ChangedValueCallback被調(diào)用的時(shí)候,調(diào)用CurrentReading和MaxReading的CoerceValue回調(diào)委托,這樣就可以達(dá)到相互作用的依賴屬性一變應(yīng)萬變的”千機(jī)變“。
換句話說,當(dāng)相互作用的幾個(gè)依賴屬性其中一個(gè)發(fā)生變化時(shí),在它的PropertyChangeCallback中調(diào)用受它影響的依賴屬性的CoerceValue,這樣才能保證相互作用關(guān)系的正確性。 前面也提高ValidateValue主要是驗(yàn)證該數(shù)據(jù)的有效性,最設(shè)置了值以后都會(huì)調(diào)用它來進(jìn)行驗(yàn)證,如果驗(yàn)證不成功,則拋出異常。
十三. 依賴屬性監(jiān)聽
如果想監(jiān)聽依賴屬性的改變,可以用兩種方法實(shí)現(xiàn),在很多時(shí)候,我們兩種方法都會(huì)用到:
用DependencyPropertyDescriptor 比較簡(jiǎn)便,在代碼里面寫起來也比較便捷;
用OverrideMetadata的方式主要在自定義控件以及處理一些類間關(guān)系的時(shí)候;
第一種方法:派生自這個(gè)類,然后定義它的屬性,重寫屬性的原數(shù)據(jù)并傳遞一個(gè)PropertyChangedCallBack參數(shù)即可,如下代碼:
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;
}
}
第二種方法:這個(gè)方法更加簡(jiǎn)單,獲取DependencyPropertyDescriptor并調(diào)用AddValueChange()為其掛接一個(gè)回調(diào)函數(shù),如下代碼:
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");
}
十四. 代碼段(自動(dòng)生成)
代碼段可以說是一個(gè)非常普遍且實(shí)用的功能,我們可以利用它來簡(jiǎn)化和規(guī)范我們的代碼。在項(xiàng)目當(dāng)中我們通常會(huì)定義大量的代碼段,如怎樣寫一個(gè)類、怎樣定義一個(gè)方法、公用代碼庫等等都可以定義成代碼段,今天不著重講這一塊,下面就來看看在默認(rèn)的VS中有哪些代碼段:
上面看到的是visual basic的代碼段(一不小心截圖截錯(cuò)了,呵呵),但不幸的是針對(duì)C#的代碼段卻很少。不過沒關(guān)系,既然默認(rèn)沒有提供那么多代碼段,我們可以自己動(dòng)手嘛,正所謂自己動(dòng)手豐衣足食嘛!相信大家都有自定義代碼段的經(jīng)歷,同時(shí)在網(wǎng)上也有很多好的代碼段下載,我用得最多的是DrWPFSnippets,由于接觸WPF和Silverlight是在07年,所以當(dāng)時(shí)自己也定義過一些代碼段,由于自己主要精力還是在技術(shù)架構(gòu)、ASP.NET、WCF、OO等方面,所以在08年以后就開始使用網(wǎng)上的代碼段資源了,當(dāng)然由于之前項(xiàng)目也自己寫了一些代碼段,所以很多時(shí)候都是混合起來使用,大家可以到http://drwpf.com/blog/2010/04/30/updated-code-snippets/去下載,這個(gè)代碼段包最早從2007年11月就提供下載了,在今年四月份進(jìn)行了升級(jí),同時(shí)支持VS2005/VS2008/VS2010,所以大家可以下載下來體驗(yàn)一下,很不錯(cuò)的哦!下載以后點(diǎn)擊DrWPFSnippets.vsi就會(huì)自動(dòng)安裝,安裝完成以后,你會(huì)看到如下界面,圖中的Shortcut就是你要按的快捷鍵,不過生成的代碼會(huì)出現(xiàn)有些幫助類找不到的情況,如RoutedEvent會(huì)生成一個(gè)RoutedEventHelper的類,這個(gè)是沒有關(guān)系的,你到網(wǎng)上一搜就可以把這個(gè)類加入到你的代碼當(dāng)中。那么運(yùn)行就十分正常了。在安裝的時(shí)候提醒一下,最好一次安裝成功,否則你會(huì)為眾多的彈窗口感到十分厭惡,呵呵!
那么現(xiàn)在你就可以在項(xiàng)目當(dāng)中使用了,如按下re+TAB鍵兩次,你就會(huì)看到如下界面,然后選擇你的選項(xiàng)即可生成需要的代碼(這里re就是Routed event的簡(jiǎn)寫)。
如下是生成的代碼,你可以直接使用或者經(jīng)過適當(dāng)修改使用。
#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
十五. 模擬依賴屬性實(shí)現(xiàn)
古人有”不入虎穴焉得虎子“的名句,我們今天也試著入一入虎穴,探探依賴屬性里面到底藏著什么不可告人的秘密,在往下講之前,我們先來看一下DependencyObject 、DependencyProperty 以及PropertyMetadata到底包含哪些功能,如下面三幅圖
通過前面三幅圖,我們就可以了解WPF依賴屬性系統(tǒng)的大體結(jié)構(gòu)以及主要功能,再者通過前面我們對(duì)它的使用,對(duì)它的內(nèi)部實(shí)現(xiàn)也有一個(gè)相對(duì)比較清晰的認(rèn)識(shí),那么接下來要做的就是:借助Reflector+VS調(diào)試內(nèi)部代碼功能一起來研究其內(nèi)部的實(shí)現(xiàn)原理。 本來想詳細(xì)寫清楚開發(fā)的過程,但是有點(diǎn)多,所以我打算直接講這幾個(gè)類。大家也可以通過這個(gè)思路來試一試,同時(shí)還可以參考Mono的源碼、WF的依賴屬性源碼等。這里要推薦的是周永恒的博客,此人對(duì)技術(shù)的理解很是透徹,博文雖少,但每篇都堪稱經(jīng)典,所以他的文章,我都通讀三遍。雖然大多概念都懂,并且讀到深處也能產(chǎn)生共鳴,其最主要目的還是學(xué)習(xí)他這種”闡述問題的思路“,后來也和此人MSN聊過幾次。所以這個(gè)依賴屬性的框架在某些程度上也借鑒了他的一些寫法。
有了前面的思路,首先定義DependencyProperty這個(gè)類,它里面存儲(chǔ)前面我們提到希望抽出來的字段。DependencyProperty內(nèi)部維護(hù)了一個(gè)全局的Map用來儲(chǔ)存所有的DependencyProperty,對(duì)外暴露了一個(gè)Register方法用來注冊(cè)新的DependencyProperty。當(dāng)然,為了保證在Map中鍵值唯一,注冊(cè)時(shí)需要根據(jù)傳入的名字和注冊(cè)類的的 HashCode取異或來生成Key。 所以我們就可以完成DependencyProperty類了,代碼如下,介紹詳見代碼注釋。:
public sealed class DependencyProperty
{
//全局的IDictionary用來儲(chǔ)存所有的DependencyProperty
internal static IDictionary<int, DependencyProperty> properties = new Dictionary<int, DependencyProperty>();
//存儲(chǔ)元數(shù)據(jù)的集合
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;
// 構(gòu)造函數(shù)
private DependencyProperty()
{
}
//構(gòu)造函數(shù)私有,保證外界不會(huì)對(duì)它進(jìn)行實(shí)例化
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();
}
//注冊(cè)依賴屬性
public static DependencyProperty Register(string name, Type propertyType, Type ownerType)
{
return Register(name, propertyType, ownerType, new PropertyMetadata());
}
//注冊(cè)的公用方法,把這個(gè)依賴屬性加入到IDictionary的鍵值集合中,Key為name和owner_type的GetHashCode取異,Value就是我們注冊(cè)的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");
}
//把剛實(shí)例化的DependencyProperty添加到這個(gè)全局的IDictionary種
properties.Add(property.GetHashCode(), property);
return property;
}
//注冊(cè)只讀依賴屬性
public static DependencyProperty RegisterReadOnly(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata)
{
DependencyProperty property = Register(name, propertyType, ownerType, typeMetadata);
return property;
}
//注冊(cè)附加依賴屬性
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的時(shí)候使用
public void OverrideMetadata(Type forType, PropertyMetadata metadata)
{
metadata.Type = forType;
_metadataMap.Add(metadata);
}
//獲取元數(shù)據(jù)信息
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 來使用這個(gè)DependencyProperty 。首先使用DependencyProperty .Register方法注冊(cè)了一個(gè)新的DependencyProperty ,然后提供了GetValue和SetValue兩個(gè)方法來操作剛剛構(gòu)造的DependencyProperty 。這個(gè)時(shí)候我們看到一個(gè)簡(jiǎn)單的依賴屬性系統(tǒng)已初見端倪了,詳見代碼注釋。
namespace Realize_DPs
{
public abstract class DependencyObject : IDisposable
{
//添加一個(gè)List來記錄修改信息
private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();
//屬性包裝器,通過它來訪問依賴屬性
public object GetValue(DependencyProperty dp)
{
//首先通過判斷是否改動(dòng)過,以此來決定是讀元數(shù)據(jù)的默認(rèn)值還是改動(dòng)了的值
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;
}
}
//屬性包裝器,通過它來設(shè)置依賴屬性的值
public void SetValue(DependencyProperty dp, object value)
{
//首先通過判斷是否改動(dòng)過,以及改動(dòng)過,則繼續(xù)對(duì)改動(dòng)過的元素賦值,否則對(duì)_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()
{
//暫時(shí)還沒有處理
}
}
internal struct EffectiveValueEntry
{
internal int PropertyIndex { get; set; }
internal object Value { get; set; }
}
}
前面有了DependencyProperty 和DependencyObject 類,那我們現(xiàn)在來新建一個(gè)比較重要的類 PropertyMetadata ,它的作用和功能很強(qiáng)大,我們這里只是簡(jiǎn)單進(jìn)行了構(gòu)建,如下代碼:
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;
// 構(gòu)造函數(shù)重載
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)
{
// 實(shí)現(xiàn)元數(shù)據(jù)繼承之間的合并
}
protected virtual void OnApply(DependencyProperty dependencyProperty, Type targetType)
{
// 當(dāng)元數(shù)據(jù)被這個(gè)屬性應(yīng)用,OnApply就會(huì)被觸發(fā),在此時(shí)元數(shù)據(jù)也將被密封起來。
}
}
}
前面我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的依賴屬性系統(tǒng),現(xiàn)在就得先測(cè)試一下其功能,代碼如下:
class Program : DependencyObject
{
public static readonly DependencyProperty CounterProperty;
static Program()
{
//注冊(cè)依賴屬性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("讀取元數(shù)據(jù)設(shè)置的默認(rèn)值: "+pro.Counter.ToString());
Program pro2 = new Program();
pro2.Counter = 22.5;
Console.WriteLine("通過SetValue設(shè)置改變了的值: " + pro2.Counter.ToString());
Console.ReadLine();
}
}
那么測(cè)試結(jié)果為:
利用VS自帶的類圖,可以看到剛才我們實(shí)現(xiàn)的這個(gè)依賴屬性類及類之間的關(guān)系圖:
由于上面的代碼在很多方面都很粗糙,所以希望大家能下載代碼進(jìn)行改造,同時(shí)也希望給出反饋。
十六. 本文總結(jié)
這篇文章洋洋灑灑寫了很多,我們現(xiàn)在簡(jiǎn)單回顧一下:在開篇之前我們會(huì)先介紹比本篇更重要的一些東西,然后插播了一段”云計(jì)算之旅“的廣告(廣告費(fèi)很昂貴 ,所以格外小心),作為最近幾個(gè)月執(zhí)著研究的東西,終于可以在下周和大家見面了,所以心中甚是喜悅。在前面的兩個(gè)內(nèi)容之后我們正式進(jìn)入本篇的主題——依賴屬性。依賴屬性是WPF的核心概念,所以我們花費(fèi)了大量的時(shí)間和篇幅進(jìn)行論述,首先從依賴屬性基本介紹講起,然后過渡到依賴屬性的優(yōu)先級(jí)、附加屬性、只讀依賴屬性、依賴屬性元數(shù)據(jù)、依賴屬性回調(diào)、驗(yàn)證及強(qiáng)制值、依賴屬性監(jiān)聽、代碼段(自動(dòng)生成) 等相關(guān)知識(shí),最后我們模擬了一個(gè)WPF依賴屬性的實(shí)現(xiàn),對(duì)內(nèi)部實(shí)現(xiàn)原理進(jìn)行了一些研究。在接下來的三篇”剖析路由事件”、”剖析命令”、”剖析綁定”也會(huì)采用這篇文章的風(fēng)格,希望能盡量說透,如果有誤之處還希望各位能夠批評(píng)指正!
十七. 相關(guān)代碼下載
在文章的最后,我們提供代碼的下載,這幾篇文章最重要的就是下載代碼來細(xì)細(xì)研究,代碼里面也添加了比較詳細(xì)的注釋,如果大家有什么問題,也可以和我聯(lián)系,如果有不正確的地方也希望多多海涵并能給我及時(shí)反饋,我將感激不盡!
上圖就是整個(gè)代碼包的結(jié)構(gòu)圖,下載鏈接:DependencyPropertiesDemo.rar
NET技術(shù):WPF基礎(chǔ)到企業(yè)應(yīng)用系列7——深入剖析依賴屬性(WPF/Silverlight核心),轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。