老司机夜插-理伦理片-理伦片免费-理伦片免费观看-理伦片免费看-理伦日韩-理论福利片-理论片第一页-理论片电影-理论片理论

金喜正规买球

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

轉帖|其它|編輯:郝浩|2010-11-03 14:23:15.000|閱讀 1302 次

概述:前幾篇我們講了WPF的一些基本知識,但是始終沒有接觸最核心的概念,那么從這篇文章開始的下面幾篇文章中,我們會分別深入討論一下依賴屬性、路由事件、命令和綁定等相關概念,希望這幾篇文章對大家能有所幫助。由于自己才疏學淺且是對這些技術的使用總結和心得體會,錯誤之處在所難免,懷著技術交流的心態,在這里發表出來,所以也希望大家能夠多多指點,這樣在使一部分人受益的同時也能糾正我的錯誤觀點,以便和各位共同提高。

# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>

一. 摘要

  首先圣殿騎士很高興這個系列能得到大家的關注和支持,這個系列從七月份開始到現在才第七篇,上一篇發布是在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源碼(),這個是個非常好的東西,對.NET的功能大部分都進行了實現,我之前研究它不是因為它的跨平臺,是感興趣它的源碼,大家也可以在線查看它的源碼(),說到java2s這個網站,也是我平時去得比較多的網站,因為它比較全面和方便,同時也會給我們帶來意想不到的收獲。再其次就是研究一些開源的框架和項目,比如pet shop 4.0()、BlogEngine.NET()、Spring.NET()、Castle()、log4net()、NHibernate()、iBATIS.NET()、Caliburn()、MVVM Light Toolkit()、Prism()等等。這里要注意的是:在研究的過程中一定要先熟悉功能,再研究它內部的源碼和實現,然后再創造出自己的框架。這樣才能激發我們研究的欲望,才會產生作用和反作用力,從而才會使我們真正受益。

四. 云計算廣告插播

  由于這段時間白天要研究云計算專題(公司項目原因,最主要還是自己的興趣使然),晚上和閑暇時間又要寫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="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Button x:Name="myButton" Background="Azure">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Black"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
Click
</Button>
</Grid>
</Window>
 

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

  

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

七. 依賴屬性的繼承

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

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

  Window.FontSize 設置會影響所有的內部元素字體大小,這就是所謂的屬性值繼承,如上面代碼中的第一個Label沒有定義FontSize ,所以它繼承了Window.FontSize的值。但一旦子元素提供了顯式設置,這種繼承就會被打斷,如第二個Label定義了自己的FontSize,所以這個時候繼承的值就不會再起作用了。

  這個時候你會發現一個很奇怪的問題:雖然StatusBar沒有重寫FontSize,同時它也是Window的子元素,但是它的字體大小卻沒有變化,保持了系統默認值。那這是什么原因呢?作為初學者可能都很納悶,官方不是說了原則是這樣的,為什么會出現表里不一的情況呢?其實仔細研究才發現并不是所有的元素都支持屬性值繼承。還會存在一些意外的情況,那么總的來說是由于以下兩個方面:

1、有些Dependency屬性在用注冊的時候時指定Inherits為不可繼承,這樣繼承就會失效了。

2、有其他更優先級的設置設置了該值,在前面講的的“依賴屬性的優先級”你可以看到具體的優先級別。

  這里的原因是部分控件如StatusBar、Tooptip和Menu等內部設置它們的字體屬性值以匹配當前系統。這樣用戶通過操作系統的控制面板來修改它們的外觀。這種方法存在一個問題:StatusBar等截獲了從父元素繼承來的屬性,并且不影響其子元素。比如,如果我們在StatusBar中添加了一個Button。那么這個Button的字體屬性會因為StatusBar的截斷而沒有任何改變,將保留其默認值。所以大家在使用的時候要特別注意這些問題。

 

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

public class MyCustomButton : Button
{
static MyCustomButton()
{
//通過MyStackPanel依賴屬性MinDateProperty的AddOwner方式實現繼承,注意FrameworkPropertyMetadataOptions的值為Inherits
MinDateProperty = MyStackPanel.MinDateProperty.AddOwner(typeof(MyCustomButton),
new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
}

public static readonly DependencyProperty MinDateProperty;

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


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

public static readonly DependencyProperty MinDateProperty;

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

那么就可以在XAML中進行使用了

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

最后的效果如下:

 

八. 只讀依賴屬性

  我們以前在對簡單屬性的封裝中,經常會對那些希望暴露給外界只讀操作的字段封裝成只讀屬性,同樣在WPF中也提供了只讀屬性的概念,如一些WPF控件的依賴屬性是只讀的,它們經常用于報告控件的狀態和信息,像IsMouseOver等屬性, 那么在這個時候對它賦值就沒有意義了。 或許你也會有這樣的疑問:為什么不使用一般的.Net屬性提供出來呢?一般的屬性也可以綁定到元素上呀?這個是由于有些地方必須要用到只讀依賴屬性,比如Trigger等,同時也因為內部可能有多個提供者修改其值,所以用.Net屬性就不能完成天之大任了。
那么一個只讀依賴屬性怎么創建呢?其實創建一個只讀的依賴屬性和創建一個一般的依賴屬性大同小異(研究源碼你會發現,其內部都是調用的同一個Register方法)。僅僅是用DependencyProperty.RegisterReadonly替換了DependencyProperty.DependencyProperty而已。和前面的普通依賴屬性一樣,它將返回一個DependencyPropertyKey。該鍵值在類的內部暴露一個賦值的入口,同時只提供一個GetValue給外部,這樣便可以像一般屬性一樣使用了,只是不能在外部設置它的值罷了。

下面我們就用一個簡單的例子來概括一下:

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

//內部用SetValue的方式來設置值
DispatcherTimer timer =
new DispatcherTimer(TimeSpan.FromSeconds(1),
DispatcherPriority.Normal,
(object sender, EventArgs e)=>
{
int newValue = Counter == int.MaxValue ? 0 : Counter + 1;
SetValue(counterKey, newValue);
},
Dispatcher);

}

//屬性包裝器,只提供GetValue,這里你也可以設置一個private的SetValue進行限制
public int Counter
{
get { return (int)GetValue(counterKey.DependencyProperty); }
}

//用RegisterReadOnly來代替Register來注冊一個只讀的依賴屬性
private static readonly DependencyPropertyKey counterKey =
DependencyProperty.RegisterReadOnly("Counter",
typeof(int),
typeof(Window1),
new PropertyMetadata(0));
}

  
XAML中代碼:

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

效果如下圖所示: 

 

九. 附加屬性

  前面我們講了依賴屬性。現在我們再繼續探討另外一種特殊的Dependency屬性——附加屬性。附加屬性是一種特殊的依賴屬性。他允許給一個對象添加一個值,而該對象可能對此值一無所知。

  最好的例子就是布局面板。每一個布局面板都需要自己特有的方式來組織它的子元素。如Canvas需要Top和left來布局,DockPanel需要Dock來布局。當然你也可以寫自己的布局面板(在上一篇文章中我們對布局進行了比較細致的探討,如果有不清楚的朋友也可以再回顧一下)。

