一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

WPF基礎到企業應用系列7——深入剖析依賴屬性(WPF/Silverlight核心)

  一. 摘要

  首先圣殿騎士很高興這個系列能得到大家的關注和支持,這個系列從七月份開始到現在才第七篇,上一篇發布是在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等技術,在用這些技術做項目的同時積累了較豐富的經驗,那么大家就可以形成自己的一套開發知識庫,知道這些技術怎么能快速搭建企業所需要的應用、知道這些技術在使用中會出現什么樣的問題以及如何解決。那么在這個時候你就可能已經在團隊中起到比較核心的作用,如果項目經理給你一個任務,你也可以很輕松且高效的勝任,同時在項目當中由于你也比較清楚業務邏輯,所以當機會來臨的時候,你很快會成為團隊的骨干,逐漸你就會帶幾個初級一點的工程師一起做項目;如果你不喜歡帶團隊,你就會成為資深的高級開發或者架構師。那么在向外方面我個人認為最重要的是積累經驗,對常見的應用要比較熟悉且有自己總結的一套開發庫——比如對普通的網站、電子商務系統、ERP、OA、客戶端應用等等有比較豐富的經驗。

  (二)向內:

  在前面你使用了這些技術開發項目之后,你會遇到很多問題,為了解決這些問題,你會逐漸研究一些比較底層次的東西。比如對于ASP.NET,你會逐漸的去深入理解ASP.NET的整個處理過程、頁面的生命周期、自定義控件的開發等等,由于自己最初是由C、C++、Java這樣過渡到.NET的,所以對一些細節總喜歡鉆牛角尖,這也浪費了不少時間,但同時也得到了很多意外之喜。

  對于C#語言上,你也會逐漸去刨根問底,想看看這些語法糖背后到底隱藏著什么秘密,很多對.NET技術比較癡迷的人都會選擇對C# 1.0 語言通過IL代碼來深層次認識,然后對C#2.0、C#3.0、C#4.0都編譯為C# 1.0 來學習,這樣他們就能認識到語言的內部到底是怎么執行的,正所謂知道的同時也知道其所以然。

  對于WF,你不僅要知道各 Activity的使用,你也得知道其內部的原理,比如WF 內部就是依靠依賴屬性來在工作流中的各 Activity 間傳遞屬性值的,如果你細心,你還原WF的依賴屬性源碼,你會發現它和WPF、Silverlight中的依賴屬性大同小異,原理基本一樣、只是針對特定技術進行了適當的調整。

  對于數據底層操作也一樣,不管你是用的拼接SQL、存儲過程、IBATIS.NET、Nhibernate,Active Record,Linq to sql、Entity framework還是自己開發的ORM組件,你得明白它內部的原理,你要知道這些開源的代碼還是很值得研究的,我的經驗是先熟練使用這些功能,然后再剖析它的源碼,然后自己寫一套自己的框架,現在我也在精簡自己的ORM框架,因為之前把重點放在了實現盡可能多的功能,所以對性能等細節沒有做過多優化,后面也會向大家慢慢學習。

  對WPF和Silverlight一樣,你不僅要知道怎么用這些技術,你要知道它的原理,比如對依賴屬性,你知道它的內部原理,就可以對平時出現的諸如我設置的值怎么沒有起作用、我Binding的元素怎么沒有出現等等問題; 對路由事件,你也會經常遇到我的事件怎么沒有執行、我的自定義控件事件怎么處理不對、路由傳遞怎么沒有起作用等等,這個時候你如果深入理解了路由事件的內部處理,這些問題就迎刃而解了;對WPF和Silverlight新多出來的命令特性,大家很多時候也是比較疑惑,也會遇到命令失效等等問題,其實如果你深入的了解了它的原理,你就會知道,它其實在內部也是事件,只不過微軟在里面做了很多封裝而已;對Binding就更是如此,我們也不想在這篇文章談開去,畢竟在下面的幾篇文章會詳細的對這些技術進行涉及。

  (三)擴展:

  通過前面的向內和向外的修煉以后,接下來要做的就是不斷實踐,不斷總結經驗,在這個過程中更重要的是要懂得分享,有分享才會使自己和他人共同提高,有分享才能讓自己擺脫狂妄的井底之蛙思想,還記得自己剛做技術的一兩年里,天天喜歡提及大型架構、大型數據處理、操作系統底層代碼如何如何,甚至把AOP、IOC、SSH、OO及設計模式、SOA等詞語時常掛在嘴邊,生怕別人不知道自己不懂。但隨著自己技術實質上的提高以及經驗的積累,自己也就逐漸成熟起來,對這些技術逐漸深入理解且理解了其內部實現原理,這樣反而自己變得謙虛起來了,對之前的那些思想感到無比的羞愧。同時也明白自己在慢慢成長了,現在都習慣戲稱自己為打雜工,其實更多時候用打字員會合理一些,所以希望大家能多多指教,這樣我才能更快地擺脫打字員的生活。我在這里也對擴展做一點小的總結:

  記錄學習:這是學習很重要的一步,你不一定要寫技術博客,你也可以做一些例子來記錄,你也可以在學習之后寫一個總結,畢竟人的精力十分有限,在很多時候,它并不能像硬盤一樣存儲起來就不會丟失,更多的時候它更像一塊內存。

  同道交流:在這一層里我覺得最重要的就是和一些技術較好的人成為朋友,和他們經常探討一些技術,這樣可以縮短學習的周期,同時也能快速的解決問題,畢竟人的精力十分有限,你沒有遇到過的問題,說不定其他人遇到過。在這方面自己也體會頗深,也很感謝之前幾個公司及現在公司的同事、社區朋友以及一些志同道合之士,感謝你們的指點,沒有你們的指點,我也不可能從小鳥進化成逐鹿程序界的菜鳥,我也為自己能成為一只老菜鳥感到自豪!

  少考證、多務實:在擴展的這一層里,我們要謹記不要為了考證而去考證,那樣是沒有任何實際作用的。對MVP也一樣,一切順其自然為好,記得大學時候身邊就有人連續四次榮獲MVP稱號,這叫我在當時是相當的佩服,在佩服之余我們要切記務實,沒有務實的東西都是很虛擬飄渺的。還記得自己當初在大學里面受到考證風氣的影響,神經兮兮的去考過了什么國家計算機四級和MCP等一大堆證件,后來到公司面試興高采烈拿著20多張證書,才知道那些東西根本就沒有什么價值,反而讓自己去學習了最不喜歡的技術,同時也給自己掛上了考證族的名號。所以后來總結就是勞民傷財、徒添傷悲!

  技術分享:在自己公司及其他公司進行一些技術培訓或者討論,其實重要的不是什么榮譽,而是在把這個培訓看成是一些技術交流和分享,因為在這個過程中,你可能會重新認識你所掌握的技術、你可能會遇到一些志同道合的人、你可能會在分享過程中糾正以前的錯誤認識、你可能會在各方面得到提高從而完善自己的知識體系,但是最重要的是你要認真對待每一次培訓,知之為知之不知為不知,不要不能教導他人反而誤導了他人。記得有一次在公司培訓OO與設計模式,我知道這個專題想在一下午的時間把它講清楚是非常困難的,這個不像之后培訓的WPF、WCF和Silverlight那么單純,并且每個人的基礎都不一樣,當中有還沒有畢業的實習生、剛畢業不久的畢業生、工作了數年的工程師及技術大牛們,所以如何把這些知識很好的插入到每個人的知識樹上面成了我考慮的重點。同時我的心里也比較矛盾,一方面希望參加培訓的同事多一些,另一方面希望人越少越好。前者則是按照常理來考慮的,畢竟培訓者都希望自己培訓,越受歡迎越好,這樣才能使自己的思想得到更多人的認可,自己也能實現分享知識的目的。后者則是擔心怕講不好,少一點人就少一點罪過。可是恰巧這一次是歷次培訓中最多的一次,來參加培訓的同事有一百多人,不過幸好由于會議室坐不下,才分成了兩批,這樣就可以讓我具備了更充分的時間和更好的心態。總之培訓是向內和向外的提煉與升華,正所謂“自己理解的知識未必能使人家理解”,這不僅考驗的是技術,還考驗了一個人的綜合能力。

  (四)結論:

  前面從向內和向外以及擴展三個方面進行了簡單闡述,用一句話概括就是:向內深不可測、向外漫無邊際、擴展才能超越極限。由于這里只是對本文及下面的三篇文章做一些鋪墊工作,所以我們也不細細分解,那么我也得稍微推薦一點資料才對得起大家:第一還是研究微軟的類庫,對我們常見的應用進行研究,可以結合Reflector+VS調試內部代碼功能一起研究(IL能幫我們看清楚一些內部原理,但是不推薦細究,因為它會浪費我們很多時間,畢竟是微軟搞出來的這么一套東西,說不定微軟哪天就換了)。其次就是研究MONO源碼(www.mono-project.com),這個是個非常好的東西,對.NET的功能大部分都進行了實現,我之前研究它不是因為它的跨平臺,是感興趣它的源碼,大家也可以在線查看它的源碼(www.Java2s.com),說到Java2s這個網站,也是我平時去得比較多的網站,因為它比較全面和方便,同時也會給我們帶來意想不到的收獲。再其次就是研究一些開源的框架和項目,比如pet shop 4.0(http://software.informer.com/getfree-NET-pet-shop-4.0-download/)、BlogEngine.NEThttp://www.dotNETblogengine.NET/)、Spring.NEThttp://www.springframework.NET/)、Castle(http://www.castleproject.org)、log4NEThttp://logging.apache.org/log4NET/)、NHibernate(http://www.hibernate.org/343.html)、iBATIS.NEThttp://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擁有了依賴屬性后,代碼寫起來就比較得心應手,功能實現上也變得非常容易了。如果沒有依賴屬性,我們將不得不編寫大量的代碼。關于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!";
}
}
}

  屬性是我們再熟悉不過的了,那么究竟依賴屬性怎么寫呢?依賴屬性和屬性到底有什么區別和聯系呢?其實依賴屬性的實現很簡單,只要做以下步驟就可以實現:
  第一步: 讓所在類型繼承自 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 允許我們可以在多個地方設置依賴屬性的值,所以我們就必須要用一個標準來保證值的優先級別。比如下面的例子中,我們在三個地方設置了按鈕的背景顏色,那么哪一個設置才會是最終的結果呢?是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>

  通過前面的簡單介紹,我們了解了簡單的依賴屬性,每次訪問一個依賴屬性,它內部會按照下面的順序由高到底處理該值。詳細見下圖

