国产精品青草久-国产精品情侣愉拍-国产精品区网红主-国产精品区一区二-国产精品热久久-国产精品热热热-国产精品人aⅴ-国产精品人成在线-国产精品人妻人伦-国产精品人人

金喜正规买球

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
成人午夜福利免费体验区 | 国产午夜亚洲精品不卡 | 日韩精品人成在线播放 | 三级片在线观看午夜av | 伊人婷婷色香五月 | 国产免费高潮流白 | 91果冻传媒天美传媒 | 中文亚洲av片在线观看不卡 | 国产精品三级av及在线观看 | 精品视频在线免费观 | 日本久久综合网站点击 | 国产黑色 | 国产人成无码视频在 | 成人无码影片在线观看 | 亚洲成a人片在线观看天堂无码 | 精品国产哟哟 | 日韩精品一区中文字幕在线 | 97视频在线精 | 国产精品对白清晰受不了了 | 亚洲爆乳无码精品aaa片蜜桃 | 人人片av麻烦 | 精品国产91久久久久久久黄无码 | 按着她的腰疯狂的撞击闷哼 | 国产欧美日韩综合在线成 | 国产精品婷婷午 | 日本中文字幕在线视频一区 | 亚洲av福利无码无一区二区 | 日韩精品欧美在线成人 | 亚洲一区无 | 无码专区永久免费av网站 | 97啪啪| a级毛片观看 | 精品久久久久久亚洲综合网 | 国产乱沈| 国产乱国产乱老熟300部视频 | 亚洲国产精品资源 | 成人视频 | 人人操人人摸97 | 99久久五月天婷婷中文字幕精品 | 无码人妻精品一区二区三区9厂 | 欧美视频一区二区三区 | 在线日本看片免费人成视久网 | 国产精品无码av在毛片 | 中文字幕第1页精品一区 | 国产曰批视频 | 国产ts系列赵恩静在线观看 | 日韩欧美亚蕉久久二一精品视频 | 亚洲av无码一区二区三区dv | 999国产视频网 | 精品久久久久香蕉网 | av天堂午夜精品一区二区三区 | 99久久国产综合精品成人影院 | 亚洲精品美女久久久久9999 | 国产aⅴ视频一区二区三区 国产aⅴ天堂亚洲国产av | 精品一区二区三区 | 精品久久久久久久九九九精品 | 无遮挡1000部拍拍拍免费观看 | 日本高清视频网站www | 国语自产精品视频熟女 | 国产精品亚洲av高清二区 | 亚洲欧美一区二区三区不卡 | 精品久久久久久久久国产免费 | 午夜男女爽爽视频在线观看 | 亚洲午夜福利院在线观看 | 国产成人深夜福利在线观看 | 国产高清午夜精品 | 91大神在线 | 国产精品美女久久久网站动漫 | 中文字幕在线有码高清视频 | 亚洲精品97久久中文字幕无码 | 国产午夜无码片在线观看网站 | 97精品人妻无码专区在线视频 | 午夜a级毛片免费看 | 人人狠狠综合久久亚洲 | 91香蕉视频免费软件下载 | 波多野结衣(波多野結衣) | 国产91色综合久久麻豆 | 中文字幕+乱码+中文乱码www | 精品久久无码中文字幕 | 97无码超碰中文字幕 | 亚洲精品无码专区久久久 | 国产成人精品久久一区二区精品 | 精品无码成人久久久久久动漫 | 国产精品理论片在线观看 | 国产丝袜调教视频免费的 | 欧美伊人久久大香线蕉 | 日本有码在线中文字幕 | 特级丰满少妇一级aaaa爱毛片 | 国产成人喷潮在线观看 | 午夜在线观看亚洲国产欧洲 | 国产在线麻豆影院 | 午夜视频在线观看免费观看在线观看 | 日本不卡新2區 | 国产999精品久久久久久 | 国产成人av无码片在线观看 | 日本免费看黄 | 99国精品午夜福利视频不卡 | 97色伦图片| 日本一区二区免费不卡视频 | 色偷偷超碰av男人天堂 | 亚洲精品亚洲人成人网 | 国产免国产免‘费 | 亚洲国产精品看片在线观看 | 欧洲变态另类zozo | 欧美一区二区三区成人片在线 | 深夜精品寂寞在线观看黄网站 | 日韩欧美精品一区二区三区在线 | 国产伦精品一区二区三区在线观看 | 精品无码国产自产在线观看水浒传 | 日韩精品欧美在线视频在线 | 国产成人午夜高潮毛片 | 波多野结衣一区二区免费视频 | 日本熟妇的诱惑中文字幕 | 日本三级片在线观看 | 在线观看高清免费国产 | 91精品国产91久久久久久麻豆 | 国产午夜福利电影免费在线观看 | 午夜丰满少妇一级毛影院 | 亚洲精品专区无码 | 国产亚洲欧洲 | 国产乱子伦精品免费无码专区 | 国产成人亚洲视频在线 | 99久久99久久精品免费看蜜桃 | 二区三区三区不卡 | 无码亚洲一区二区三区在线观看 | 99在线精品日韩一区免费国产 | 一区二区三区欧美 | 成人爱做日本视 | 无码毛片| 亚洲日韩天堂一区二区免费 | 国产爆乳无码一区二区麻豆 | 91大神在线精品网址 | 专区中文字幕视频专区 | 人妻一区二区三区巨免费 | 韩剧tv | 国产激情综合在线观看 | 无码精品 | 国产精品白丝无码视频一区 | 国产av一区二区三区无码野战 | 国产国语特级三级a毛片 | 免费无码久久成人网站入口 | 91大香蕉| 在线看片福利无码网址 | 无码亚洲国产一区 | 国产三区四区在线观看 | 精油按摩性av一区二区 | 国产亚洲精品拍拍拍拍拍 | 国产精品毛片 | 色综合天天综合网国产人 | 中文字幕亚洲视频 | 亚洲一区日韩一区 | 午夜成人性做爰a片无码潘金莲 | 国产午夜福利不卡在线观看 | 亚洲国产成人精品福利无码 | 国产精品视频一区二区三区不卡 | 亚洲熟妇无 | 在线无码专区人妻 | 人人影院| 高清一区二区三区日本 | 久久99精品久久久久久国产 | 91影视免费版 | 亚洲一区免费观看 | 精品人妻无码一区二区色欲aⅴ | 99热这里只 | 日本中文一二区有码在线观看 | 国产成人亚洲影院在线观看 | 国产九九在线 | 国产欧美日韩视频在线观看一区 | 亚洲国产精久久久久久久 | 色婷婷久久综合中文久久 | 国产v精品成人免 | 91精品国产高清久久久久久 | 在线精品ac国产大全 | 99热这里只有成人精品国产 | 午夜福利精品视频在线 | 日本一区二区影院 | 国产精品vā在线观看无码 国产精品va在线观看无码不卡 | 日韩精品无码一区二区三区不卡 | 99在线 | 国产91视频在线观看 | 免费一级a毛片在线播放 | 亚洲国产精彩中文乱码av | a一区二区三区乱码在线 | 日韩av无码一区 | 中文字幕在线看aⅴ无码 | 亚洲国产精品成人精品无码区 | 麻豆精品一区二区视频在线 | 99ri在线 | 亚洲最全av天在线观看 | 波多野结衣乳巨码无在线观看 | 99re8这里有 99re99精品视频在线播放 | 国产精品鲁鲁视频 | 日韩精品无码一区二区三区不卡 | 成年免费a级毛片∴ | 精品国产成人在线 | 欧美日韩午 | 国产成人精品久久综合 | 国产成人午夜福利小电影 | 亚洲精品国产a久久久久久 亚洲精品国产va在 亚洲精品国产啊女成拍色拍 | 欧美激情一区二区亚洲专区 | 无遮挡1000部拍拍 | 精品熟人妻一区二区三区四区不卡 | 91色老久久精品偷偷蜜臀 | 一区二区三区国产 | 99国内精品久久久久久久 | 色视频在线观看 | 99久久精品出品国产一区 | 亚洲精品无码午夜福利理论片 | 香蕉视频91 | 欧美精品成人3d在 | 成人乱人乱一区二区三区 | 国产男人的天堂 | 国产专区在线观看 | 国产乱子伦农村叉叉叉 | 国产在线精品一区二区中文 | 熟妇人妻av无码一区二区三区 | 日韩区欧美区中文字幕 | 国产精品视频免费一区二区三区 | 午夜男女很黄的视频 | 国产成人精品综合久久久免费观看 | 高清一区二区三区视频 | 精品人人妻人人澡人人爽人人 | 国产亚洲av片天天在线观看人 | 天天爽夜夜爽夜夜爽 | 91香蕉在线观看 | 黄网站色视频免费观看 | а√天堂在线观看免费 | 人妻少妇精品 | 91麻豆精品国产高清在线 | 欧美日韩一区二区三区高清在线 | 欧美精品第一页 | 内射一区二区精品视频在线观看 | 亚洲综合亚洲综合网成人 | 日本精品视频在线观看 | 亚洲a∨毛片无码专区国产乱码 | 在线免费观看日本视频 | 91a∨精品影库一二三区 | 成人精品久 | 国产精品高清一区二区三区久久你 | 久久91精品国产91久久户 | 日产一线二线三线 | 91成人网址 | 亚洲熟妇| 在线观看日韩欧美 | 亚洲精品久久久久久伊人 | 福利一区二区三区 | ts俞喵喵国产人妖在线播放 | 日韩精品一区二区三区中文在 | 亚洲av无码久久久久久精品 | 国产a∨国片精品一区二区 国产a∨精品一区二区三区不卡 | 午夜成人av乱码无码午夜 | 国产啪精品视频网给免丝袜 | 日本理论午夜中文字幕 | 国产欧美综合在线观看 | 亚洲精品www久久久久久 | 91婷婷 | 高跟黑色丝袜国产91在线 | 日韩欧美三级 | 亚洲欧美精品中文字幕 | 99热成人精品国产免 | 国产高清精 | 国产一级特黄在线播放 | 国产三级aⅴ视频在线观看 国产三级a毛视频在线观看 | 97久久久久人妻精品专区 | 久别的草原电视剧免费观看高清 | 亚洲国产欧美在线人成 | 韩国理伦片一区二区三区在线播放 | 欧美日韩国产高清视频 | 美女粉嫩流水一区二区三区 | 成人毛片18女人毛片免费看 | 国产日韩欧美一区 | 中文字幕av一区二区三区人妻少妇 | 国产午夜无码片在线观看 | 国产一区二区三区不卡视频在线 | 亚洲日韩av片在线观看 | 国产一二三区视频 | 国产精品视频每日更新 | 国产国语av毛片在线看 | 国产精品成 | 日韩精品中文字幕 | 国产成人精品无缓存在线播放 | 午夜电影院理论片8888琪 | 午夜欧美日韩精品久久久久久 | 国产黃色精品三級一区二区 | 国产一级在线观看视频 | 国产精品美女自在线观看免费 | 国产天堂av手机在线 | 精品久久亚洲一级α | 国产精品无码久久综合网 | 亚洲国产精品va在线观看香蕉 | 免费无码又爽又刺激视频在线 | 无码专区日韩精品中文字幕 | 午夜电影网首页 | 97伦理影院[天蚕土豆] | 二区精品视频在线观看 | 欧美日产影院久久 | 国产中文字幕乱人伦在线 | 国产伦精品一区二区三区 | 欧洲在线性爱av | 国产精品人伦一区二区三 | 国内自拍视频一 | 99亚洲乱人伦aⅴ精品 | 亚洲欧美日韩精品久久亚洲区 | 国产一区丝袜高跟在线i91传媒 | 在线精品91青草国产 | 91传媒完整版高清在线观看 | 欧美精品黑 | 91九色蝌蚪 | 国产欧美日韩一区 | 99国产精品国产精品九九 | 中文字幕在线观看 | 欧美最猛性xxxxxx | 国产区视频在线观看 | 91在线超高颜值国产 | 国产成人精品 | 精品无码久久久久久国产 | 国产成人欧美视频在线观看 | 国产精品亚洲欧美一区麻豆 | 午夜秋霞| 日本久久高清一区二区三区毛片 | 91精品国产色 | 91视频app下载污污 | 99re99精品视频在线播放 | 性色av性色生活片 | 一级片在线观看 | 精品在线一区二区 | 精品欧美日韩视频一区二区 | 91精品区 | 97久久国产亚洲精品超碰热 | 成年人毛片网 | 中文字幕高清有码在线中字 | 亚洲va中文在线播放免费 | 亚洲va中文字 | 国产老太睡小伙子视频 | 综合亚洲av图区 | av网站在线免费观看 | 国产日产亚洲系列最新 | 色欲网天天无码av | 二区啪视频 | 午夜三级毛片欧美国产精品 | 中文字幕无码一区在线 | 亚洲国产成人精品久久久国产 | 中文字幕有码在线观看 | 国产精品成人va在线观看网 | 99久久99久| 欧美性狂猛xxxⅹxx吞精 | 午夜国产大片免费观看 | 91国偷自产一区二区三区 | 国产精品嫩草影院永久第一 | 91麻豆精品激情在 | 欧美日韩精品久久 | 日韩午夜理论免费tv影院 | 国产精品专区第一页在线观看 | 久久艹鲁鲁射 | 国产成人无码精品久久二区三区 | 黄网站免费在线观看日韩 | 老司机成人永久精品视频 | 久草免费资 | 亚洲欧美一 | 97久久香蕉国产线看观看 | 97影院午夜午夜伦不卡 | 亚洲成人黄色在线观 | 日本亚洲精品视频 | a级国产乱理伦片在线观 | 亚洲国产综合精品一区在线播放 | 动漫无遮挡h纯肉亚洲资源大片 | 中文精品久久久久中文 | 国产爆乳无码视频在线观 | 成人看片黄a免费看那个网址 | 国产手机在线αⅴ片无码观看 | 日韩一区二区三区不卡免费毛片av | 国产专区亚洲精品无码 | 国产91视频在线观看 | 日韩人妻香蕉网在线 | 国产成人午夜福利r在线观看观看 | 福利区在 | 无码国产成人久久 | 熟妇高潮精品一区二区三区 | 午夜av内射一区二区三区红桃视 | 国产亚洲精久久久久久无码 | 亚洲伦无码中文字幕 | 不卡一区二区视频日本 | 天天爽爽夜夜爽国产精品欧 | 国产精品亚洲v毛片一区二区 | 中文字幕精品在线观看 | 999国内精品永久免费视频 | 国产永久观看在线 | 91精品久久综合 | 日韩另类无码变态视频 | 99久久国产综合精品网成人影院电影 | 无码专区3d动漫精品一区二区 | 91久久人澡人人添人人爽欧美 | 中无码人妻丰满熟妇啪啪 | 亚洲成人黄色在线观 | 国产免费aⅴ片在线观看麻豆 | 四虎影视国产永久免费 | 99久久精品免费观看区一 | 国产av无码专区亚洲awww | 91香蕉视频在 | 天天操天天日天天操 | 变态另类国产 | 国产亚洲综合一区二区a片吴施蒙 | 色综合天天综合网国产人 | 国产精品网红尤物 | 亚洲欧美国产 | 亚洲另类激情综合偷自拍 | 国产午夜精品一区二 | 91精品啪在线观看国产色 | 毛片va一区二区三区 | 日韩午夜精品免费理论片 | 欧美一级专区免费大片 | 亚洲精选一区二区 | 天美传媒免费观看一二三在线 | 天美传媒麻豆精东蜜桃 | 亚洲欧美精品一中文字幕 | 麻豆一区二区三区精品视频 | 亚洲va欧美va | 午夜无码s片在线观看影院 午夜无码不卡中文字幕最新 | 丰乳肥臀| 午夜伦理片720p | av一区| 亚洲精品综合精品自拍 | 欧美乱人伦中 | 97中文人妻免费观看 | 日韩av蜜桃永久无码精品 | 亚洲三级片视 | 国产户外露出在线观看 | 天堂网果冻传媒 | 中文字幕久精品免费视频 | 成人福利网站 | 91精品国产自产91精品蜜臀 | 牛牛本精品99久久精品 | 中文字幕亚洲综合久久2025 | 午夜理论片最新午夜理论剧 | 亚洲成av人片一区二区密柚 | 亚洲aⅴ无码精品一区二区三区 | 天美传媒免费观看一二三在线 | 精品国产精品国自产观看 | 在线日韩国产图区精 | 色视频综合无码一区二区三区 | 欧美亚洲爆乳一区二区三区 | 在线观看一级国产 | 精品无码一区 | 国产精品秘麻豆免费版下载 | 亚洲国产私拍精品国 | 亚洲综合色一区二区三区另类 | 无码三级在线看中文字幕 | 91精品夜夜夜一区二区 | 无码精品视频 | 国产精品白丝祙喷水视 | 国产香蕉在线观看 | 一区二区三区美女视频免费观看 | 国产三级在线观看播放 | 日韩av不卡在线观看五月 | 色综合久久久久久久久久 | 亚洲午夜无码片在线观看影院百度 | 无码欧精品 | 国产91高潮流白浆在线播放un | 狠狠色狠狠综合天天 | 福利视频导航大全 | 国产成人av免费观看 | 97精品国产高清自在线 | 亚洲日韩欧美一区二区三区 | 国产在线一区二区三区四区 | 人体内射精一区二区三区 | 精品久久无码中文字幕一区 | 在线无码一区二区三区不卡国产 | 亚洲中文字幕无码av | 欧美日本精品一区二区三区 | 无码aⅴ精品一区二区三区 无码aⅴ精品一区二区三区浪潮 | 一区二区自拍中文字幕福 | 无码国产69精品久久久久网站 | 免费一区二区三区手机av | 精品九九99久久人妻免费 | 在线a毛片免费视频观看 | 国产成人午夜精品免费 | 国产精品自在在线免费 | 97亚洲精品无码久久久久久久 | 免费无码av片在线观看潮喷 | 亚洲精品伊人久 | 欧美成人精品 | 国精品无码一区二区三区在线 | 99精品热在线高清观看视频 | 在线成年女人免费视频播放器 | 国产一区国产二 | 99久久精品九九亚洲精品 | 波多野结衣无码高清在线观看 | 国产91无码网站在线观看 | 日本windowsphone| 国产乱人伦av在线a麻豆 | 成人国产精品日本在线观看 | 91人妻人人爽人人狠狠 | 色婷婷在线观看视频 | 亚洲大片在线观看网址 | 极品无码av国 | 国产精品99久久久久久一二区 | 国产一级毛片一区二区三区 | 成人www视频网站免费观看 | 精品日本一区二区三区在线观看 | 国产日韩精品中文字无码 | 国产亚洲日韩av在线播放 | 国产女人乱 | 69无人区卡一卡二卡 | 亚洲аv天堂手机版在线观看 | 国产精品自产拍在线观看55 | 亚洲精品视频久久 | 亚洲国产成人片在线观看 | 欧美精品一国产成人综合久久 | 日韩一区二区三区精品 | 中文亚洲av片在线1观看 | 四虎国产精品成人免费久久 | 99riav在线播视频 | 亚洲v在线观看v | 亚洲另类无码专区国内精 | 精品国产无码大片在线看 | 中文无码伦av中文字幕 | 91大神精品全国在线 | 欧美色蜜桃97高清在线观看 | 日本大片免a费观看视频 | 国产免费福利视频一区二区 | 99国内精品久久久久久久 | 97伦理影院[天蚕土豆] | 日韩中文字幕无码 | 日夜夜操天天爽在欧美亚 | 国产一级二级免费观看 | 三级黄色爱情片 | 亚洲国产一区二区久久 | 九九免费久久这里有精品23 | 国产毛片情侣视频 | 91精品国产91久无码网站 | 91人人| 日韩有码欧美激情 | 亚洲精品高清一线久 | 人妻夜夜爽天天爽一区 | 蜜臀av性久久久久蜜臀aⅴ麻豆 | 欧美亚洲精品中文字幕乱码免费 | 69sex久久精品国产麻豆 | 国产91白丝在线播放 | 无码一区二区三区中文字幕 | 午夜福利一区二区三区不卡 | 91天堂| 色先锋久久亚洲中文字幕 | 国产一区2区 | 欧美午夜免费大片 | 亚洲重口无码av影院 | 91亚洲精品在线 | 91视频播放 | 午夜在线视频91精品 | 国产精品午夜无码av体验区 | 在线观看黄色小视频 | 一级做a爱全免费视频免费 一级做a爰片 | 国产无套粉嫩白浆在线精品 | 丰满少妇被猛烈高清播放 | 亚洲国产成人精品福利 | 欧美系列 | 中日av乱码一区 | 无码国产精品一区二区vr | 精品久久久久久久无码 | 99国产综合亚洲精品 | 亚洲精品一区中文字幕乱码 | 日韩av无码专区免费 | 国产欧美a∨一区二区 | 九月婷婷人人澡人人添 | 国产做a | 国产真人无码作爱免费视频 | 99精品国产在热久久 | 亚洲欧美日韩久久 | 国产精品碰碰人人a久久 | 波多野结衣视频在线观看 | 亚洲精品成人久久av | 欧美极品少妇 | 国产福利网 | 丰满爆乳bbwbbwbbw | 无码免费不卡av手机在线观看 | 亚洲国产一区二区三区 | 国产成人午夜福利r在线观看观看 | 国产妇女性爽视频免费 | 91国在线啪精品一区 | 欧美曰韩一区二区三区 | 国产综合精品 | 亚洲av色一区二区三区 | 91大神精品视频高清免费观看 | 成全在线观看免费高清电视剧 | 99久久国产精品免费热麻豆 | 99视频精品国产在线视频 | 91视频在线| 国产亚洲精品久久久久片小 | 很黄的网站在 | 成人h在线一区二区 | 成人av在线一区二区三区 | 99久久国产精品免费热麻豆 | 国产精品精 | 日韩爽爽视频爽爽 | 波多野结衣亚洲av无码无在线观看 | 国产欧美国日产网站 | 日韩精品无码一区二区三区三州 | 午夜福利免费院 | 国产欧美日韩va另类在线播放 | 少妇高潮无乱码高清在线观看 | 国产探花在线观看 | 亚洲免费在线视频观看 | 99精品免费在线观看 | 91视频免费版app下载 | 午夜看片网址97久久精品视频 | 国内高清| 性无码一区二区三区在线观看 | 国产动漫频道 | 亚洲精品无码 | 免费一级a毛片在线播放 | 国产精品无码av在毛片 | 欧美成人污午夜免费福利在线观看 | 在线看所有av的网站 | 午夜国产片在线观看精品 | 91免费国产在线观看 | 国产精品乱码一区二区三区软件 | 成人综合高清久久亚洲中文字幕精 | 东京热一区二区av | 国产成人综合高清 | 欧美精品一区二区三区不卡网 | 国产国产久热这里只有精品 | 精品国产三级a | 91国精产品自偷自偷现象深度解析 | 欧美精品v国产精品 | 国产精品va无码一区二区三区 | 日韩精品免 | 午夜成人精品福利网站在线观看 | 成人午夜性a一级毛片免费看 | 91精品一区国产高清在线gif | 无码久久久久冒白浆 | 日韩av片无码一区二区三区不 | 99香蕉视频 | 国产欧美一区二区三区午夜精品 | 亚洲av无码一区二区三区在线播放 | 成人性爱视频在线观看 | 久久99精品久久久久久国 | 国产成人精品magnet | 97久久精品一区二区三区剧情介绍 | 亚洲精品免播放器在线 | 99精品免视看 | 91精品成人福利在线播放 | 国产三级视频在线 | 成人精品视频99在线观看免费 | 欧洲一曲二曲三曲视频 | 91成版人在线观看入口 | 香蕉久久av一区二区三区 | 成人看片黄a免费看那个网址 | 羞羞影院午夜男女爽爽免费 | 国产成a人片在线观看视频 国产成a人片在线观看视频9 | 中文字幕亚洲综合久久2025 | 国产精品午夜理论片在线播放 | 日本不卡一区二区三区 | 亚洲午夜在线观看 | 国产高清无码在线播放 | 国产精品国产av片国产 | 亚洲成色www久久网站 | 九九九九九在线精品区 | 91精品国产福利线观看久久 | 国产在线精品一区二区不卡顿 | 神马午夜电影光棍影院在线观 | 精品国产福利片在线观看 | 日韩视频在线观看 | 韩国男男腐啪gv | 亚洲精品91 | 国产三级深夜精品视频 | 日本精品久久久久久久久免 | 天天日偷偷干天天操天天 | 精品黑人一区二区三区 | 亚洲av中出手机版在线观看 | 亚洲日韩国产一区二区蜜桃 | 国产欧美日韩va另类在线播放 | 在线成av人电影观看 | 国产女主播在线观看 | 91亚洲精品亚 | 天天av翘人人添亚洲综合网 | 欧美日韩在线精品一区二区三区 | 在线精品国产一区二区三区88 | 国产内地激情精品毛片在线一 | 91精品国产热久久福利 | 亚洲永久无码av一区二区三区 | 亚洲中文字幕第30页 | 亚洲午夜精品久久久久久app | 国产久爱免费精品视频 | 日韩成人黄页网 | 精品无码一区二区三区东京热 | 亚洲中文字幕久久精品蜜桃 | 色欲久久久天天天综合影院 | 中文国产欧美在线观看 | 天美传媒麻豆精东蜜桃 | 国内久久综合无码精品 | 另类日韩| 亚洲精品日韩专区 | 国产成年+在线观看 | 国产中文字幕在线观看 | 国产精品伦理一区二区三区 | 亚洲精品乱码久久久久久久久久久久 | 少妇高潮喷水惨叫久无码一区二区 | 一区二区三区av | 丰满大码的熟女在线视频剧集正版 | 国产午夜精品久久精品电影 | 高潮毛片无遮挡高清免费视频 | 国产麻豆91网在线看 | 亚洲无码高清在线观看一区二区三区 | 亚洲国产精久久久久久久 | 亚洲中文字幕成人在线 | 日韩av少妇无码专区 | 中文字幕精品一区二 | 亚洲精品成人久久av | 午夜国产毛片v区一区二区三区 | 国产精品免费av片在线观看 | 日韩人妻无码一区二区三区久久 | 91成人精品国语自产拍 | 国产午夜精品一区二区三区不 | 日韩无码一区二区 | 国产精品自产拍在线观看55 | 国产精品亚洲电影久久成人影院 | 国产激情一区二 | 午夜成人精品无码色欲 | 国产亚洲综合91精品 | 在线天堂新版资源www在线 | 极品人妻的娇喘呻吟 | 亚洲中文字幕久久无码 | 国产女人aaa级久久久级 | 在线观看黄色小 | 精品国产三级毛片 | 日本乱码伦午夜福利在线 | 国产a∨国片精品白丝jk制服 | 精品999久久久久久中文字幕 | 久久97人人超人人超碰超国产 | 成人欧美日韩视频一区 | 亚洲日韩精品一区二区三区 | 亚洲精品动漫免费二区 | 亚洲中文字幕无码久久精品1 | 欧美精品黑人粗大 | 无码人妻精品一区二区蜜桃91 | 久久99精品亚洲乱码三区 | av无码理论片在线观看免费网站 | 在线a精品一区二区 | 亚洲av无码潮喷在 | 91香蕉成人免费网站 | 亚洲永久无码av一区二区三区 | 在线免费观看成年人视频 | 91精品自在线在线视频 | 3d动漫精品啪啪一 | 国产热re99久久6国产精品首 | 国产一区二区在线影院 | 麻豆视频传媒入口 | 亚洲av无码av吞精久 | 国产亚洲第一午夜福利合集 | 久久88台湾三级香港三级 | 国产suv精品一区二区6 | 亚洲国产精品超碰 | 国产欧美综合在线一区二区三区 | 国产sm精品调教在线 | 国产aⅴ一区二区三区精品 国产aaaa | 99久久婷婷国产综合精品青草免费 | 97超碰中文字幕久久精品 | 国产精品天天看 | 成a在线 | a级毛片无码免费真人久久 a级毛片在线观看 | 日韩国产在线 | 99久久精品国产91久久久 | 亚洲五码中文字幕 | 午夜福利视频免费看床戏激情 | 国产精鲁鲁视频在线观看 | 亚洲国产激情一区二区三区 | 无码成人区久久 | 国产精品毛片一区视频播 | 日韩av在线播放 | 91精品久久久久久久免费看 | 国产极品尤物在线网址 | 国产亚洲精品观看91在线 | 人人超碰91尤物精品国产 | 国产老熟女乱伦一区二区三区 | 久久99国产精品二区 | 九色国产在视频线精品视频 | 国产人成视频免费看 | 高潮喷水 | 福利视频网站 | 国产精品一区二区三区不卡视频 | 在线精品免费视频无码的 | www污污污网站在线观看美女被操 | 九一伦理| 中文无码区a∨视频 | 午夜在线观看视频免费成人 | 国产日产久久高清欧美一区 | 国产成a人v | 波多野结衣中文乱码免费 | 国产色噜噜噜在线精品 | 91大神在线| 国产一区二区三区久久精品 | 亚洲乱码无人区卡1卡2卡3 | 亚洲欧美视频在线 | 超碰97人人做人人爱网站 | 亚洲暴爽av天天爽日日碰 | 中文字幕一区二区三区在线不卡 | 97视频在线精 | 多人乱p杂交公车高清免费观看 | 婷婷久久亚洲综合国产 | 加勒比在线东京热在线中文字幕 |