欧美日韩亚-欧美日韩亚州在线-欧美日韩亚洲-欧美日韩亚洲第一区-欧美日韩亚洲二区在线-欧美日韩亚洲高清精品

金喜正规买球

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那么單純,并且每個人的基礎都不一樣,當中有還沒有畢業的實習生、剛畢業不久的畢業生、工作了數年的工程師及技術大牛們,所以如何把這些知識很好的插入到每個人的知識樹上面成了我考慮的重點。同時我的心里也比較矛盾,一方面希望參加培訓的同事多一些,另一方面希望人越少越好。前者則是按照常理來考慮的,畢竟培訓者都希望自己培訓,越受歡迎越好,這樣才能使自己的思想得到更多人的認可,自己也能實現分享知識的目的。后者則是擔心怕講不好,少一點人就少一點罪過??墒乔∏蛇@一次是歷次培訓中最多的一次,來參加培訓的同事有一百多人,不過幸好由于會議室坐不下,才分成了兩批,這樣就可以讓我具備了更充分的時間和更好的心態??傊嘤柺窍騼群拖蛲獾奶釤捙c升華,正所謂“自己理解的知識未必能使人家理解”,這不僅考驗的是技術,還考驗了一個人的綜合能力。

(四)結論:

  前面從向內和向外以及擴展三個方面進行了簡單闡述,用一句話概括就是:向內深不可測、向外漫無邊際、擴展才能超越極限。由于這里只是對本文及下面的三篇文章做一些鋪墊工作,所以我們也不細細分解,那么我也得稍微推薦一點資料才對得起大家:第一還是研究微軟的類庫,對我們常見的應用進行研究,可以結合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>

效果如下圖所示: 

 

九. 附加屬性

  前面我們講了依賴屬性?,F在我們再繼續探討另外一種特殊的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就會被調用??梢钥吹浇Y果并沒有完全按照我們先前的流程先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