first  由于這個流程圖偏理想化,很多時候我們會遇到各種各樣的問題,這里也不可能一句話、兩句話就能夠把它徹底說清楚,所以我們就不過多糾纏。等遇到問題之后要仔細分析,在找到原因之后也要不斷總結、舉一反三,只有這樣才能逐漸提高。

  七. 依賴屬性的繼承

  依賴屬性繼承的最初意愿是父元素的相關設置會自動傳遞給所有層次的子元素 ,即元素可以從其在樹中的父級繼承依賴項屬性的值。這個我們在編程當中接觸得比較多,如當我們修改窗體父容器控件的字體設置時,所有級別的子控件都將自動使用該字體設置 (前提是該子控件未做自定義設置),如下面的代碼:

<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的截斷而沒有任何改變,將保留其默認值。所以大家在使用的時候要特別注意這些問題。

proinher

  前面我們看了依賴屬性的繼承,當我們自定義的依賴屬性,應該如何處理繼承的關系呢? 請看下面的代碼(注釋很詳細,我就不再費口水了):

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>

  最后的效果如下:

2010-8-26 1-14-30

 

 

  八. 只讀依賴屬性

  我們以前在對簡單屬性的封裝中,經常會對那些希望暴露給外界只讀操作的字段封裝成只讀屬性,同樣在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>

  效果如下圖所示: 