下面代碼中的Button 就是用了Canvas的Canvas.Top和Canvas.Left="20" 來進行布局定位,那么這兩個就是傳說中的附加屬性。

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

  在最前面的小節中,我們是使用DependencyProperty.Register來注冊一個依賴屬性,同時依賴屬性本身也對外提供了 DependencyProperty.RegisterAttached方法來注冊附加屬性。這個RegisterAttached的參數和 Register是完全一致的,那么Attached(附加)這個概念又從何而來呢?

  其實我們使用依賴屬性,一直在Attached(附加)。我們注冊(構造)一個依賴屬性,然后在DependencyObject中通過 GetValue和SetValue來操作這個依賴屬性,也就是把這個依賴屬性通過這樣的方法關聯到了這個DependencyObject上,只不過是通過封裝CLR屬性來達到的。那么RegisterAttached又是怎樣的呢?

下面我們來看一個最簡單的應用:首先我們注冊(構造)一個附加屬性

public class AttachedPropertyChildAdder
{
//通過使用RegisterAttached來注冊一個附加屬性
public static readonly DependencyProperty IsAttachedProperty =
DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyChildAdder),
new FrameworkPropertyMetadata((bool)false));

//通過靜態方法的形式暴露讀的操作
public static bool GetIsAttached(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsAttachedProperty);
}

//通過靜態方法的形式暴露寫的操作
public static void SetIsAttached(DependencyObject dpo, bool value)
{
dpo.SetValue(IsAttachedProperty, value);
}
}

在XAML中就可以使用剛才注冊(構造)的附加屬性了:

  

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

十. 清除本地值

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

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

XAML中代碼如下:

<Window x:Class="WpfApplication1.DPClearValue"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//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); }
}
}

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

 

十一. 依賴屬性元數據

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

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-&gt;P。其中以FrameworkPropertyMetadata參數最多,亦最為復雜。

  FrameworkPropertyMetadata的構造函數提供了很多重載,我們挑選最為復雜的重載來看它到底有哪些參數以及提供了哪些功能:

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

  其中第一個參數是默認值,最后兩個參數分別是是否允許動畫,以及綁定時更新的策略(在Binding當中相信大家并不陌生),這個不詳細解釋了。重點看一下里第三、四兩個參數,兩個 CallBack的委托。結合前面Register的時候提到的ValidateValueCallback共組成三大&rdquo;金剛“,這三個Callback分別代表Validate(驗證),PropertyChanged(變化通知)以及Coerce(強制)。當然,作為 Metadata,FrameworkPropertyMetadata只是儲存了該依賴屬性的策略信息,WPF屬性系統會根據這些信息來提供功能并在適當的時機回調傳入的delegate,所以最重要的還是我們定義的這些方法,通過他們傳入委托才能起到真正的作用。

  上面講了元數據暴露給我們的構造函數,其實在其內部還提供了兩個方法,這個在做自定義控件的時候,也很值得注意:

protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
// 實現元數據繼承之間的合并
}

protected virtual void OnApply(DependencyProperty dependencyProperty, Type targetType)
{
// 當元數據被這個屬性應用,OnApply就會被觸發,在此時元數據也將被密封起來。
}

前面講了這么多,那么我們現在就來看看依賴屬性回調、驗證及強制值到底是怎么使用的呢?大家千萬要堅持住,后面內容更加精彩!

十二. 依賴屬性回調、驗證及強制值

我們通過下面的這幅圖,簡單介紹一下WPF屬性系統對依賴屬性操作的基本步驟:

 

  • 第一步,基礎值就是上面“六.依賴屬性的優先級”提供的那些顯示設置,所以它的優先級比較好確定,但有些不會按常規出牌,所以也需要注意總結。
  • 第二步,如果依賴屬性值是計算表達式 (如前面示例中的綁定等語法特性),這個時候就會計算表達式的結果作為第二步的值。
  • 第三步,動畫是一種優先級很高的特殊行為,很多時候,我們都會聽到動畫優先的聲音,所以它的優先級高于其他基礎設置;
  • 第四步,強制則是注冊時提供的 CoerceValueCallback 委托,它負責驗證屬性值是否在允許的限制范圍之內,和我們對屬性的驗證一樣,比如強制設置該值必須大于于0小于10等等;
  • 第五步,驗證是指我們注冊依賴屬性所提供的 ValidateValueCallback 委托方法,它最終決定了屬性值設置是否有效,當數據無效時會拋出異常來通知。

前面我們講了基本的流程,下面我們就用一個小的例子來進行說明:

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

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

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

private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("當值改變時,我們可以做的一些操作,具體可以在這里定義: {0}", e.NewValue);
}

private static object CoerceValue(DependencyObject d, object value)
{
Console.WriteLine("對值進行限定,強制值: {0}", value);
return value;
}

private static bool IsValidValue(object value)
{
Console.WriteLine("驗證值是否通過,返回bool值,如果返回True表示嚴重通過,否則會以異常的形式暴露: {0}", value);
return true;
}

}
}

 

結果如下:

 

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

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

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

 

依賴屬性代碼文件如下:

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

//屬性包裝器,通過它來暴露CurrentReading的值
public double CurrentReading
{
get { return (double)GetValue(CurrentReadingProperty); }
set { SetValue(CurrentReadingProperty, value); }
}

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

//屬性包裝器,通過它來暴露MinReading的值
public double MinReading
{
get { return (double)GetValue(MinReadingProperty); }
set { SetValue(MinReadingProperty, value); }
}

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

//屬性包裝器,通過它來暴露MaxReading的值
public double MaxReading
{
get { return (double)GetValue(MaxReadingProperty); }
set { SetValue(MaxReadingProperty, value); }
}

//在CoerceCurrentReading加入強制判斷賦值
private static object CoerceCurrentReading(DependencyObject d, object value)
{
Gauge g = (Gauge)d;
double current = (double)value;
if (current < g.MinReading) current = g.MinReading;
if (current > g.MaxReading) current = g.MaxReading;
return current;
}


//當CurrentReading值改變的時候,調用MinReading和MaxReading的CoerceValue回調委托
private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinReadingProperty);
d.CoerceValue(MaxReadingProperty);
}

//當OnMinReading值改變的時候,調用CurrentReading和MaxReading的CoerceValue回調委托
private static void OnMinReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MaxReadingProperty);
d.CoerceValue(CurrentReadingProperty);
}

//在CoerceMinReading加入強制判斷賦值
private static object CoerceMinReading(DependencyObject d, object value)
{
Gauge g = (Gauge)d;
double min = (double)value;
if (min > g.MaxReading) min = g.MaxReading;
return min;
}

//在CoerceMaxReading加入強制判斷賦值
private static object CoerceMaxReading(DependencyObject d, object value)
{
Gauge g = (Gauge)d;
double max = (double)value;
if (max < g.MinReading) max = g.MinReading;
return max;
}