欧美精品网站一区二区三区 | 日韩一区二区三区免费播放 | 99精品国产福利免费一区二区 | 精品欧乱仑在线 | 观看日本电影 | 国产伦精品一区二区三区视 | 国产一区二区三区美女图片 | 国产又黄又大又粗又硬又猛樱花 | 精品福利一区二区三区免费视频 | 国产一区二区三区精品综合 | 中文字幕亚洲不卡在线亚瑟 | 全网热播最新电影电视剧 | 国产欧美日韩综合视频专区 | 91区国产福利在线观看午夜 | 视频一区二区三区免费观看 | 亚洲jizz| 又色又爽又黄刺激在线观看 | 亚洲高清中文字幕综合网 | 日日噜噜夜夜狠狠视频无 | 精品国产午夜福利不卡在 | 国产欧美一区二区精品久 | 亚洲一区二区三区深夜天堂 | 91短视频在线观看 | 七七影院 | a级情欲片在线观看 | 中日韩无砖码一线二线 | 永久在线免费观看美女热比网站 | 好看热播经典影视视频 | 日日天干夜夜人人添 | 精品一区二区三区密臀在线 | 欧美日韩国产丝袜另类 | 亚洲日韩欧美在线一区二区 | 伦视频中文字幕亚洲天堂网 | 国产激情免费播放 | 成视人a免费观看视频 | 日本一本之道之视频在线不卡 | 成人精品视频一区二区三区尤物 | 精品国产乱码一区二区三区 | 国产欧美国日产在线播放 | 国产精品网红尤物福利在 | 亚洲国产一区在线观看视频 | 97视频专区 | 亚洲天堂 | 美女在线观看永久免费网站 | 视频在线中文字幕亚洲 | 国产免费专区 | 最新电视剧免费观看 | 亚洲日本欧美日韩髙清观看 | 国产欧美一区二区三区精品 | 津渝完整视频线上观看 | 欧美视频人人干人人 | 亚洲欧美日韩综合精品 | 国产欧洲精品自在自线官方 | 视频一区二区在线 | 高清一区二区三区视 | 夜色福利院在 | 免费国产 | 亚欧乱色国产精品免费视频 | 欧美手机手机在线视频一区 | 成人动漫在线播放一区二区 | 国产免费一区 | 国产区精品自拍 | 欧美不卡视频一区发布 | 亚洲激情小说另类欧美 | 亚洲人精品午夜射精日韩 | 亚洲一区二区三区四区视频 | 综合乱伦国产中文 | 亚洲中文字幕30页 | 欧美91| 久一线视 | 果冻传媒视频一二在线观看 | 欧美肥妇bwbwbwbxx | 大香伊人中文字幕伊人 | 亚洲aⅴ男人的 | 欧美综合精品一区二区三 | 精品日韩在线一区二区 | 免费视频精品一区二区三区 | 老司机午夜福利 | 日国产一区三区三区在线观看 | 欧美精品在线播放 | 内裤包裹| 国产欧美在线 | 日韩成人精品无v国产 | 在线精品欧 | 人人鲁免费 | 最新天美传媒 | 在线观看男人的 | 天堂网在线最新版www资源网 | 中文字幕在线观看不卡 | 国产精品高清一区二区三区 | 国产普通话漏脸在线观看 | 91精品国产人成网站 | 国产亚洲一区二区三区在线 | 国产福利在线观看极品美女 | 国产日韩精品一区二区在线观看 | 亚洲熟女精品一区二区成人 | 欧美一区二区三区播放 | 九九九国产视频 | 国产精品一区一区 | 国产高清精品亚洲明星换脸 | 亚洲欧美国产日韩精 | 日本一卡2卡3卡无卡免费 | 亚洲精品乱无伦国产 | 福利片+国产+合集 | 国产一级特黄aaa大片在线观 | 日韩性生活| 在线欧美鲁香蕉94色 | 免费三级在线观看中文字幕 | 成人拍拍拍在线观看 | 亚洲成脛∨人片在线观看福利 | 国产voyeur精品偷窥222 | 性欧美精品一区二区三区在线播放 | 中文字幕精品视频第一区第二 | 中文字幕在线视频观看网站 | 成人影片一区免费观看 | 天堂在线视频网站 | 国产日韩精品在线播放 | 亚洲国产vv | 三级特黄60分钟在线观看 | 婷婷综合尤物精品国产 | 色中色影视| 大伊香蕉精品一区视频在线 | 成人一区专区在 | 亚洲女色福利免费视频 | 91成人深夜在线观看 | 欧美交换乱理伦 | 特黄三级 | 黄又爽免费网站 | 国产一区二区精品在线 | 青青青国产观91 | 黑人操中国女人 | 国产偷国产偷精 | 国产情侣在线视频播放 | 国产在线一区二区三区四区 | 日韩精品电影一区 | 一区二区三区精品视频免费播放 | 中文字幕久 | 日本在线观看中文字幕无线观看 | 暴躁少女csgo视频 | 国产自在自线精品午夜视频 | 国产特黄特色a级在线视 | 秋霞国产午夜 | 亚洲欧美一区视频 | 天天综合网日韩欧美影视导航 | 日韩在线视频在线观看 | 精品人人| 最新欧美精品一区二区三区不卡 | 最新好剧电影在线观影平台 | 国产在线精品一区二区三区直播 | 日韩欧美综合在线另类 | 国产精品亚洲欧美动漫卡通 | 精品人伦一区二区三区蜜桃 | 国产在线精品一区二区中文 | 亚洲国产高清 | 手机看片久 | 99国产婷婷综合在线视频 | 韩国免费视频一区二区三区 | 又粗又黄又猛又爽大片a | 亚洲综合另类小说色区一 | 日韩亚洲一区二区三区 | 二区在线播放 | 国产一级精品高清 | 免免费看 | 在线观看高清三级综合 | 国产不卡视频在线观看 | 亚洲码在线观看 | 亚洲欧美日韩国产一区二区三区 | 国产男生午夜福利免费网站 | 97视频专区 | 欧美黑人在线免费观看 | 欧美a级成人 | 免费一区二区三区视频 | 全网热播最新电影电视剧 | 高清免费a级在线观看国产 最新福利电影在线看 | 521国产精品网站在线观看 | 亚洲色精品一区二区三区 | 白丝内裤 | 99精品国产高清自在线看超 | 亚洲综合专区 | 国产精品三级 | 欧美伊人影院 | 日韩亚洲产 | 精品国产乱码一区二区三区 | 欧美综合亚洲日韩精品区 | 国产精品拍自在线 | 精品欧美一区二区三区在线观看 | 日韩欧美国产精 | 午夜在线观看亚 | 高清在线午夜一区二区亚洲 | 99视频精品全部免费在线 | 国产日韩欧美视频在线观看 | 最新中文字幕在线观 | 91精品国产丝袜在线拍 | 五月丁香婷婷综合影院 | 国产精品人娇在线内谢 | 日本欧美一区二区三区在线观看 | 九九在线观看视 | 少女哔哩 | 啪啦完整高清观看视频 | 三年片大全在线观看免费观看大全 | 免费动漫成本人视频网站 | 2025精品久 | 91九色成人 | 国产日产高| 国产精品亚洲专区在线播放 | 国产人妖兮 | 国产午夜爽爽窝窝在线观看 | 99香蕉国产线观看免费 | www黄在线观看 | 亚洲日韩天堂在线 | 国产亚洲精品ae86 | 国产精品99精品一区二区 | 两个人www在线观看免费视频 | 国产馆精品推荐在线观看 | 免费高清影视资源观看 | 麻花视频v3.2.2纯净版 | 亚洲欧洲自拍图片 | 亚洲色国产电 | 亚洲综合一区自偷自拍 | 一二三区在线播放国内精品自产拍 | 97精品亚成在人线免视频 | 国产精品欧 | 2025亚洲欧美日韩在线观看 | 国产免费三级a在线观看 | 中文字幕不卡 | 美女爽到尿喷出来 | 性欧美xxxxⅹoooo3d画 | 欧美亚洲一区二区三区三 | 午夜三级 | 国产男生午夜福利免费网站 | 欧美.日韩.日本国产视频 | 免免费看 | 国产一级a毛做免费视频 | 精品国产污网站在线观看15 | 极品国产一区二区三 | 国产午夜视频在线观看 | 国产欧美精品一区二区色综合 | 色哟哟精品视 | 三年片大全在线观看免费观看大全 | 日韩不卡手机视频在线观看 | 免费aⅴ大片在线观看 | 午夜视频在线免费 | 国产精品美女一区二区视频 | 天美传奇mv免费观看完整版 | 中国老熟女重囗味hdxx | 吖v国产在线高清播放 | 亚洲911国产精品 | 日韩伦理一区二区三区 | 九九热在线免费观看 | 亚洲中文精品乱伦 | 一道免费一区二区三 | 亚洲综合视频在线观看 | 免费视频大片在线观看 | 国产亚洲欧美另类一区二区三区 | 欧美激情a∨在线视频播放 欧美午夜视频网站在线观看 | 污污污污污污www网 午夜福利小视频400 | 超刺激高跟鞋脚交视频在线 | 国产区成人精品视频 | 热播电视剧在线观 | 免费一级国产大片 | 欧美人成网站观看www | 国产免费观看久 | 亚洲综合色区激情自拍 | 国产在线观看高清看片 | 国产精品一区二区亚瑟不卡 | 精品+在线+国产手机 | 特黄特色的大片观看免费视 | 老司机67194免费观看 | 亚州有码91 | 夜夜草视频 | 伊人热热精品中文字幕 | 国产区综合| 日韩精品电影亚洲一区 | 日本在线观看视频精品一区 | 在线视频直播 | 欧美人成在线播放网站色 | 91天堂а8天堂资源在线官网 | 国内精品一区二区 | 亚洲国产精品一区二区三区 | 国产精品免费视频观看拍拍 | 精品一区二区夜色 | 60分钟日韩床大片免费观 | 国产91蜜芽在线观看 | 亚洲精品综合精品自拍 | 国产熟女激情视频自拍 | 国产国产精品 | 亚洲一级淫片免费在线观看 | 国产精彩乱子真实视频 | 亚洲春色在线观看 | 国产欧美精品一区二区三区 | 亚洲欧美视频一区二区三区 | 日韩电影 | 亚洲欧美日韩看片 | 国产乱xxⅹxx国语对白 | 最近播放中文版在线观看免费 | 全视频tv| 日韩a∨精品一区二区三区 国产亚洲视频中文字幕97精品 | 国产在线视频欧美亚综合 | 国产免费乱理伦片在线观看 | 日本欧美一区二区三区在线观看 | 欧美日韩国产精品二区在线观看 | 亚洲欧美中文字幕乱码在线 | 级欧美一级一级国产 | 欧美日韩不卡中文网 | 成人aⅴ综合视频国产 | 国产精品偷窥熟女精品视频 | 日本一本二本三区免费2025高 | 亚洲欧美一区二区三区电影 | 无限资源最 | 精品偷拍视频一区二区三区 | 极品美女在线观看免费直播 | 亚洲欧美综合高清在线 | 日韩国产一区二区中文字幕 | 一区二区和激 | 一区二区区别是什么 | 国产国拍 | 欧美丰满老妇熟乱xxxxx视频 | 亚洲一区精品在线视频 | 国产福利免费在线观看 | 亚洲国产日韩a不卡线欧美 日韩a优精品在线观看 | 热映电影 | 果冻传媒一区二区天美传媒 | 日韩亚洲欧美高清在线观看 | a级全黄试看30分钟国产 | 在线视频一区二区三区三区不卡 | 亚洲欧美日韩国产综合在线看片 | 国内精品一区二区三区最新 | 欧美日韩视频在线第一区 | 激情五月天色五月 | 午夜伦理电影网电影午夜伦理 | 亚洲欧美综合一区二区三区黄大片 | 日本一区二区高清国产 | 日韩综合网 | 精品一区在线观看 | 国产精品自产拍在线观看花钱看 | 五月天在线观看视频网站 | 国产精品成人aaaaa网站 | 中文字幕精品亚洲一区 | 亚洲高清一区二区三区不卡 | 日韩在线一区二区三区观看 | 国产又黄又大又粗的视频 | 91免费看片| 91大神在线资源观看无广告 | 99久在线观看| 国产欧美日本在 | 91成人精品一区二区三区四区 | 亚洲男人的天堂在线aⅴ视频 | 亚洲裸男gv | 办公室系列欧美精品 | 视频免费热播在线观看 | 国产精品交换 | 97视频在线观看视频 | 色五月日 | 亚洲高清视频一区 | 在线播放成人高 | 91精品欧美产品免费观看 | 亚洲成a人片在线v观看 | 亚洲成a人片在线观看www流畅 | 亚洲欧美另类视频小说专区 | 中文字幕人 | 国产一区二区三区日韩欧美 | 高清一区二区三区 | 欧美精品高清在线观看 | 美国精品午夜剧场免费观看 | 国产91青青成人a在线 | 亚洲高清国产一区二区三区电影 | 欧美国产亚洲一区 | 亚洲2025天天堂在线观看 | 欧美视频人人干人人 | 亚洲熟女一区二区三区 | 亚洲精品国产综合 | 91视频官| 亚洲综合精品第一页 | 国产精品全网免费在线播放 | 国产丝袜在线精品丝袜 | 最刺激黄a大片免 | 揄拍成人国产精品视频 | 国产亚洲美女精 | 91极品蜜桃臀在线播放 | 国产亚洲日本精品成人专区 | 99re热视频这里只有综合亚洲 | 国产日韩欧美亚洲 | 日韩精品欧美精品国产精品 | 免费获取 | 国产一区二区精品 | 亚洲中文字幕姦 | 国产伦精品一区二区三区精品 | 亚洲男人天 | 青青热在 | 九九自拍视频 | 秋霞电影午夜在线观看 | 国产精品福利在线观看免费 | 欧美午夜不卡在线观看最新 | 频精品99 | 国产精品免费精品自在线观看 | 亚洲精品乱码在线观看 | 成年女性特黄午夜视频免费看 | 不卡日韩中文字幕在线观看 | 成年免费大片黄在看 | 欧美高清性色生活片免费观看 | 欧美在线人成北岛玲 | 国产日韩欧美综合一区 | 91秒拍国产福利一区 | 亚洲视频在线免费观看 | www.91| 国产免费观看视频 | 国产精品最新资源在线 | 欧美性猛交xxxx乱大交3 | 午夜免费福利不 | 污污网站在线观看免费 | 最近播放中文版在线观看免费 | 丝袜美腿精| 一区二区三区四区视频在线 | 欧美一区二区三区视频在线观看 | 国产欧美日韩精品综合在线 | 国产一区二区三区不卡在线 | 欧美ab片 | 日本高清不卡aⅴ免费网站 精品国产污 | 国产亚洲精品a在线观看app | 精品視頻無碼一區二區三區 | 日本护士视频欧美无砖专区 | 国产亚洲人成网站观看 | 成人3d精品动漫在线播放 | 精品精品国产免费看不卡 | 97精品国产自在现线免费 | 日本三级做a全过程在线观看 | 精品国内一区二区三区免费 | a∨国产a∨ | 新欧美三级经典在线观看 | 成人国产一区二区三区精 | 亚洲欧美中日韩中文字幕 | 日本一本 | 91欧洲在线视精品在亚洲 | 国产剧情在线一区观看 | 亚洲日韩国产一区二区三区在线 | 日本vs亚洲vs韩国一区三区 | 肥老上视频 | 日本三级韩国三级三级a级按 | 日本高清中文字幕一区二区三区 | 国产激情一区二区三区在线hd | 妺妺窝人体色www在线观看 | 国产极品翘臀在线观 | 欧美日韩国产综合视频在线看 | 亚洲国产大片在线观看 | 99国产婷婷综合在线视频 | 国产精品亚洲社区在线观看 | 又色又爽又黄的视频 | 国产精品va在 | 善良的老师中文字 | 一区视频 | 日本一区二区三区精品视频 | 国产l精品国产亚洲区在线观 | 欧美成在线视频 | 国产中文字幕在线点播 | 欧美日韩精品一区二区在线 | 日本精品一区二区三区 | 国产精品不卡在线观看的a站 | 国产精品福利社 | 午夜福利理论片 | 精品福利一区二区在线 | 91午夜在线免费观看小视频 | 国产xxxxxxbd| 最好免费观看高清视频大全 | 中国老太婆bb | 国产乱理伦片在线观看网站 | 国产又滑又嫩又白 | 性生交生活影碟 | 国产乱国产乱老熟300部视频 | 成人天堂资源在线观看 | 国内20 | 大地影院mv在线观看视频免费 | 麻花豆传媒mv在线观 | 91天堂国产网站 | 8x8×拨牐拨 国产suv精品一区二区6 | 日韩成人精品无v国产 | 国产乱码精品一区二区三 | 在线观看亚洲精品国产 | 不卡视频一区二区三区免费观看 | 国产精品日韩精品 | 2025国产精品自在拍在线播放 | 精品国产品国语在线不卡 | 中文字幕精品视频在线观看 | 99re国产 | 国产专区91| 国产精品13页 | 亚洲日韩欧美在线一区二区 | 好看热播经典影视视频 | 国产精品网友自拍 | 话务耳麦 | 日韩免费在线观看性生活视频 | 国产日本卡二 | 国产福利91精品在线观看 | 日韩欧美国产动漫在线 | 国产亚洲日韩欧美一区二区三区 | 2025免费高清电影大全 | 日韩专区亚洲精品欧美专区 | 538国产精品一区二区在线 | 国产精品不卡免费视频 | 99这里只有精品视频国产 | 亚洲精品在看在线观看 | 欧美一区二区三区日韩免费播 | 国产午夜福利电影免费在线观看 | 中文字幕第一页 | 在线一区免费播放 | 亚洲人成网77777色在线播放 | 国产在线一区二区三区不卡在线 | 国产精品一区二区日韩91 | 在线中文字幕 | 香蕉一区二区三区中文字幕 | 深爱激情 | 国产又大| 亚洲国产自 | 亚洲亚洲人成网站在线观看 | 日本一区二区在线不卡 | 国产自经典三级在线观看 | 精品国产自在在线在线观看 | 亚洲综合成人精品成人精品 | 日本高清中文字幕一区二区三区 | 亚洲国产精品尤物yw在线观看 | 国产亚洲欧美视频 | 国产综合精品一区二区 | 亚洲s色大片 | 欧美激情在线精品video | 国自产拍亚洲免费视频 | 亚洲精品国产摄像头 | 亚洲中文欧美日韩在线不卡 | 亚洲国产精品一区二区www | 色综合中文字幕色综合激情 | 91桃色在线看片 | 国产96在线| 欧美成妇人吹潮在线播放 | 国产一级特黄aaa大片在 | 午夜福利蜜桃青 | 国产视频91完整版播放 | 日韩精品电影一区亚洲 | 99re热视频在线| 免费国产小视频在线观看 | 成年片色大黄全 | 亚洲人成电影网 | 国产一级a爱片在线观看视频 | 三级视频网站在线观看视频 | 亚洲熟女www一区二区三区 | 午夜一区二区三区 | 亚洲精品免费视频 | 日韩大片在线永久免费观看网站 | 精品一区二区在线观看 | 欧美精品国产一区二区三区 | 日本va在线视频播放 | 又黄又粗暴的g | 成人精品一| 亚洲男人的天堂在线观看 | 亚洲高清国产拍精品动图 | 国产voyeur精品偷窥222 | 国产精品黄在线观看免费 | 九一看片 | 九九在线精品视 | 欧美日韩亚洲国产精品自拍 | 偷国产偷精品高清尤物 | 国产亚洲精品福利在线 | 夫妇交换性3中文字 | 在线观看成人年视频免费 | 色一情一伦一区二区三 | 国产免费一区二区三区在线观看 | 欧美日韩性高爱潮视频 | 亚洲成a人在线观看片 | 在线欧美日韩成人 | 国产精品一区二区久 | 在线一区二区美欧视频 | 精品国产福利片在 | 国产黄a三级三级看三级 | 欧美国产日本高清不卡 | 欧美综合视频在线观看 | 观看福利 | 亚洲精品第一国产综合精品 | 国语精品 | 手机在线视频 | 国产精品日韩在线观看一区二区 | 国产又黄又猛又粗又爽的a 羞羞影视 | 国产一区二区网站 | 亚洲天天做日日做天天谢日日欢 | 日韩国产一区二区三区在线 | 加勒比一本大道香 | 亚洲视频中文字幕 | 性一交一 | 亚洲va欧洲va日韩v | 写真福利理论片在线播放 | 337人体做爰大胆视频 | 制服丝袜第一页在线 | 欧美激情视频在线免费观看 | 国产午夜高清高清在线观看 | 污网站在线观看视频平台 | 欧美日韩国产综合视频在线观看 | 国产女女互摸互慰在线观 | 日本强不 | 综合五月激情二区视频 | 国产精品蜜桃 | 国产精品亚洲视频在线观看 | 欧洲亚洲一区二区三区 | 2025天堂在线亚洲精品专区 | 天天看片在线观看 | 欧美日韩精品一区二区三区视 | 国产免费直播在线观看视频 | 国产亚洲情侣一区二区无 | 国产区二区 | 97青青 | 欧美一性一乱一交一视频 | 国产精品亚洲欧美高清 | 三级网站| 国产精品冒白 | 亚洲欧美日韩国产精品一区第一页 | 中奖视频在线观看国产 | 国产人成视频 | 成人欧美在线观看 | 国产绿奴视频在线观看 | 三年片免费观看影视大全视频 | 在线精品亚洲欧洲第一页 | 日本三级韩国三 | 国产主播一区二区三区在线观 | 国产精品成人不卡在线观看 | 7799综合天天看 | 亚洲3d卡通动漫在线 | 在线看片免费人成视频手机观看 | 欧美剧免费在线观看 | 国内一区二区三区在线观看 | 亚洲午夜国产精 | 色综合欧美 | 日韩美女永久网址在线观看 | 亚洲综合另类第一页 | 国产啪亚洲国产 | 在线播放国产不卡免费视频 | 国内揄拍 | 999精品| 国产又黄又粗又色又刺激视频 | 黑人巨大精品欧美视频一区 | 免费人成年激情视频在线观看 | 国产l精品国产亚洲区在线观看 | 国产熟女一区二区五月婷 | 99热国产这里只有精品 | 亚洲偷偷拍一区二 | 91一区二区午夜免费 | 97视频精品全国免费观看 | 国产精品第一偷怕自怕1区 7799天天综合 | 精品視頻無碼一區二區三區 | 美女视频黄的网站全免弗 | 国产精品v欧美精品v日韩精品 | 日本一区午夜艳熟免费 | 精品一卡二卡三卡四 | 国产3级在线观看 | 超前点播最新电影电视剧 | 老师裸露胸免 | 亚洲色一色噜一噜噜噜人与 | 精品动漫福利h视频在线观看 | 中文字幕美日韩在线高清 | 亚洲欧美国产另类首页 | 免费网站看v片在线爱的影院 | 亚洲va欧美va天堂v国产综合 | 精品91视频网站 | 国产亚洲欧美一区二区精 | 44极品视频在 | 亚洲成年看片在线观看 | 99精品国产福利片在线观看 | 亚洲欧美日韩ⅴ在线观看91 | 五月丁香六月综合激情在线观看 | 日本中文一二区有码在线 | 国产免费一区二区三区免费视频 | 美女黄频 | 国产精品激情一区在线观看 | 琪琪午夜福利免费院 | 亚洲精品乱拍国产一区二区三区 | 蜜臀91精品国产高清在线观看 | 国产特黄一级aa在线 | 日韩美女永久网址在线观看 | 伊人亚洲日韩欧美一区、二区 | 女同恋性一区二区三区四区 | 国产视频三级 | 国语自产精品视频在线看 | 国产欧美精品一区二区三区四 | 亚洲人午夜射精 | 日韩一区二区三区视频 | 日本三级网址 | 免费看日产一区二区三区 | 亚欧精品一区二区三区四区 | 国产在线视频在线观看 | 免费播放| 日本中文字幕一区二区有码在线 | 欧美人与动牲 | 丝袜一区二区高跟鞋 | 日韩在线成年视频人网站观看 | 亚洲91精品 | 国产极品| 中文在线а√天堂官网 | 日本欧美视频在线观看三区 | 中文字幕欧美日本亚洲 | 国产拍自 | 亚洲欧美日韩一区在线 | 日本韩一级二级三级 | 国产偷v国产偷v | 三年片免费观看影视大全视频 | 精品视频一区二区三区在线观看 | 国产精品天干天干在线综合 | 亚洲一区二区三区影院 | 精品国产欧美一区二区最新 | 人人天天夜夜曰曰狠狠狠肉感 | 羞羞视频下载 | 亚洲成ⅴ人片乱码色午夜 | 精品国产福利盛宴在线观看 | 天堂а√中文在线官网 | 日韩欧美一区二区三区永久免费 | 国产精品成熟老妇女 | 在线播放亚洲精品 | 日本汚视频在线观 | 免费黃色三級片在线观看18 | 国产福利在线观看永久免费 | 欧美高清国产一区二区三区 | 一二三四视频免 | 舔射插啊~在线观 | 国产一区二区影视 | 精品国产一区 | 伦视频中文字幕亚洲天堂网 | 亚洲偷窥另类xxxxx乱室佳宾 | 五月桃花网婷婷亚洲综合 | 欧美日产欧美日产国产精品 | 欧美国产精品va在线观看 | 花蝴蝶免 | j8又粗又大又长又爽又硬电影 | 这里只有精品在线观看视频 | 亚洲精品国产suv一区 | a级粗大硬长爽猛视频免费 视频二区日韩 | 国产乱子伦不卡视频 | 亚洲中文波霸中文字幕 | 欧美一区精品视频一区二区 | 国精产品一品二品国精品69xx | 91网视频网站 | 好看的电视剧电影 | 玩两个丰 | 水蜜桃视频网站在线观看网址 | 五月综合缴| 一本之道高清 | 亚洲成l人在线观看线路 | 中文字幕一精品亚洲无线一区 | 国产日产韩 | 欧美精品一区 | 精品国产一区二区 | 国产专区一va亚洲v天堂 | 97porm国内自拍视频 | 国产乡下三级全黄三级bd | 宅男噜噜噜一区二 | 高清欧美性猛交xxxx黑人猛交 | 二区三区欧美精品在线观看 | 天美麻花果冻苏蜜清歌 | 日本亚洲黑人在线播放 | 中文免费高清特 | 日本欧美韩国一区二区三区 | 日韩大片在线观看入口 | 精品国产免费第一区二区 | 欧美一区精品视频一区二区 | 亚洲国产精品女人 | 国产精品区免费视频 | 一区二区三区四区在线视频 | 婷婷丁香六 | 国产一级特黄一级毛 | 欧美日韩在线 | 五月综合激情婷婷六月 | 欧美a级情欲片在线观看免费 | 日韩亚洲国产欧美在线看片 | 欧美精品亚洲精品日韩专区va | 另类欧美日韩精品一区二区在线 | 日本免费一区二区在线 | 午夜视频在线观看一区二区 | 日本a级c片免费看三区 | 亚洲成年看片在线观看 | 欧美专区亚洲专区 | 不要播放器看在线播放a国产 | 中文字幕在线观看 | 在线观看91精品国产性色 | 国产一级一级一级国产片 | 日本成a人片在 | 911国产自产精品a | 性生交大全免费看 | 视频一区二区亚洲欧美 | 野花韩国高清免费视频6 | 五十路○の豊満な肉体 | 国产一区二区三区精品视频 | 亚洲日韩欧美九 | 中文有码国产精品 | 综合欧美日 | 亚洲产国偷v| 欧美综合自拍亚洲综合百度 | 国产香蕉国产精品偷在线观看 | 午夜视频在线观 | 神马影院首页 | 欧洲亚洲一区 | 亚洲国产日韩在线人高清au | 欧美日韩亚洲精品瑜伽裤 | 日本一区二区三区免费在线观看 | 日韩色在线影院性色 | 欧美v亚洲v综 | 不要播放器看在线播放a国产 | 亚洲国产精品不卡高清在 |