ReadOnly_DPs  九. 附加屬性

  前面我們講了依賴屬性。現在我們再繼續探討另外一種特殊的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中就可以使用剛才注冊(構造)的附加屬性了:

Attached_Properties  

  在上面的例子中,AttachedPropertyChildAdder 中并沒有對IsAttached采用CLR屬性形式進行封裝,而是使用了靜態SetIsAttached方法和GetIsAttached方法來存取IsAttached值,當然如果你了解它內部原理,你就會看到實際上還是調用的SetValue與GetValue來進行操作(只不過擁有者不同而已)。這里我們不繼續深入下去,詳細在后面的內容會揭開謎底。

  十. 清除本地值

  在很多時候,由于我們的業務邏輯和UI操作比較復雜,所以一個龐大的頁面會進行很多諸如動畫、3D、多模板及樣式的操作,這個時候頁面的值已經都被改變了,如果我們想讓它返回默認值,可以用ClearValue 來清除本地值,但是遺憾的是,很多時候由于WPF依賴屬性本身的設計,它往往會不盡如人意(詳細就是依賴屬性的優先級以及依賴屬性EffectiveValueEntry 的影響)。ClearValue 方法為在元素上設置的依賴項屬性中清除任何本地應用的值提供了一個接口。但是,調用 ClearValue 并不能保證注冊屬性時在元數據中指定的默認值就是新的有效值。值優先級中的所有其他參與者仍然有效。只有在本地設置的值才會從優先級序列中移除。例如,如果您對同時也由主題樣式設置的屬性調用 ClearValue,主題值將作為新值而不是基于元數據的默認值進行應用。如果您希望取消過程中的所有屬性值,而將值設置為注冊的元數據默認值,則可以通過查詢依賴項屬性的元數據來最終獲得默認值,然后使用該默認值在本地設置屬性并調用 SetValue來實現,這里我們得感得PropertyMetadata類為我們提供了諸如DefaultValue這樣的外部可訪問的屬性。

  上面講了這么多,現在我們就簡單用一個例子來說明上面的原理(例子很直觀,相信大家能很容易看懂)

  XAML中代碼如下:

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

  后臺代碼:

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