//當MaxReading值改變的時候,調用MinReading和CurrentReading的CoerceValue回調委托
private static void OnMaxReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinReadingProperty);
d.CoerceValue(CurrentReadingProperty);
}

//驗證value是否有效,如果返回True表示驗證通過,否則會提示異常
public static bool IsValidReading(object value)
{
Double v = (Double)value;
return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
}
}
}

 

XAML代碼如下:

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

XAML的后臺代碼如下:

public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
//設置CurrentReading的值,這個時候會觸發哪些變化?調試代碼吧!
gauge1.CurrentReading = 100;
}

private void btnSetBelowMin_Click(object sender, RoutedEventArgs e)
{
//設置CurrentReading的值,這個時候會觸發哪些變化?調試代碼吧!
gauge1.CurrentReading = -100;
}

private void btnSetAboveMax_Click(object sender, RoutedEventArgs e)
{
//設置CurrentReading的值,這個時候會觸發哪些變化?調試代碼吧!
gauge1.CurrentReading = 10000;

}
}

  在上面的例子中,一共有三個依賴屬性相互作用——CurrentReading、MinReading和MaxReading,這些屬性相互作用,但它們的規則是MinReading≤CurrentReading≤MaxReading。根據這個規則,當其中一個依賴屬性變化時,另外兩個依賴屬性必須進行適當的調整,這里我們要用到的就是CoerceValue這個回調委托,那么實現起來也非常的簡單,注冊MaxReading的時候加入CoerceValueCallback,在CoerceMaxReading函數中做處理:如果Maximum的值小于MinReading,則使MaxReading值等于MinReading;同理在CurrentReading中也加入了CoerceValueCallback進行相應的強制處理。然后在MinReading的ChangedValueCallback被調用的時候,調用CurrentReading和MaxReading的CoerceValue回調委托,這樣就可以達到相互作用的依賴屬性一變應萬變的”千機變“。

  換句話說,當相互作用的幾個依賴屬性其中一個發生變化時,在它的PropertyChangeCallback中調用受它影響的依賴屬性的CoerceValue,這樣才能保證相互作用關系的正確性。 前面也提高ValidateValue主要是驗證該數據的有效性,最設置了值以后都會調用它來進行驗證,如果驗證不成功,則拋出異常。

十三. 依賴屬性監聽

  如果想監聽依賴屬性的改變,可以用兩種方法實現,在很多時候,我們兩種方法都會用到:
  用DependencyPropertyDescriptor 比較簡便,在代碼里面寫起來也比較便捷;
  用OverrideMetadata的方式主要在自定義控件以及處理一些類間關系的時候;
  第一種方法:派生自這個類,然后定義它的屬性,重寫屬性的原數據并傳遞一個PropertyChangedCallBack參數即可,如下代碼:

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

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

private static void FlowDirectionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((MyTextBox)sender).FontWeight = (((MyTextBox)sender).FlowDirection == FlowDirection.LeftToRight) ? FontWeights.Bold : FontWeights.Normal;

}
}

 

第二種方法:這個方法更加簡單,獲取DependencyPropertyDescriptor并調用AddValueChange()為其掛接一個回調函數,如下代碼:

private void Window1_Loaded(object sender, RoutedEventArgs e)
{
//第二種方法,通過OverrideMetadata
DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
descriptor.AddValueChanged(tbxEditMe, tbxEditMe_TextChanged);
}

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

十四. 代碼段(自動生成)

  代碼段可以說是一個非常普遍且實用的功能,我們可以利用它來簡化和規范我們的代碼。在項目當中我們通常會定義大量的代碼段,如怎樣寫一個類、怎樣定義一個方法、公用代碼庫等等都可以定義成代碼段,今天不著重講這一塊,下面就來看看在默認的VS中有哪些代碼段

 

  上面看到的是visual basic的代碼段(一不小心截圖截錯了,呵呵),但不幸的是針對C#的代碼段卻很少。不過沒關系,既然默認沒有提供那么多代碼段,我們可以自己動手嘛,正所謂自己動手豐衣足食嘛!相信大家都有自定義代碼段的經歷,同時在網上也有很多好的代碼段下載,我用得最多的是DrWPFSnippets,由于接觸WPF和Silverlight是在07年,所以當時自己也定義過一些代碼段,由于自己主要精力還是在技術架構、ASP.NET、WCF、OO等方面,所以在08年以后就開始使用網上的代碼段資源了,當然由于之前項目也自己寫了一些代碼段,所以很多時候都是混合起來使用,大家可以到去下載,這個代碼段包最早從2007年11月就提供下載了,在今年四月份進行了升級,同時支持VS2005/VS2008/VS2010,所以大家可以下載下來體驗一下,很不錯的哦!下載以后點擊DrWPFSnippets.vsi就會自動安裝,安裝完成以后,你會看到如下界面,圖中的Shortcut就是你要按的快捷鍵,不過生成的代碼會出現有些幫助類找不到的情況,如RoutedEvent會生成一個RoutedEventHelper的類,這個是沒有關系的,你到網上一搜就可以把這個類加入到你的代碼當中。那么運行就十分正常了。在安裝的時候提醒一下,最好一次安裝成功,否則你會為眾多的彈窗口感到十分厭惡,呵呵!

 

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

 

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

#region Swindle

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

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

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

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

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

TrioEventArgs args = new TrioEventArgs(arg, arg2, arg3);
args.RoutedEvent = SwindleEvent;
RoutedEventHelper.RaiseEvent(target, args);
return args;
}

#endregion

十五. 模擬依賴屬性實現

  古人有”不入虎穴焉得虎子“的名句,我們今天也試著入一入虎穴,探探依賴屬性里面到底藏著什么不可告人的秘密,在往下講之前,我們先來看一下DependencyObject 、DependencyProperty 以及PropertyMetadata到底包含哪些功能,如下面三幅圖

 

 

 

 

   

  通過前面三幅圖,我們就可以了解WPF依賴屬性系統的大體結構以及主要功能,再者通過前面我們對它的使用,對它的內部實現也有一個相對比較清晰的認識,那么接下來要做的就是:借助Reflector+VS調試內部代碼功能一起來研究其內部的實現原理。 本來想詳細寫清楚開發的過程,但是有點多,所以我打算直接講這幾個類。大家也可以通過這個思路來試一試,同時還可以參考Mono的源碼、WF的依賴屬性源碼等。這里要推薦的是周永恒的博客,此人對技術的理解很是透徹,博文雖少,但每篇都堪稱經典,所以他的文章,我都通讀三遍。雖然大多概念都懂,并且讀到深處也能產生共鳴,其最主要目的還是學習他這種”闡述問題的思路“,后來也和此人MSN聊過幾次。所以這個依賴屬性的框架在某些程度上也借鑒了他的一些寫法。

  有了前面的思路,首先定義DependencyProperty這個類,它里面存儲前面我們提到希望抽出來的字段。DependencyProperty內部維護了一個全局的Map用來儲存所有的DependencyProperty,對外暴露了一個Register方法用來注冊新的DependencyProperty。當然,為了保證在Map中鍵值唯一,注冊時需要根據傳入的名字和注冊類的的 HashCode取異或來生成Key。 所以我們就可以完成DependencyProperty類了,代碼如下,介紹詳見代碼注釋。:

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