//修改本地值
void MakeEverythingAzure(object sender, RoutedEventArgs e)
{
UIElementCollection uic = myDockPanel.Children;
foreach (Shape uie in uic) { uie.Fill = new SolidColorBrush(Colors.Azure); }
}
}

  當按下”改變所有的值“按鈕的時候,就會把之前的值都進行修改了,這個時候按下”清除本地值“就會使原來的所有默認值生效。

ClearLocallySetValues

  十一. 依賴屬性元數據

  前面我們看到一個依賴屬性的注冊最全的形式是下面這樣子的:

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屬性系統對依賴屬性操作的基本步驟:

2010-8-26 15-20-38

  • 第一步,基礎值就是上面“六.依賴屬性的優先級”提供的那些顯示設置,所以它的優先級比較好確定,但有些不會按常規出牌,所以也需要注意總結。
  • 第二步,如果依賴屬性值是計算表達式 (如前面示例中的綁定等語法特性),這個時候就會計算表達式的結果作為第二步的值。
  • 第三步,動畫是一種優先級很高的特殊行為,很多時候,我們都會聽到動畫優先的聲音,所以它的優先級高于其他基礎設置;
  • 第四步,強制則是注冊時提供的 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;
}

}
}

  結果如下:

2010-8-26 1-29-16

  當SimpleDP屬性變化之后,PropertyChangeCallback就會被調用。可以看到結果并沒有完全按照我們先前的流程先Coerce后Validate的順序執行,有可能是WPF內部做了什么特殊處理,當屬性被修改時,首先會調用Validate來判斷傳入的value是否有效,如果無效就不繼續后續的操作,這樣可以更好的優化性能。從上面的結果上看出,CoerceValue后面并沒有立即ValidateValue,而是直接調用了PropertyChanged。這是因為前面已經驗證過了value,如果在Coerce中沒有改變value,那么就不用再驗證了。如果在 Coerce中改變了value,那么這里還會再次調用ValidateValue操作,和前面的流程圖執行的順序一樣,在最后我們會調用ValidateValue來進行最后的驗證,這就保證最后的結果是我們希望的那樣了(正如打游戲一樣,打了小怪,在最后過總關的時候還是需要打大怪才能闖關的)。

  上面簡單介紹了處理流程,下面我們就以一個案例來具體看一看上面的流程到底有沒有出入,這個例子改編于Sacha Barber 的Dependency Properties代碼示例,我相信通過這段代碼你會對這個上面講的概念有更清晰地認識。

  UI很簡單,黃色部分顯示當前值,我們在初始化的時候把它設置為100,然后它的最小值和最大值分別設置為0和500,按鈕”設置為-100“企圖把當前值設為-100,按鈕”設置為1000“試圖把當前值設為1000。具體大家看代碼(我都寫了注釋,很容易理解的).

2010-8-26 0-51-57

  依賴屬性代碼文件如下:

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中有哪些代碼段:

2010-8-26 9-12-59

  上面看到的是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的類,這個是沒有關系的,你到網上一搜就可以把這個類加入到你的代碼當中。那么運行就十分正常了。在安裝的時候提醒一下,最好一次安裝成功,否則你會為眾多的彈窗口感到十分厭惡,呵呵!

2010-8-26 10-33-42

  那么現在你就可以在項目當中使用了,如按下re+TAB鍵兩次,你就會看到如下界面,然后選擇你的選項即可生成需要的代碼(這里re就是Routed event的簡寫)。

vsprin

  如下是生成的代碼,你可以直接使用或者經過適當修改使用。

    #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到底包含哪些功能,如下面三幅圖

2010-8-26 18-38-50

2010-8-26 18-52-44

2010-8-26 19-14-21

  通過前面三幅圖,我們就可以了解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();
}
}

  那么測試結果為:

2010-8-26 0-50-36  利用VS自帶的類圖,可以看到剛才我們實現的這個依賴屬性類及類之間的關系圖: 2010-8-26 0-49-19

  由于上面的代碼在很多方面都很粗糙,所以希望大家能下載代碼進行改造,同時也希望給出反饋。

 

 

  十六. 本文總結

  這篇文章洋洋灑灑寫了很多,我們現在簡單回顧一下:在開篇之前我們會先介紹比本篇更重要的一些東西,然后插播了一段”云計算之旅“的廣告(廣告費很昂貴 ,所以格外小心),作為最近幾個月執著研究的東西,終于可以在下周和大家見面了,所以心中甚是喜悅。在前面的兩個內容之后我們正式進入本篇的主題——依賴屬性。依賴屬性是WPF的核心概念,所以我們花費了大量的時間和篇幅進行論述,首先從依賴屬性基本介紹講起,然后過渡到依賴屬性的優先級、附加屬性、只讀依賴屬性、依賴屬性元數據、依賴屬性回調、驗證及強制值、依賴屬性監聽、代碼段(自動生成) 等相關知識,最后我們模擬了一個WPF依賴屬性的實現,對內部實現原理進行了一些研究。在接下來的三篇”剖析路由事件”、”剖析命令”、”剖析綁定”也會采用這篇文章的風格,希望能盡量說透,如果有誤之處還希望各位能夠批評指正!

  十七. 相關代碼下載

  在文章的最后,我們提供代碼的下載,這幾篇文章最重要的就是下載代碼來細細研究,代碼里面也添加了比較詳細的注釋,如果大家有什么問題,也可以和我聯系,如果有不正確的地方也希望多多海涵并能給我及時反饋,我將感激不盡!

DependencyPropertiesDemo  上圖就是整個代碼包的結構圖,下載鏈接:DependencyPropertiesDemo.rar

NET技術WPF基礎到企業應用系列7——深入剖析依賴屬性(WPF/Silverlight核心),轉載需保留來源!

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。

主站蜘蛛池模板: 91在线播放视频 | 国产成人亚洲综合一区 | 久久一区二区三区99 | 韩国一级毛片视频免费观看 | 国产成人精品系列在线观看 | 亚洲国产精品线播放 | 美女的胸又黄又www网站免费 | 中文字幕加勒比 | 日本成人一区二区三区 | 久久久久久综合一区中文字幕 | 国产2页| 超碰97人人射妻 | 国产免费成人在线视频 | 精品国产91久久久久久久 | 美女福利视频一区 | 99在线视频免费 | 国产精品午夜久久 | 真实国产乱子伦精品免费 | 五月激情丁香婷婷综合第九 | 欧美日韩一二三四区 | 黄a级网站在线观看 | 欧美亚洲高清日韩成人 | 免费一级做a爰片性色毛片 免费一看一级毛片人 | 亚洲色图偷 | 香蕉草草久在视频在线播放 | 伊人视屏| 欧美在线精品永久免费播放 | 综合久久久久久久 | 中文乱码精品一区二区三区 | 91久久综合精品国产丝袜长腿 | 激情图片亚洲 | 成人免费在线观看视频 | 亚欧精品一区二区三区四区 | 青青草97国产精品免费观看 | 免费观看国产一区二区三区 | 国内精品小视频福利网址 | 极品美女一区二区三区 | 精品在线观看免费 | 久久精品国产自在一线 | 亚洲aaaa级特黄毛片 | 色哟哟在线网站 |