// 構造函數
private DependencyProperty()
{

}

//構造函數私有,保證外界不會對它進行實例化
private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata)
{
this.name = name;
property_type = propertyType;
owner_type = ownerType;
def_metadata = defaultMetadata;
}

// 常用屬性
public PropertyMetadata DefaultMetadata
{
get { return def_metadata; }
}

public bool IsAttached
{
get { return attached; }
}

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

public string Name
{
get { return name; }
}

public Type OwnerType
{
get { return owner_type; }
}

public Type PropertyType
{
get { return property_type; }
}

public Type ValidatorType
{
get { return validator_type; }
}


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

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

//注冊的公用方法,把這個依賴屬性加入到IDictionary的鍵值集合中,Key為name和owner_type的GetHashCode取異,Value就是我們注冊的DependencyProperty
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata)
{
DependencyProperty property = new DependencyProperty(name, propertyType, ownerType, defaultMetadata);
globalIndex++;
property.Index = globalIndex;

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

//把剛實例化的DependencyProperty添加到這個全局的IDictionary種
properties.Add(property.GetHashCode(), property);
return property;
}

//注冊只讀依賴屬性
public static DependencyProperty RegisterReadOnly(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata)
{
DependencyProperty property = Register(name, propertyType, ownerType, typeMetadata);
return property;
}

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

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

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

//子類繼承重寫以及其他需要重寫Metadata的時候使用
public void OverrideMetadata(Type forType, PropertyMetadata metadata)
{
metadata.Type = forType;
_metadataMap.Add(metadata);
}

//獲取元數據信息
public PropertyMetadata GetMetadata(Type type)
{
PropertyMetadata medatata = _metadataMap.FirstOrDefault((i) => i.Type == type) ??
_metadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type));
if (medatata == null)
{
medatata = def_metadata;
}
return medatata;
}

}

  有了DependencyProperty ,那么接下來就需要定義DependencyObject 來使用這個DependencyProperty 。首先使用DependencyProperty .Register方法注冊了一個新的DependencyProperty ,然后提供了GetValue和SetValue兩個方法來操作剛剛構造的DependencyProperty 。這個時候我們看到一個簡單的依賴屬性系統已初見端倪了,詳見代碼注釋。

namespace Realize_DPs
{
public abstract class DependencyObject : IDisposable
{
//添加一個List來記錄修改信息
private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();

//屬性包裝器,通過它來訪問依賴屬性
public object GetValue(DependencyProperty dp)
{
//首先通過判斷是否改動過,以此來決定是讀元數據的默認值還是改動了的值
EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
if (effectiveValue.PropertyIndex != 0)
{
return effectiveValue.Value;
}
else
{
PropertyMetadata metadata;
metadata = DependencyProperty.properties[dp.GetHashCode()].DefaultMetadata;
return metadata.DefaultValue;
}
}

//屬性包裝器,通過它來設置依賴屬性的值
public void SetValue(DependencyProperty dp, object value)
{
//首先通過判斷是否改動過,以及改動過,則繼續對改動過的元素賦值,否則對_effectiveValues增加元素
EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
if (effectiveValue.PropertyIndex != 0)
{
effectiveValue.Value = value;
}
else
{
effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index, Value = value };
_effectiveValues.Add(effectiveValue);
}
}

public void Dispose()
{
//暫時還沒有處理
}
}

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

  前面有了DependencyProperty 和DependencyObject 類,那我們現在來新建一個比較重要的類 PropertyMetadata ,它的作用和功能很強大,我們這里只是簡單進行了構建,如下代碼:

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

public delegate object GetValueOverride(DependencyObject d);

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

// 構造函數重載
public PropertyMetadata()
{

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

protected bool IsSealed
{
get { return _sealed; }
}

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

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

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

protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
// 實現元數據繼承之間的合并
}

protected virtual void OnApply(DependencyProperty dependencyProperty, Type targetType)
{
// 當元數據被這個屬性應用,OnApply就會被觸發,在此時元數據也將被密封起來。
}
}
}

前面我們實現了一個簡單的依賴屬性系統,現在就得先測試一下其功能,代碼如下:

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

//屬性包裝器,暴露讀寫接口
public double Counter
{
get { return (double)GetValue(CounterProperty); }
set {SetValue(CounterProperty, value); }
}

static void Main(string[] args)
{
Program pro = new Program();
Console.WriteLine("讀取元數據設置的默認值: "+pro.Counter.ToString());

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

那么測試結果為:

 

利用VS自帶的類圖,可以看到剛才我們實現的這個依賴屬性類及類之間的關系圖:

 

十六. 本文總結

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

十七. 相關代碼下載

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


標簽:

本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn

文章轉載自:博客轉載自圣殿騎士

為你推薦

  • 推薦視頻
  • 推薦活動
  • 推薦產品
  • 推薦文章
  • 慧都慧問
掃碼咨詢


添加微信 立即咨詢

電話咨詢

客服熱線
023-68661681

TOP
色125综合| 国产高清亚洲精品26u | 色欲AV亚洲永久无码精品 | 精品伊人久久大线蕉色首页 | 啊插得好深高H | 黄色成年人视频 | 亚洲A片无码一区二区蜜桃 亚洲A片无码一区二区蜜桃久久 | 激情aa视频试看免费 | 国产在线视视频有精品 | 亚州色区 | 朝鲜美女免费一级毛片 | 国产不锈钢 | 受快穿被肉来肉去NP男男 | 国产V片在线播放免费观看大全 | 久久亚洲国产最新网站 | gay男打屁股视频网站 | 蜜柚AV久久久久久久 | J8又粗又硬又大又爽又长A片 | 国产人妻久久精品一区 | 免费一级a毛片在线播放视 免费一级a毛片在线播放 | 特级做A爰片久久毛片A片国 | 岛国三级 | 秋霞网一区二区 | 天天色天天爱 | C日本学生妹视频色呦呦 | 亚洲精品久久久久久成人 | 韩国高清大片免费观看在线第9集 | 国产一级黄色毛片 | 国产99网站 | 久久99婷婷五月综合色啪 | 粉泬毛多水多30岁女人都是水 | 日韩精品在线观看免费 | 欧美无修正 | 色爱区综合激情五月综合激情 | 天天干天天舔天天操 | 亚洲一卡2卡3卡4卡5卡乱码 | 国产高清免费观看 | 国产日韩精品一区二区在线观看 | 最近中文字幕高清中文字幕1 | 婷婷激情在线视频 | 蝴蝶谷成人网站 | 怡红院成人网 | 亚欧免费观看在线观看更新 | 在线观看黄片 | 好屌草这里只有精品 | 夂久亚州精华国产精华华液 | 欧美重囗味成人无码区 | 黄网站免费永久在线观看下载 | 小小水蜜桃视频高清在线观看1 | 国产精品高清在线观看地址 | 岳艳的胯下做爰岳艳小说 | 成人免费无码A片免费看软件 | 最近高清中文在线观看国语字幕7 | 奇米在线777在线视频 | 郭美美17.2g ed2k| 真人一级毛片国产 | 哪里看毛片 | 亚洲欧美日韩中文综合v日本 | 国产色婷婷精品综合在线观看 | 精品午夜寂寞黄网站在线 | 国产精品久久无码人妻一区二区 | 国产爽的冒白浆的视频 | www日本在线观看 | 免费护士一级毛片 | 玉蒲电影完整版 | 相爷与女H嗯啊 | 一本大道卡一卡二卡三乱码 - 八 | 久久99热这里只有精品66 | 色四月婷婷| 国产精一品亚洲二区在线播放 | 色猫咪导航 | 国产亚洲精品久久孕妇呦呦你懂 | 国产又色又爽又高潮免费视频麻豆 | 搡女人真爽免费视频大全 | 亚洲一区二区三区夜色 | 国产一级视频在线观看网站 | 台湾18dy| 黄色网页在线看 | 日本高清免费中文字幕不卡 | 99久在线| 国产熟妇久久精品亚洲熟女图片 | 武侠古典91色综合 | 激情综合网五月婷婷 | 黄色片a| 无限免费动漫看片的视频 | 99久久免费国产精品特黄 | 蜜桃视频在线观看免费视频网站WWW | 国产美女流白浆的免费视 | 91制服| 最新亚洲国产手机在线 | 国产人碰人摸人爱视频 | 最近电影大全免费 | 无码专区aaaaaa免费视频 | 美国一级毛片在线观看 | 亚洲精品午夜 | 国产精品成人h视频 | 视频区 国产 图片区 小说区 | 精品国产片一区二区三区 | 成人免费的性色视频网站 | 日韩特黄特色大片免费视频 | 久久精品中文字幕第一页 | 国产亚洲精品久久久久婷婷瑜伽 | 91视频色| 激情影院内射美女 | 免费国产久久啪在线 | 精品四虎 | 粉嫩AV久久一区二区三区王玥 | 最新国产三级久久 | 国产高清在线91福利 | 高清毛片AAAAAAAAA片 | 免费看成人A片无码网站 | 精品亚洲国产成人A片在线播放 | 第一次破處在线国语视频播放 | 日韩欧美伦理片 | 永久黄网站色视频免费观看 | 99re在线精品视频免费 | 国产一区a | 国产美女流白浆的免费视 | 影音先锋a色情av资源 | 影音先锋天堂网资源av | 亚洲AV鲁丝一区二区三区 | 欧洲不卡一卡2卡三卡4卡网站 | 三级韩国日本三级在线 | 国产人妖ts | 欧美日韩一区不卡在线观看 | 五月综合激情婷婷六月 | 人人妻人爽A片二区三区 | 亚洲午夜久久久精品影院视色 | 欧美一级大片免费看 | 国产黄A片三級三級三級 | 精品久久一区 | 青娱乐啪啪 | 一本色道无码道在线 | 狠狠干狠狠干狠狠干 | 久久99久久成人免费播放 | 夜夜综合网 | 欧美性生交大片免费看A片 欧美性生交大片免费看A片免费 | 亚洲色欲成人无码网站在线观看 | 亚洲婷婷国产精品电影人久久 | 体育生爽擼又大又粗的雞巴的动漫 | 男人和女人做污污污的事APP免费 | 青青热久免费精品视频在首页 | 成人午夜免费视频毛片 | av我要看| 精品国产久线观看视频 | 国产精品色拉拉免费看 | 99视屏| 灌饱娇嫩H将军公主最新章节 | 丁香色综合 | 免费一级特黄特色大片在线观看 | 国产品无码一区二区三区在线 | 视频二区 调教中字 知名国产 | 又长又大又粗又硬3p免费视频 | 免费三圾片在线观看 | 欧美成人精品区综合A片 | 欧美性生交18XXXXX无码 | 人妻熟女制服师生中文字幕 | 色婷婷亚洲综合 | 天天干天天添 | 视频一区欧美 | 厨房少妇人妻好深太紧了 | 狠狠ri | 青青草一区 | 麻豆妓女爽爽一区二区三 | 少妇大叫太大太粗太爽了A片 | 波多野结衣免费在线视频 | 天天色综合天天 | 在线看成品视频入口免 | 手机看片日韩日韩国产在线看 | 最近免费视频中文字幕2018完整版 | 中文字幕人成乱在线视频 | 中文字幕乱码日本高清在线 | 苍井空三年级片网站 | 国产精品美女久久久久AV超清 | 亚洲日本欧美国产在线视 | 欧美丰满大乳无码少妇 | 免费国产直接看片av | 一夲道DVD高清无码 一边摸一边叫床一边爽 | 欧美日韩不卡合集视频 | 亚洲伊人久久综合影院2024 | 国产a视频| 日本三级吃奶头添泬无码 | 黄色污网站| 女人一看就湿的爽文 | 女人aaaaa片一级一毛片 | 国产一级精品视频 | 国产成人手机视频 | 国产亚洲色婷婷久久精品99 | 大色网我爱看 | 国内精品久久影院 | 天天噜天天干 | 日韩吃奶摸下AA片免费观看 | 国产成人久久综合第一区 | 亚洲网站免费 | 久久无码欧美一二三区 | 在线观看成人网 | (无码)中文在线 | yellow字幕中文在线观看 | 伊人婷婷综合缴情亚洲五月 | 国产福利资源在线 | 单亲真实乱子伦免费视频 | 亚洲第一成年免费网站 | 国产在线永久视频 | 久久99久久精品久久久久久 | 精品日韩视频 | 97人人澡人人爽人人模 | 亚洲AV成人一区二区三区啪啪 | 亚洲一区二区三区四区五区六 | 日本一本免费线观看视频 | 日本熟妇乱人免费视频 | 亚洲午夜免费 | 欧美xxxx精品另类 | 四虎必出精品亚洲高清 | 四虎影视高清视频在线观看 | 国产色吧 | 清纯漂亮小美女准备啪啪 | 精久久| 最近中文字幕手机大全 | 色综合久 | 边做边爱完整版免费视频播放视频 | 少妇P毛又多又黑A片免费 | 被黑人强到高潮喷水A片 | 丁香花在线视频观看免费 | 黄网在线播放 | 国产a级毛片 | 美国全免费特一级毛片 | 伧理片午夜伧理片毛片日本 | 一本久道久久综合多人 | 久久久久综合中文字幕 | 婷婷在线免费视频 | 国产久| 五月丁香婷婷天堂 | 日韩一区二区三区精品 | 秋霞电影伦理伦理片 | 色情久久久AV熟女人妻网站 | 一个人看的视频在线观看高清 | 伊人久久丁香色婷婷啪啪 | 国产亚洲精品成人AV久久 | 涩欲国产一区二区三区四区 | 久久综合气久久狠狠狠97色 | 玖玖玖精品视频免费播放 | 老司机试看午夜 | 欧美老熟妇又粗又大 | 国产亚洲欧美日韩综合综合二区 | 中文天堂在线视频 | 国产自产v一区二区三区c | 色125综合 | 四虎影视免费完整版在线观看 | 久久综合九色综合国产 | 久久久GOGO无码啪啪艺术 | 日本不卡在线观看免费v | 成人a毛片免费视频观看 | 国产日韩成人内射视频 | 欧美精品免费xxxxx视频 | 99在线观看视频 | 99视频在线 | 精品久久久久久影院免费 | 国产免费A片好硬好爽好深漫画 | 久久视频精品38在线播放 | 日本黄色三级网站 | 亚洲乱码中文字幕久久孕妇黑人 | 麻豆影视视频高清在线观看 | 亚洲熟女乱综合一区二区在线 | 免费看成人AA片无码视频羞羞网 | 日本中文字幕在线播放 | 从零开始的异世界生活第一季 | 欧美人与牲动交xxxx | 日韩一级片网址 | 91精品国产免费青青碰在线观看 | 网站可以免费观看 | 天堂在线网站 | 男女啪啪抽搐高潮动态图 | 色久激情 | 精品一区 二区三区免费毛片 | 蝌蚪窝99视频 | 国产嫖妓一区二区三区无码 | 国产精品99久久免费黑人人妻 | 亚洲国产精品日本无码小说 | 手机在线观看视频免费视频 | 天堂网www中文在线 天堂网www在线资源中文 | 强奷皇后娇呻浪吟前后夹击 | 国产护士资源总站 | 黄色免费播放 | 国语乱码中文字幕 | 日本精品无码特级毛片 | 欧美 国产 亚洲视频 | 我强进了老师身体在线观看 | 色.www | 国产一级αv片免费观看 | 野花社区WWW中文高清版 | 免费视频片在线观看大片 | 亚洲无人区码二码三码区别图 | AV片在线观看免费光看高清 | 亚洲 素人 字幕 在线 最新 | 成人小视频在线观看 | 日本成人区 | 极品少妇粉嫩小泬啪啪小说 | 天天干天天综合 | 和寡妇在做爰 | 免费高清视频免费观看 | 噜啊噜色在线观看视频 | YELLOW字幕中文字幕免费 | 国产jizz美国jizz免费看 | 日本12一14eenxxxxtv | 777奇米影视四色永久 | 日本中文在线 | 国产熟女内射OOOO | 办公室制服丝祙在线播放 | 欧美激情视频在线观看一区二区三区 | 狠狠躁日日躁夜夜躁A片小说免费 | 亚洲国产天堂久久精品网 | 国产真人无码AV在线观看APP | 欧洲无人区码SUV | 淫熟女 | 一级毛片免费在线观看网站 | 亚洲69av| 国产婷婷色 | 国产午夜婷婷精品无码A片 国产午夜视频在线观看 | cao在线| 日本一区二区三区免费播放视频站 | 久久精品熟女亚州AV麻豆 | 免费的中国黄网站大全 | 欧美成人精品动漫在线专区 | 国产黄网在线观看 | 人碰人碰人成人免费视频 | 久草精品在线 | 成人看的视频 | 久久久国产亚洲精品 | 海角社区2024入口地址 | 91福利院| 艳妇荡岳丰满交换做爰 | 另类网站 | 人妻无码AV中文系列免费 | 国产SUV精品一区二区五 | 五月天丁香视频 | 四虎在线观看一区二区 | chinese农树野外videos | 精品在线99 | 亚州日韩精品AV片无码中文 | 亚洲色综合成人 | 邓丽欣6分钟种子 | 在线视频你懂得 | 性av网址大全 | 国产AV人人妻人人爽 | 插插插色欲综合网 | 性香港xxxxx免费视频播放 | 另类重口100页在线播放 | 午夜福利视频合集1000 | 国产黄色在线免费观看 | 成人做爰WWW | 狼人青草久久网尹人 | 亚洲成成品网站源码中国有限 | 桃色社区 | 亚VA芒果乱码一二三四区别 | 无码乱人伦一区二区亚洲一 | 中文字幕va| 院人全年无休计划2免费观看全集完整版 | 精品AV一区二区三区不卡 | 欧美亚洲熟妇一区二区三区 | 免费国产精品视频在线 | 国产精品99r8免费视频2022 | 国产一在线 | 色欲天天天综合网 | 一女被多男灌满白浆受孕 | 大乐透23105晒票 | 亚洲网站黄色 | 国产成人免费高清在线观看 | 国产做国产爱免费视频 | 女闺蜜扒开腿让我CAO她 | 国产女人综合久久精品视 | 色婷婷色综合缴情网站 | 亚洲婷婷国产精品电影人久久 | 丁香婷婷久久 | 日韩成人免费视频播放 | 开心婷婷丁香 | 日韩经典一区 | 翁公与小莹在客厅激情 | 国产欧美日韩中文视频在线 | 免费女性裸身照无遮挡网站 | 韩国成人理伦片免费播放 | 精品日韩在线 | 色欲AV亚洲情无码AV蜜桃 | 最近的最新的中文字幕视频 | 天天射天天干天天插 | 国模超超| 丁香六月深婷婷激情五月 | 桃子视频直播高清在线 | 欧美乱性| 66精品综合久久久久久久 | 久久一日本道色综合久久m 久久一日本道色综合久 | 最近新中文字幕大全高清 | 免费观看又色又爽又黄的 | 成人老司机深夜福利久久 | 国产真人做爰免费视频 | 2018高清国产一区二区三区 | 综合久久88色情 | 伊人久久久久久久久久 | 免费看国产黄线在线观看 | 熟妇乱子作爱视频大陆 | 女人被躁到高潮免费视频 | 国产亚洲精品97在线视频一 | 久久99久久精品国产99热 | 免费播放大片免费观看视频 | 中国国语对白高潮A片 | 伊人蕉久75影院在线播放 | 成年人网站免费 | 久青草国产免费观看 | 长篇肉戏香艳完本小说排行榜 | 人妻体内射精一区二区三区 | 乱欲小话说又粗又大 | 亚州av| 又大又爽又黄无码A片小说 又大又硬又粗做大爽A片 | 在线 国产 欧美 专区 | 国产亚洲欧洲日韩在线观看 | 国产三级国产精品国产普男人 | 欧美日韩一线 | 午夜福利1692免费视颍 | 综合自拍亚洲综合图区Av | 亚洲欧美自拍色综合图 | 99精品久久久久久国产人妻 | 蜜臀亚洲AV永久无码精品老司机 | 最近最新高清中文字幕 | 丰满高潮大叫少妇 | 在线观看国产黄色 | 成人免费永久在线观看视频 | 内射中出无码护士在线 | 无码专区aaaaaa免费视频 | 97制片厂爱豆传媒 | 欧美日韩国产亚洲一区二区 | 久婷婷| 国产毛片视频网站 | A片扒开双腿进入做视频 | 欧美激情bd高清在线播放 | 色-情-伦-理一区二区三区电影 | 国产亚洲精品VA片在线播放 | 国产亚洲第一伦理第一区 | 国产精品扒开腿做爽爽爽王者A片 | 高清av电影 | 狠狠骚 | 亚洲一卡2卡3卡4卡5卡新国色天香 | 日韩精品无码视频一区二区蜜桃 | 色护士精品影院www 色狠狠色综合吹潮 | 国产在线观看免费视频软件 | 夜色贵族图片 | 韩国伦理电影在线看线 | 亚洲国产精品无码成人A片小说 | 久久aⅴ免费观看 | 伦理qvod电影| 欧美干b视频| 777奇米影视一区二区三区 | 超m自缚痴女 | 日本丰满大乳人妻无码苍井空 | 国产又大又黑又粗免费视频 | 色妞网站 | 精品国产中文字幕在线视频 | 一区二区三区高清不卡 | 午夜精品久久久久久久99热 | 亚洲一区二区三区四区五区六 | 久久国产热 | 伊人情人综合成人久久网小说 | 中文字幕免费视频精品一 | 午夜精品人妻无码一区二区三区 | 国产视频一二区 | 亚洲欧洲国产精品久久 | 羞国产在线拍揄自揄视频 | 免费观看亚洲视频 | 任你搞视频这里只有精品 | 黄页免费在线看 | 99re在线视频免费观看 | 国产精品久久久久久人妻香蕉 | 亚洲日本中文字幕区 | 久久精品热2019 | 久久久国产精品福利免费 | 日韩精品视频在线免费观看 | 91制片厂果冻传媒天美传媒 | 苍井空视频线免费观看 | 韩国色情高潮做大尺度电在线观看 | 久久久毛片 | 欧美又粗又猛又爽又黄A片 欧美又大又粗毛片多喷水 欧美又大又粗又湿A片 | 99久久中文字幕伊人情人 | 天天搞天天操 | 精品欧美一区二区在线观看 | 韩国青草视频 | 亚洲视频一区 | 天天操天天干天天拍 | 九九热热九九 | 免费看日韩A片无码视频软件 | 2021国产精品一卡2卡三卡4卡 | 最近高清中文在线国语视频 | 日韩久久一区二区三区 | 亚洲欧美色鬼久久综合 | 乱亲女H秽乱长久久久 | 欧美乱妇高清正版在线观看 | 久久亚洲欧美 | 国内精品伊人久久久久妇 | 青青热久免费精品视频在app | 日本国产一卡二卡三新区 | 2024夜夜干天天天爽 | 久久久网久久久久合久久久久 | 同涩电影网 | 伊人综合网站 | 日本在线视| 福利卡—卡二卡三卡四卡 | 精品亚洲欧美中文字幕在线看 | 国产精品久久丫毛片A片软件 | 91粉色视频在线导航 | 麻豆国产传媒18精品A片 | 日本黄A级A片国产免费 | 亚洲激情中文字幕 | H狠狠躁死你H视频A片 | 俺去也官方 | 国产亚洲日韩精品激情 | 国产内地激情精品毛片在线一 | 无码免费一区二区三区日本A片 | 最近免费字幕中文大全 | 国产一区在线播放 | 91美女视频在线观看 | 丁香色综合 | 成人精品综合免费视频 | 在线视频www777788coom | 高清无码专区av | 日韩成人 | 亚洲人成电影网站在线观看 | 亚洲综合AV久久国产精品凡士林 | 最近最新2019中文在线观看 | 最近新韩国日本免费看 | 一级毛片视频免费 | 亚洲欧洲中文日韩久久AV乱码 | 香港激情黄三级在线视频 | 国产在线综合色视频 | 男女做爰猛烈动高潮A片色情 | 最近的2024中文字幕国语版 小说 | 欧美黑人性xxx猛交 欧美黑人双插 | 日本加勒比在线精品视频 | 亚洲区色情区激情区小说色情书 | 国产99在线观看 | 异族tube欧美疯狂xxx | 日本哺乳期xxxxhd奶水 | 国产精品_国产精品_国产精品 | 国精品午夜福利视频不卡麻豆 | A片高潮抽搐揉捏奶头视频 A片高潮抽搐揉捏奶头视频在线看 | 我们的生活第七季在线观看免费高清 | 久久艳妇乳肉豪妇荡乳A片PY | 国产精品永久免费视频观看 | 真人性做爰无遮无挡动态图 | 亚州少妇无套内射激情视频 | 秋霞伦理片 | 国产精品久久久久久无码不卡 | 美女祼体添鸡把 | 欧美一区二区三区四区在线观看 | 国产九九九九九九九A片 | 无码做爰视频WWW网站建设 | 天噜啦精品免费视频日本免费视频 | 把女人弄爽大黄A大片片 | 中文字字幕在线中文乱码 | 粗大的内捧猛烈进出A片 | 日本无码一区人妻免费视频 | 高潮迭起AV乳颜射后入 | 91成人午夜精品福利院在线观看 | 幻女与人xx00毛片免费 | 久久精品成人国产午夜 | 久久免费国产视频 | 九九国产视频 | 亚洲AV成人一区二区三区在线观看 | 婷婷在线免费观看 | 国产人妻人伦精品98 | 人人看人人干 | 精品视频在线一区 | 国色一卡2卡3卡4卡在线新区 | 宋徽宗是南宋还是北宋 | 国产电影无码午夜在线播放 | 伊人网综合视频 | 99国产精品综合AV无码 | 麻豆视频在线观看完整版 | 小污女导航福利入口 | 亚洲AV久久无码精品热九九 | 工口里番全彩无肉码3D啪啪 | 综合天天 | 午夜伦理一yy4480影院 | 宅男在线永久免费观看99 | 最爽乱小说录目伦小说 | 激情婷婷六月天 | 国产色情18一20岁片A片下载 | 成人午夜视频在线观看 | 老司机精品视频一区二区 | 欧洲-级毛片内射 | 欧美日本性 | 91九色视频在线观看 | 国产又爽又大又黄A片图片 国产又爽又大又黄A片小说 | 亚洲午夜网站 | 曰本无码人妻丰满熟妇5G影院 | 九九久久国产精品大片 | 影音先锋av悠悠资源网 | 日韩精品亚洲专区在线影院 | 成人电亚洲在线 | 亚洲综合日韩精品欧美综合区 | 热久久国产欧美一区二区精品 | 97一期涩涩97片久久久久久久 | 精品无人乱码一区二区三区的优势 | 青草热久精品视频在线观看 | 伊人成综合人网 | 国产精品爽爽久久久久久 | 中文无码乱人伦中文视频播放 | 久拍国产在线观看 | 高h禁伦没羞没躁 | 午夜射精日本三级 | 99久久免费午夜国产精品 | 色婷婷国产精品视频一区二区三区 | JIZZ老师护士| 欧美精品久久99人妻无码 | 91国内视频在线观看 | 狠狠干美女 | 玩弄放荡人妇系列短篇下载 | 久久亚洲精品AV成人无码 | 偷偷鲁在线影院 | 黄色免费看网站 | 好男人视频社区精品免费 | 男男挤奶油进去PLAY高污 | 把女人弄爽的特黄A大片 | 日本大片A成人无码超级麻豆 | a免费网站 | 古装无遮挡一级毛片 | 亚洲欧美日韩综合久久久久 | 性欧美一区 | 黄色你懂的 | 久久免费99精品久久久久久 | 欧美激情综合 | 第章丰腴美妇岳服侍巨龙 | 最近免费更新中文在线观看 | 亚洲精品色情婷婷在线播放 | 欧美AAAA片免费播放观看 | 五月婷婷丁香花综合网 | 欧美精品1 | 成年啪啪网站免费播放看 | 亚洲第一卡二新区乱码 | 99久久99久久久99精品齐 | 国产中文字幕在线视频 | 免费在线成人电影 | 国产色情伦在线观看 | 91制片厂果冻传媒天美传媒 | 99国产精品国产精品 | 国产精品 日韩 | 亚洲精品一区二区三区四区乱码 | 三八色 | 波多野结衣在线视频免费观看 | 色欲AV久久一区二区三区 | 色在线视频观看 | 久久穴 | 国产精品自在自线亚洲 | 日韩精品免费看 | 国产成人精品一区二区三区影院 | 亚洲精品一区无码A片 | 欧美性色网 | 免费晚上看片www | 久久精品AV一区二区无码 | 越南护士毛茸茸性 | 阿v天堂2024在无码 | 国产免费观看黄A片又黄又硬小说 | 精品亚洲麻豆1区2区3区 | 色多多成人版污污网站APP大全 | 91在线看视频 | AV国産精品毛片一区二区 | 三级毛片免费 | 日本成人久久 | 日韩A片中文字幕视频免费 日韩MV欧美MV中文无码 | 99r在线| 日本欧美亚洲中文在线观看 | 国产不卡视频一区二区三区 | 国产综合无码一区二区色蜜蜜 | 2024一本久道久久综合狂躁 | 亚洲欧美日本综合 | 亚洲欧美日韩在线观看一区二区三区 | 免费毛片视频网站 | 日本黄色xxxx | xxxxxbbbbb欧美性极品 | 少妇扒开粉嫩小泬视频 | 99久久无码一区人妻A片竹菊 | 中文字幕色网站 | 日本丰满大乳人妻无码 | 国产色情久久久久久久久 | 色网免费| 黄网页在线观看 | 免费aⅴ在线 | 91女神视频 | 福利视频网址导航 | 国产aⅴ片 | 国产免费AV吧在线观看 | 妞妞影视一二三区 | 在线成 人av影院 | 国产亲妺妺乱的性视频播放 | 国产成人深夜福利在线观看 | 成人男女网18免费0 成人免费在线视频观看 | 97精品超碰一区二区三区 | 日本视频在线免费 | 91免费福利精品国产 | 91香蕉视频免费 | 欧美AAAA级A片又粗又硬 | 99re国产精品 | 午夜窝窝| 老湿机69福利 | 狠狠色丁香久久婷婷综合图片 | 日本又色又爽又黄的A片在线电影 | 国产啪精品视频网免费 | 日韩一区二区三区视频在线观看 | 国产欧美日韩灭亚洲精品 | 国产精品久免费的黄网站 | 成电影人免费网站 | 日本不卡不码高清免费 | 女人一看就湿的爽文 | 毛篇片在线观看地址 | 2024国产精品视频一区 | 一二三四日本高清无吗 | 奶大灬好大灬好硬灬好爽在线播放 | 国精一区二区AV在线观看网站 | 国产三级毛片视频 | 日韩在线观看精品 | 中文字幕人成乱在线视频 | 手机久草视频分类在线观看 | 日韩做A爰片久久毛片A片毛茸茸 | 在线欧美日韩精品一区二区 | 国产精品成人免费福利 | 妞干网免费在线 | 日本高清一卡二卡三卡四卡无卡 | 日韩精品视频免费网址 | www.久久综合| 亚洲日产韩国一二三四区 | 天天噜夜夜噜 | 毛片TV网站无套内射TV网站 | 三级免费网址 | 水蜜桃文化传媒网站 | 久久亚洲人成网站 | 怡红院在线看一区二区 | 和少妇邻居做爰5 | 久久国产精品伦理 | 国内精品久久毛片一区二区 | 黄色国产网站 | 茄子人成年短视频 | 精品人妻无码一区二区三区葡京 | 在线97| 青青自拍视频一区二区三区 | 乱码欧美一卡2卡3卡4 | 亚洲成av人在线视 | 要狠狠撸 | h片在线| 久久精品国产在热久久2019 | 小荡货腿张开给我cao免费视频 | 少妇少妇做爰片AA | 高清不卡毛片 | 成人视频在线视频 | 久久在线视频免费观看 | 最近高清中文在线国语视频 | 美国无人区 | 免费福利视频导航 | 国产亚洲AV片在线观看16女人 | 日韩一区精品视频一区二区 | 精品国产乱码久久久久久人妻 | 相爷与女H嗯啊 | 又大又爽又硬的曰皮视频 | 欧美日韩一卡2卡三卡4卡新区 | 亚洲熟伦熟女新五十路熟妇 | 99久久久无码国产精品AAA | 你懂的日韩 | 久久99精国产一区二区三区四区 | 久久精视频| 999久久久国产精品 999精品国产 | 欧美一级www片免费观看 | 黄色一级片在线免费观看 | 六月成人网 | 国产真实夫妇4P交换A片 | 日本成人不卡 | 黄色片免费播放 | 纯肉高H种马艳遇风流多 | 蜜桃少妇AV久久久久久高 | 波多野结衣在线网站 | 国产福利91精品一区二区三区 | 色狠狠AV老熟女 | 在线网站 | 91影院在线播放 | 攻把受做得合不拢腿play | 色四播播 | 亚洲高清无码在线 视频 | www.伊人网| 少妇高潮惨叫久久久久久欧美 | 亚洲国产中文视频二区 | 日韩18视频在线观看 | 一区二区三区A片无码视频不卡 | 神马电影dy888午夜我不卡 | 东京热主页| 国产在线观看免费视频在线 | 欧美日本一道免费一区三区 | 国产精品恋恋影视 | 美国一级毛片片aa久久综合 | 国产超级乱淫视频播放免费 | 6080yyy午夜理论A片app | 91精品国产亚一区二区三区 | 2021国产成人精品久久 | 日韩一级片在线观看 | 国产一区二区精品在线观看 | 日韩avdvd|