久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

WPF:依賴屬性

 紫殿 2020-12-02

WPF:依賴屬性

前言:
在使用WPF的時候,,總會有一個疑問,,依賴屬性跟普通的類屬性有什么區(qū)別,微軟要在WPF引入它想要解決什么問題,?如果不解除這個疑惑,,在編程的時候心里總會不踏實(shí)。因此我在網(wǎng)上找了一些資料,,終于弄懂了它的由來和機(jī)制,,特意和大家分享,如有不足,,請各位指正,!
依賴屬性的由來:
在WinForm時代,每個控件類(如TextBox)都會包含許多屬性,,但是真正用到的少之又少(如text),,其他屬性就會白白耗費(fèi)內(nèi)存資源。那么問題來了,,如果只生成一個控件對象,,“無用”的屬性對性能影響不大,但是當(dāng)你在一個窗體實(shí)例化幾十個控件對象的時候,,那內(nèi)存消耗就很可觀了,。所以,我們應(yīng)該引入一個機(jī)制,,在實(shí)例化對象的時候,,按需“給”它屬性,這樣子就不會讓“無用”的屬性白白占用內(nèi)存資源,,因此依賴屬性誕生了,。
依賴屬性的定義:
先比較淺顯地講講依賴屬性的定義,,即本身可以沒有值,而依賴于其他數(shù)據(jù)源而取得值的屬性,。
自定義依賴屬性:
在深入了解依賴屬性之前,,很有必要弄清楚依賴屬性是如何敲出來的,。假設(shè)有一個Person類,,現(xiàn)在我要定義一個依賴屬性NameProperty,代碼如下:

    //依賴屬性必須在依賴對象DependencyObject或其子類中定義    class Person : DependencyObject    {        //CLR屬性包裝器,,使得依賴屬性NameProperty在外部能夠像普通屬性那樣使用        public string Name        {            get { return (string)GetValue(NameProperty); }            set { SetValue(NameProperty, value); }        }         //依賴屬性必須為static readonly,,后續(xù)講解        public static readonly DependencyProperty NameProperty =            DependencyProperty.Register("Name", typeof(string), typeof(Person), new PropertyMetadata("DefaultName"));    }代碼分析:

  1. 依賴屬性必須在依賴對象(DependencyObject)或其子類中定義,因此你能夠發(fā)現(xiàn)絕大多數(shù)WPF控件都是繼承自DependencyObject的,;
    2.CLR屬性Name的作用之一是為了讓外界在使用依賴屬性時,,像使用普通屬性一樣;
    3.依賴屬性約定以Property結(jié)尾,;
    4.依賴屬性必須聲明為static readonly,,原因后續(xù)講解;
    5.生成依賴屬性需要Register方法而不是new,,方法的參數(shù)依次是CLR屬性名,、依賴屬性對應(yīng)的類型(注意:所有依賴屬性真正的類型是DependencyProperty,這里的意思是依賴屬性所“存儲”的值的類型),、依賴屬性的宿主類,、依賴屬性的默認(rèn)元數(shù)據(jù);
    6.默認(rèn)元數(shù)據(jù)有許多重載,,這里使用的是只有一個參數(shù)的重載類型,,傳入的參數(shù)是依賴屬性的默認(rèn)值。
    Register方法的內(nèi)部機(jī)制:
    很明顯,,自定義依賴屬性的關(guān)鍵在于Register方法,,它的內(nèi)部機(jī)制是:
    1.創(chuàng)建DependencyProperty實(shí)例dp;
    2.根據(jù)CLR屬性名和宿主類型名生成哈希碼hashcode,,用于唯一標(biāo)示依賴屬性,;
    3.在DependencyObject類中有一個全局變量:哈希表PropertyFromName,用于存儲所有依賴屬性的hashcode,,因此在這一步檢查是否已經(jīng)存在相同hashcode,,若無則以hashcode和實(shí)例dp作為鍵值對存進(jìn)哈希表PropertyFromName,否則報錯,;
    4.最后返回實(shí)例dp,。
    注意,hashcode并不是實(shí)例dp的哈希值,,而是一個名為GlobalIndex的int全局變量,,由DependencyObject內(nèi)復(fù)雜的算法算出來,。
    GetValue和SetValue方法的內(nèi)部機(jī)制:
    也許這個時候你會有疑問:既然依賴屬性聲明為static,,就代表依賴屬性只有一份拷貝,那么我定義多個Person類的實(shí)例對象時,,一旦改變其中一個對象的依賴屬性時,,其他的對象豈不是都跟著改變,?這時候就需要探討GetValue和SetValue的內(nèi)部機(jī)制了,這里只說GetValue,,因?yàn)榕薌etValue后,對SetValue的機(jī)制就自然明白了,。
    在GetValue函數(shù)中能夠看到一個EffectiveValueEntry類的實(shí)例,,它就好像是一個房間入口(Entry),,進(jìn)去后就能獲取想要的值,;在每個EffectiveValueEntry的構(gòu)造函數(shù)中有一個名為PropertyIndex的參數(shù),傳入的值其實(shí)就是上面提到的唯一標(biāo)示不同實(shí)例的GlobalIndex,,這樣子,,每一個實(shí)例對象的依賴屬性值所對應(yīng)的入口(Entry)就不一樣了,;而在DependencyObject類中能夠看到 private
     EffectiveValueEntry[] _effectiveValues 這樣一個語句,,當(dāng)某個實(shí)例的依賴屬性被讀取時,,就會到這個數(shù)組檢索它對應(yīng)的EffectiveValueEntry,如果找不到則代表依賴屬性還沒有被手動賦值(如Name="LiMing"),,DependencyObject類的內(nèi)部算法就會返回依賴屬性的默認(rèn)值(在Register方法第四個參數(shù)中設(shè)定),。
    這樣就明白了,被static關(guān)鍵字所修飾的依賴屬性對象真正作用只是用來檢索真正的屬性值,,而不是用來存儲值的,;而用于檢索值的是GlobalIndex,,因此為了保證GlobalIndex的穩(wěn)定性,,需要添加readonly關(guān)鍵字,。
    所以,依賴屬性是以犧牲算法來節(jié)省內(nèi)存空間,。
    結(jié)語:
    其實(shí)依賴屬性的內(nèi)部機(jī)制比上述復(fù)雜的多,,如果想真正理解其中的原理,還是需要各位自己去鉆研,。Thanks for your attention!

WPF: 只讀依賴屬性的介紹與實(shí)踐

在設(shè)計(jì)與開發(fā) WPF 自定義控件時,,我們常常為會控件添加一些依賴屬性以便于綁定或動畫等。事實(shí)上,,除了能夠添加正常的依賴屬性外,,我們還可以為控件添加只讀依賴屬性(以下統(tǒng)稱“只讀屬性”),以增加控件的靈活性,。

這聽起來有些矛盾,。只讀依賴屬性,只能讀不能寫,,卻又怎么能提高控件的靈活性呢,?想想我們常用的 IsMouseOver 等屬性就可以理解,,它們都是只讀屬性,但如果沒有它們,,想要控制樣式將比較復(fù)雜,。

所以,總結(jié)來說,,只讀屬性的特點(diǎn)是:無法賦值,,不能綁定,不能用于動畫,,不能驗(yàn)證等,;而之所以使用它,主要目的是結(jié)合屬性觸發(fā)器 (Trigger) 來實(shí)現(xiàn)樣式的切換,。

回到頂部

實(shí)踐

比如,,我們要創(chuàng)建一個 FilePicker 控件,用戶通過它可以選擇文件,。那么,,它至少包括一個 TextBlock(或 TextBox)和一個 Button,分別用于顯示選擇文件的路徑和打開對話框,。

現(xiàn)在我們想實(shí)現(xiàn)當(dāng)用戶選擇了文件后,,控件呈現(xiàn)某種樣式。要這么做,,我們就可以增加一個 IsFilePicked 只讀屬性,,然后在 ControlTemplate 中添加 Trigger 來控制樣式的變化。

1. 創(chuàng)建(定義與注冊)

創(chuàng)建只讀屬性與創(chuàng)建普通依賴屬性一樣,,包括定義,、注冊、與 CLR 屬性包裝這三步,。不同的是要使用 DependencyProperty 的 RegisterReadOnly 方法來注冊,這個方法會返回 DependencyPropertyKey 對象,,它包含了對應(yīng)只讀屬性的標(biāo)識符,,也就是與它關(guān)聯(lián)的只讀屬性(通過 DependencyProperty 屬性獲得),并且對只讀屬性賦值也是通過它(注意:只讀屬性自身無法被賦值),,代碼如下:

// 只讀屬性的定義與注冊private static DependencyPropertyKey IsFilePickedPropertyKey = DependencyProperty.RegisterReadOnly("IsFilePicked", typeof(bool), typeof(FilePicker), new PropertyMetadata(false));public static DependencyProperty IsFilePickedProperty = IsFilePickedPropertyKey.DependencyProperty;

注意其中的命名,,因?yàn)槲覀円獎?chuàng)建的屬性是 IsFilePicked,所以上面兩個變量都是在這個名稱后加了后輟,,分別是 PropertyKey 和 Property,,這是命名規(guī)范。

另外,,我們在元數(shù)據(jù)的實(shí)例中給這個只讀屬性設(shè)置默認(rèn)值為 false,。

2. 包裝

然后,將它以 CLR 屬性的方式來包裝,由于這是個只讀屬性,,所以只需要 get 段就可以,,代碼如下:

// 只讀屬性的包裝public bool IsFilePicked{get { return (bool)GetValue(IsFilePickedProperty); }}

3. 通過 DependencyPropertyKey 賦值

在合適的位置(當(dāng)用戶選擇過文件后),使用 SetValue 方法來賦值,,SetValue 有兩個重載,,要為只讀屬性賦值,需使用第二個

(DependencyPropertyKey key, object value)

代碼如下:

  SetValue(IsFilePickedPropertyKey, true);

4. 應(yīng)用

邏輯寫好后,,在模板中增加以下 XAML 代碼,,即可:

<ControlTemplate.Triggers><Trigger Property="IsFilePicked" Value="True"><!--顯示綠色邊框--><Setter Property="BorderBrush" Value="Green" /><Setter Property="BorderThickness" Value="2" /></Trigger></ControlTemplate.Triggers>

總結(jié)

本文簡單介紹了在 WPF 中如何創(chuàng)建以及使用只讀依賴屬性,合適地使用它,,能夠使我們更靈活地實(shí)現(xiàn)對自定義控件樣式的控制,。


WPF --依賴屬性詳解

依賴項(xiàng)屬性可以稱得上是WPF中比較難理解的概念,為了搞清楚這個概念,,我都把.NET類庫進(jìn)行了反編譯,,但是,其結(jié)果我也是想到了的,,微軟不是傻子,,.NET那么龐大,就算能被你反編譯了,,你也看不懂它的代碼,。

所以說,經(jīng)過我一番研究,,雖然沒有把.NET的每一行代碼都弄明白,,不過,黃天終不負(fù)有心人,,依賴項(xiàng)屬性的使用方法與基本原理,,我可以說已經(jīng)弄明白了,恰巧,,前兩天在網(wǎng)上看到一篇討論依賴項(xiàng)屬性的文章,,寫得還不錯,作者估計(jì)也是一位高人,,再加上我個人的研究,,從實(shí)際應(yīng)用的角度來說,我現(xiàn)在已經(jīng)掌握了依賴項(xiàng)屬性的使用方法了,,不妨告訴你,,其實(shí)很簡單,可以這么說,,整個WPF都很簡單,,和許多剛接觸WPF的朋友一樣,,一開始我也是認(rèn)為它很復(fù)雜很難懂。

為什么這樣說呢,?大家都知道,,微軟官方總是為它自己推出的產(chǎn)品配備很完備的文檔,對,,就是那個很出名的MSDN,。

許多初學(xué)WPF的朋友,一定也會像我一樣,,去查閱MSDN,,通過上面的介紹來入門,可杯具正是發(fā)生在這個時候,,WPF的難懂難學(xué)就是被微軟自己的文檔所誤導(dǎo),,先別說翻譯的質(zhì)量不好,就算你看英文原文,,你大概也會看得頭暈,。

真的,那些概念模型實(shí)在太抽象了,,從剛接觸WPF到現(xiàn)在,,我都不知道把MSDN翻了多少遍了,甚至查到微軟都把我的IP列入黑名單了,,呵呵,,而且,  我也下載了英文原版的SDK來對比研究,。

說實(shí)話,,對.NET類庫進(jìn)行反編譯的學(xué)習(xí)方法效率很低,表面上說可以更深入地了解.NET框架,,但是,,我不推薦這樣學(xué)習(xí),真的,,得不嘗失,,花費(fèi)很多精力和時間,而收獲甚少,;還有就是,反編譯.NET類庫是屬于侵權(quán),,哈,,幸好我們都生活在沒有法律的中國,不然,,一定會被微軟告上法庭,。

這次反編譯,,完全出于無奈,因?yàn)橛行└拍畹拇_難以理解,。

在研究的同時,,我也進(jìn)行了反思,最后感悟是——還是那句老話:理論的東西,,哪怕你把它背下來了,,你永遠(yuǎn)也不懂。

對我們來說,,學(xué)編程為了什么,?不就是為了應(yīng)用嗎?也就是說用于實(shí)戰(zhàn),,既然這樣,,其實(shí)我們不必把理論的東西鉆得太死,不然,,鉆牛角尖容易走火入魔,。

依賴項(xiàng)屬性的重點(diǎn)在于“依賴”二字,既然是依賴了,,也就是說:依賴項(xiàng)屬性的值的改變過程一定與其它對相關(guān),,不A依賴B就B依賴A,或者相互依賴,。

說白了,,所謂依賴,主要應(yīng)用在以下地方:

1,、雙向綁定。有了這個,,依賴項(xiàng)屬性不用寫額的代碼,,也不用實(shí)現(xiàn)什么接口,它本身就俱備雙向綁定的特性,,比如,,我把員工對象的姓名綁定到搖文本框,,一旦綁定,,只要文本框中的值發(fā)生改變,,依賴項(xiàng)屬性員工姓名也會跟著變化,,反之亦然;

2,、觸發(fā)器,。這個東西在WPF中很重要,比如,,一個按鈕背景是紅色,,我想讓它在鼠標(biāo)停留在它上面是背景變成綠色,而鼠標(biāo)一旦移開,,按鈕恢復(fù)紅色。

如果在傳統(tǒng)的Windows編程中,,你一定會想辦法弄一些事件,,或者委托來處理,還要寫一堆代碼,。告訴你,,有了依賴項(xiàng)屬性,你將一行代碼都不用寫,,所有的處理均由WPF屬性系統(tǒng)自動處理,。而觸發(fā)器只是臨時改變屬性的值,當(dāng)觸完成時,,屬性值自動被“還原”,。

3、附加屬性,。附加屬性也是依賴項(xiàng)屬性,,它可以把A類型的的某些屬性推遲到運(yùn)行時根據(jù)B類型的具體情況來進(jìn)行設(shè)置,而且可以同時被多個類型對象同時維護(hù)同一個屬性值,,但每個實(shí)例的屬性值是獨(dú)立的,。

4、A屬性改變時,,也同時改變其它屬性的值,,如TogleButton按下的同時,彈出下拉框,。

為了進(jìn)行比較,,我們先來說說傳統(tǒng)面向?qū)ο缶幊讨袑︻悓傩缘亩x,請看下面一個簡單的類,,它只有一個公共屬性,。

public class Student{public string Name { set; get; }}

這時候,我們布局一下WPF主窗口,如下所示XAML:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="180" Width="300" Loaded="Window_Loaded">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Column="0" Grid.Row="0" Text="姓名:"/>
        <TextBox x:Name="txtName" Grid.Column="1" Grid.Row="0" Margin="0,5,20,5"/>
        <TextBlock Grid.Column="0" Grid.Row="1" Text="改變值:"/>
        <TextBox x:Name="txtCh" Grid.Column="1" Grid.Row="1" Margin="0,5,20,5"/>
        <Button x:Name="btn" Grid.Row="2" Grid.ColumnSpan="2" Margin="70,5,70,5" Click="btn_Click">顯示屬性值</Button>
    </Grid>
</Window>

在窗口的加載完成事件中,,我們作兩個綁定:

(1)把Student的實(shí)例的Name屬性與textBox的text屬性綁定;

(2)同時與第二個文本框也綁定,。

運(yùn)行程序,,在第一個文本框中輸入內(nèi)容,再點(diǎn)一下第二個文本框,,或點(diǎn)一下按鈕,,雖然第二個文本框也會隨之改變,但并不是同步改變,,而是當(dāng)焦點(diǎn)離開第一個文本框后才發(fā)生改變,,這就不屬于同步了。

完整代碼如下:

Student myStu = new Student { Name = "小明" };private void Window_Loaded(object sender, RoutedEventArgs e){//綁定數(shù)據(jù)BindingOperations.SetBinding(txtName, TextBox.TextProperty,new Binding { Source = myStu, Path = new PropertyPath("Name"), Mode = BindingMode.TwoWay });BindingOperations.SetBinding(txtCh, TextBox.TextProperty,new Binding { Source = myStu, Path = new PropertyPath("Name"), Mode = BindingMode.TwoWay });}private void btn_Click(object sender, RoutedEventArgs e){MessageBox.Show("當(dāng)前實(shí)例的屬性值:Name = " + myStu.Name);}

在上一文中,,我們用傳統(tǒng)面向?qū)ο蟮姆椒▉矶x了一個類,,而我們同時把該類的實(shí)例綁定到兩個文本框,第一個文本框用于輸入值,,第二個文本框用于根據(jù)第一個文本框中的輸入來取得屬性值,。

在上例中我們已經(jīng)明了,雖然能做到同步更新,,但這同步更新并不是實(shí)時的,。而是在控件失去焦點(diǎn)或點(diǎn)擊按鈕之后才發(fā)生,因?yàn)槟莻€時候是重新進(jìn)行了綁定,,所以,,一般的屬性聲明并沒有實(shí)現(xiàn)實(shí)時更新。

下面,,我們把Student類進(jìn)行改動,,把Name屬性改為依賴項(xiàng)屬性。

public class Student:DependencyObject{//注冊依賴項(xiàng)屬性public static readonly DependencyProperty NameProperty =DependencyProperty.Register("Name",typeof(string),typeof(Student),new PropertyMetadata(string.Empty));}

從定義中我們看到依賴項(xiàng)屬性的定義規(guī)則:

1,、必須是公開的靜態(tài)字段,,public static;

2,、因?yàn)槭庆o態(tài)成員而且是公開的,,有可能被惡意或無意修改,為了保險,,加上一個readonly關(guān)鍵字,;

3、調(diào)用靜態(tài)方法Register返回一個DependencyProperty實(shí)例,。

從上述內(nèi)容中,,可以進(jìn)一步分析,依賴項(xiàng)屬性是通過注冊到WPF屬性系統(tǒng)來定義的,, Register方法有多種重載,,示例中用到的是以下簽名:

  public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata);
 

       //
        // 摘要:
        //     使用指定的屬性名稱,、屬性類型、所有者類型和屬性元數(shù)據(jù)注冊依賴項(xiàng)屬性,。
        //
        // 參數(shù):
        //   name:
        //     要注冊的依賴項(xiàng)對象的名稱,。
        //
        //   propertyType:
        //     屬性的類型。
        //
        //   ownerType:
        //     正注冊依賴項(xiàng)對象的所有者類型,。
        //
        //   typeMetadata:
        //     依賴項(xiàng)對象的屬性元數(shù)據(jù),。
        //
        // 返回結(jié)果:
        //     一個依賴項(xiàng)對象標(biāo)識符,應(yīng)使用它在您的類中設(shè)置 public static readonly 字段的值,。然后,,在以后使用該標(biāo)識符引用依賴項(xiàng)對象,用于某些操作,,例如以編程方式設(shè)置其值,,或者獲取元數(shù)據(jù)。

這里說一下ownerType參數(shù),,它只的是注冊依賴項(xiàng)屬性的類型,,如本例中就是Student類。

typeMetadata是所謂的元數(shù)據(jù),,就是屬性的默認(rèn),,當(dāng)然,它也有N個構(gòu)造函數(shù),,可以同時傳遞事件委托來對屬性的改性或類型轉(zhuǎn)換時進(jìn)行事件處理,。

下面一點(diǎn)很重要,就是屬性的命名格式,,根據(jù)約定,,必須為以下格式:

XXXProperty,你的屬性名后面緊跟Property,,記?。?/p>

我們把前面的示例改一下,,作以下綁定

//聲明一個Student類Student myStu = new Student();private void Window_Loaded(object sender, RoutedEventArgs e){myStu.SetValue(Student.NameProperty, "小張");//綁定到第一個文本框BindingOperations.SetBinding(myStu, Student.NameProperty,new Binding{Source = txtName,Path = new PropertyPath("Text")});//綁定第二個文本框BindingOperations.SetBinding(txtChanged, TextBox.TextProperty,new Binding{Source = myStu,Path = new PropertyPath(Student.NameProperty)});}

這時候你運(yùn)行程序,,當(dāng)你在第一個文本框中輸入內(nèi)容時,第二個的文本框就會立即發(fā)生改變,,這就說明,,在雙向綁定的模式下,屬性的更新是實(shí)時的,。

好,,為了能像普通CLR屬性一樣使用,我們對Student類做一些封裝。在封裝之前,,說一下是如何獲取和設(shè)置屬性的,。

在WPF中,要使用依賴項(xiàng)屬性,,該類必須繼承DependencyObject類,,DependencyObject類有兩個屬性專門處理屬性的值。

(1)GetValue用于獲取值,;

(2)SetValue用于設(shè)置值。

于是,,我們對Student類作以下封裝:

public class Student:DependencyObject{//注冊依賴項(xiàng)屬性public static readonly DependencyProperty NameProperty =DependencyProperty.Register("Name",typeof(string),typeof(Student),new PropertyMetadata(string.Empty));public string Name{get{return (string)this.GetValue(NameProperty);}set{this.SetValue(NameProperty, value);}}}

雖然現(xiàn)在已經(jīng)知道如何使用依賴項(xiàng)屬性了,,但是,依賴項(xiàng)屬性是如何注冊到WPF的屬性系統(tǒng)中的,,我們似乎一頭霧水,。

要把這一問題搞清楚,只有一種辦法,,那就是把.NET類庫反編譯,,工具不用我說了,網(wǎng)上大把,,呵,,這可是侵權(quán)的,靠,,沒辦法,,誰叫微軟不開源。

通過反編譯得到Register方法的定義如下,,其實(shí)每個重載都是調(diào)用了下面這個方法:

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback){    RegisterParameterValidation(name, propertyType, ownerType);    PropertyMetadata defaultMetadata = null;    if ((typeMetadata != null) && typeMetadata.DefaultValueWasSet())    {        defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue);    }    DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);    if (typeMetadata != null)    {        property.OverrideMetadata(ownerType, typeMetadata);    }    return property;}

其它的不用看,,我們只找重點(diǎn)語句,上面代碼片段中,,加粗斜體部分為重點(diǎn),,也就是說,所以的依賴項(xiàng)屬性的注冊,,都是調(diào)用了RegisterCommon方法的,。

接著,我們來看看RegisterCommon方法是如何定義的,。

這段代碼有點(diǎn)長,,我就不全部復(fù)制過來了,我把重要的語句拿過來,。

private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback){    FromNameKey key = new FromNameKey(name, ownerType);    lock (Synchronized)    {        if (PropertyFromName.Contains(key))        {            throw new ArgumentException(SR.Get("PropertyAlreadyRegistered", new object[] { name, ownerType.Name }));        }    }
       //省略代碼.................
    return dp;}

大家注意加粗斜體字標(biāo)注的地方,,其中有一個if語句判斷,是否在PropertyFromName中存在某個key,如果存在就拋出異常,,那么這個PropertyFromName究竟是什么呢? 

好,,帶著疑問,繼續(xù)跟蹤代碼,,在DependencyProperty類中發(fā)現(xiàn)了它的定義:

private static Hashtable PropertyFromName,;

這下就明白了,原來剛來所檢查的key的容器就是一個哈希表,,而且是全局的,。

上面的代碼告訴我們,哈希表的key就是一個FromNameKey ,,

    FromNameKey key = new FromNameKey(name, ownerType);

那么,,這個FromNameKey又是如何計(jì)算的呢?繼續(xù)反編譯,,我找到了它的定義:

private class FromNameKey{// Fields    private int _hashCode;private string _name;private Type _ownerType;// Methodspublic FromNameKey(string name, Type ownerType){this._name = name;this._ownerType = ownerType;this._hashCode =this._name.GetHashCode() ^this._ownerType.GetHashCode();}    //省略代碼...............}希哈值的計(jì)算方法是:(類名 + 屬性名).HashCode();這樣一來,,就可以保證依賴項(xiàng)屬性只能注冊一次,因?yàn)楣V凳侨治ㄒ坏?。而且,,我們知道DependencyProperty類有個GlobalIndex,這個索引就是依賴項(xiàng)屬性在哈希表中的位置,,當(dāng)我們要獲取屬性的值或要設(shè)屬性的值,,就從這個索引表里尋找。

上節(jié)中,,我們分析了依賴項(xiàng)屬性的注冊和定義方法,,并解釋了依賴項(xiàng)屬性的注冊過程,但是,,有一個疑問會困惑著我們,,既然依賴項(xiàng)屬被聲明為靜態(tài)只讀字段,那為什么它的值可以被改變呢,?難道你不覺得很奇怪嗎,?

微軟的葫蘆里到底賣的什么藥呢?我們來看看,。

前文中我們提到過,,設(shè)置依賴項(xiàng)屬性的值使用SetValue方法,那好,,我們就從SetValue方法入手,。

SetValue方法的定義如下:

public void SetValue(DependencyProperty dp, object value){base.VerifyAccess();PropertyMetadata metadata = this.SetupPropertyChange(dp);this.SetValueCommon(dp, value, metadata, false, OperationType.Unknown, false);}

VerifyAccess方法是用于檢查可訪問性,這個不管它,,重點(diǎn)是看SetValueCommon方法,,它才是真正設(shè)置值的方法,,好,繼續(xù)跟入,,看看SetValueCommon方法的定義:

private void SetValueCommon(DependencyProperty dp, object value, PropertyMetadata metadata, bool coerceWithDeferredReference, OperationType operationType, bool isInternal){if (this.IsSealed){throw new InvalidOperationException(SR.Get("SetOnReadOnlyObjectNotAllowed", new object[] { this }));}Expression expr = null;DependencySource[] newSources = null;EntryIndex entryIndex = this.LookupEntry(dp.GlobalIndex);if (value == DependencyProperty.UnsetValue){this.ClearValueCommon(entryIndex, dp, metadata);}else{EffectiveValueEntry entry;EffectiveValueEntry entry2;bool flag = false;bool flag2 = value == ExpressionInAlternativeStore;if (!flag2){bool flag3 = isInternal ? dp.IsValidValueInternal(value) : dp.IsValidValue(value);if (!flag3 || dp.IsObjectType){expr = value as Expression;if (expr != null){if (!expr.Attachable){throw new ArgumentException(SR.Get("SharingNonSharableExpression"));}newSources = expr.GetSources();ValidateSources(this, newSources, expr);}else{flag = value is DeferredReference;if (!flag && !flag3){throw new ArgumentException(SR.Get("InvalidPropertyValue", new object[] { value, dp.Name }));}}}}if (operationType == OperationType.ChangeMutableDefaultValue){entry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Default) {Value = value};}else{entry = this.GetValueEntry(entryIndex, dp, metadata, RequestFlags.RawEntry);}object localValue = entry.LocalValue;Expression expression2 = null;Expression expression3 = null;if (entry.HasExpressionMarker){if (expr == null){expression3 = _getExpressionCore(this, dp, metadata);}if (expression3 != null){localValue = expression3;expression2 = expression3;}else{localValue = DependencyProperty.UnsetValue;}}else{expression2 = entry.IsExpression ? (localValue as Expression) : null;}bool flag5 = false;if ((expression2 != null) && (expr == null)){if (flag){value = ((DeferredReference) value).GetValue(BaseValueSourceInternal.Local);flag = false;}flag5 = expression2.SetValue(this, dp, value);entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);}if (flag5){if (entryIndex.Found){entry2 = this._effectiveValues[entryIndex.Index];}else{entry2 = EffectiveValueEntry.CreateDefaultValueEntry(dp, metadata.GetDefaultValue(this, dp));}}else{entry2 = new EffectiveValueEntry(dp, BaseValueSourceInternal.Local);if ((expression2 != null) && (expression2 != expression3)){DependencySource[] sources = expression2.GetSources();UpdateSourceDependentLists(this, dp, sources, expression2, false);expression2.OnDetach(this, dp);entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);}if (expr == null){entry2.IsDeferredReference = flag;entry2.Value = value;entry2.HasExpressionMarker = flag2;}else{this.SetEffectiveValue(entryIndex, dp, dp.GlobalIndex, metadata, expr, BaseValueSourceInternal.Local);object defaultValue = metadata.GetDefaultValue(this, dp);entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);this.SetExpressionValue(entryIndex, defaultValue, expr);UpdateSourceDependentLists(this, dp, newSources, expr, true);expr.MarkAttached();expr.OnAttach(this, dp);entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);entry2 = this.EvaluateExpression(entryIndex, dp, expr, metadata, entry, this._effectiveValues[entryIndex.Index]);entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);}}this.UpdateEffectiveValue(entryIndex, dp, metadata, entry, ref entry2, coerceWithDeferredReference, operationType);}}

第一個引起我注意的是DependencySource類,,它封裝了依賴性對象和依賴性屬性,并分別為兩個只讀屬性,。

還有一個比較核心的類就是EffectiveValueEntry.

internal EffectiveValueEntry(DependencyProperty dp, BaseValueSourceInternal valueSource){this._propertyIndex = (short) dp.GlobalIndex;this._value = DependencyProperty.UnsetValue;this._source = (FullValueSource) valueSource;}

FullValueSource是一個枚舉,,如果我沒猜錯的話,它就是用來標(biāo)識屬性的來源,,它有以下幾個值:

[FriendAccessAllowed]internal enum FullValueSource : short{HasExpressionMarker = 0x100,IsAnimated = 0x20,IsCoerced = 0x40,IsDeferredReference = 0x80,IsExpression = 0x10,ModifiersMask = 0x70,ValueSourceMask = 15}

1,、通過XAML標(biāo)記語言來改變屬性值,如:

<TextBox x:Name="txt" Text="這是一個測試" />

這里就通過XAML標(biāo)記為 Text屬性賦了值,。

2,、受動畫影響而改變的值。比如我畫一個矩形,,在動畫面板中我讓它演示長達(dá)6秒鐘的動畫,這期間,,矩形的X坐標(biāo)從20變?yōu)?0,,這個值就是通過動畫來設(shè)置屬性值,這個值是不會提交到屬性更改,,只是臨時更改,,動畫停止后,屬性值將會恢復(fù)為原來的值,。

3,、強(qiáng)制設(shè)置值。這個概念很奇怪,,可以把它理解為對屬性的輸入值進(jìn)行類型轉(zhuǎn)換,,我們知道,WPF里面提供了許多XXXXConvertor,,畢竟我們在XAML標(biāo)記中只能輸入文本值,,比如顏色,我們都會輸入Red這樣的值,,其實(shí)在后臺,,運(yùn)行庫把字符串的值轉(zhuǎn)換為畫刷實(shí)例,再向?qū)傩再x值,。

4,、被改變后的值,從跟蹤的結(jié)果看,,依賴項(xiàng)屬性的值都有N個版本,,分別為不同的變量來保存,,以便做驗(yàn)證和對比。

EffectiveValueEntry里面就是保存依賴項(xiàng)屬性的全局索引和屬性值,,繼續(xù)反編譯,,發(fā)現(xiàn)了一個InsertEntry方法,比較長,,代碼我不帖,,只帖簽名。

private void InsertEntry(EffectiveValueEntry entry, uint entryIndex)

兩個參數(shù)都很好理解,,第二個參數(shù)就是數(shù)組的索引,,那么,是哪個數(shù)組的索引呢,?接著反編譯,,看到DendencyObject類有一個內(nèi)部字段,定義如下:

private EffectiveValueEntry[] _effectiveValues;

對,,這下找到了,,就是一個EffectiveValueEntry的數(shù)組,好了,,不用往下跟了,,到這里基本上可以看出依賴項(xiàng)屬性是如何保存它的值了,原理如下:

每個DendencyObject類的實(shí)例都會創(chuàng)建一個專門的數(shù)組,,數(shù)組中的每個元素分別標(biāo)識著該類型的一個依賴項(xiàng)屬性,,例如我定義了一個類A,A里面定義了3個依賴項(xiàng)屬性A.kk,,A.cc,,A.ff,這樣當(dāng)A類被分配到內(nèi)存中實(shí)例化的時候,,創(chuàng)建一個EffectvieValueEntry數(shù)組,,而每個EffectiveValueEntry對應(yīng)著一個屬性,保存著該屬性的不同版本的值,,A類型有3個依賴項(xiàng)屬性,,所以對應(yīng)的EffectiveEntry數(shù)組就有3個元素。

那么,,CLR如何知道哪個EffectiveEntry對應(yīng)著哪個依賴項(xiàng)屬性呢,?前面說過,每個依賴項(xiàng)屬性都會以哈希值的鍵保存到一個全局哈希表中,,所以,,只要查找出對應(yīng)鍵就可以唯一地標(biāo)識依賴項(xiàng)屬性了。

既然知道了如何設(shè)置值,,那么,,對于如何獲取值就更好辦了,,過程剛好相反。

public object GetValue(DependencyProperty dp){base.VerifyAccess();if (dp == null){throw new ArgumentNullException("dp");}return this.GetValueEntry(this.LookupEntry(dp.GlobalIndex), dp, null, RequestFlags.FullyResolved).Value;}

看到最后一行,,其實(shí)返回了一個EffectiveValueEntry類型的變量,,再從中取出Value屬性的值,這個值其實(shí)就是對應(yīng)類型的依賴項(xiàng)屬性的值,。

那么,,EffectiveValueEntry數(shù)組中的元素是在哪兒賦值的呢?我翻遍了整個DependencyObject類也沒有找到賦值的語句,,這時候我突然想起剛才的SetValue方法,,注意到這幾行:

if (operationType == OperationType.ChangeMutableDefaultValue){entry = new EffectiveValueEntry(dp, BaseValueSourceInternal.Default) {Value = value};}else{entry = this.GetValueEntry(entryIndex, dp, metadata, RequestFlags.RawEntry);}

如果要設(shè)置的值的等于默認(rèn),還記得我們調(diào)用Register注冊依賴項(xiàng)屬性時,,最后一個參數(shù),,它是一個PropertyMetaData類型,它封裝了依賴項(xiàng)屬性的默認(rèn)值,,也就是元數(shù)據(jù),,元數(shù)據(jù)說白了就初始值,“元”在漢語中有“首”,,“開始”,,“第一”等意思,如“XX元年”,、“公元2002年”等。

所以,,上面的代碼是:如果設(shè)置的值與元數(shù)據(jù)一致,,就不用賦值了,直接從元數(shù)據(jù)里面,,這樣可以免去了在賦值過程發(fā)生的“拆箱”和“裝箱”行為消耗性能,;

如果設(shè)置的值不是元數(shù)據(jù)(默認(rèn)值),那么就動態(tài)創(chuàng)建一個EffectvieValueEntry類的實(shí)例,,并保存當(dāng)前值,。

因此,EffectiveValueEntry數(shù)組是動態(tài)分配內(nèi)存的,,就是說,,如果你不對屬性進(jìn)行賦值,那就不創(chuàng)建EffectiveValueEntry實(shí)例,,因?yàn)橐蕾図?xiàng)屬性在靜態(tài)注冊時就帶了默認(rèn)值,。

因此,對比傳統(tǒng)的面向?qū)ο髮傩韵到y(tǒng),,WPF的依賴項(xiàng)屬性有以下優(yōu)點(diǎn):

1,、類被實(shí)例化時不對屬性進(jìn)行初始化,,大大節(jié)省了內(nèi)存空間。

在傳統(tǒng)的屬性系統(tǒng)中,,比如一個類B有1000個屬性,,每個屬性都是int類型,而整型每個實(shí)例占用4個字節(jié)的內(nèi)存,,初臺化1000個屬性,,就等于分配1000個整型的有效內(nèi)存,也就是說,,B類實(shí)例化后將占用 1000 * 4 = 4000字節(jié)的內(nèi)存,,如果我的類有100000個字符串的屬性,而這些屬性都是保存中文字符,,你可想象有多么恐怖,!

WPF的依賴項(xiàng)屬性系統(tǒng)對每個類初始化時并不初始化屬性,而是等到運(yùn)行時需要設(shè)置才進(jìn)行分配,,這樣一來,,就大大節(jié)省了內(nèi)存占用率。

2,、更高的靈活性,。由于依賴項(xiàng)屬性的聲明是靜態(tài)的,也就是說,,它存在于整個應(yīng)用程序生命命周期內(nèi),,它并不屬于特定類,如Content屬性,,它可以屬于Canvas類,,也可以屬于DockPanel類,只要在全局哈希表中鍵值不重復(fù)即可,。

還有就是附加屬性,,附加屬性其實(shí)也是依賴項(xiàng)屬性,比如一個會計(jì)的職責(zé)是處理帳目和財務(wù)相關(guān)的事情,,有時候質(zhì)量控制部門比較忙,,財務(wù)部門的職責(zé)就多了一個檢測的任務(wù),但這個職責(zé)并不是財務(wù)人員所必須的,,只是在需要時才存在,。

附加屬性就是這樣,可以把其它類的屬性附加到當(dāng)前類的屬性列表,,而這些屬性并不是必須的,,需要時就存在,不需要時就不存在,,如DockPanel類的Dock就是附加在其子元素中,,因?yàn)镈ockPanel可能放N個元素,,你無法用一個值來固定它們的位置,可能一些元素Top,,或者有些元素Left,,這樣一來,只能把Dock屬性附加在它們身上,,按具體需要來設(shè)置,。

3、繼承性,。XAML中元素的屬性值可以從其容器標(biāo)記或父節(jié)點(diǎn)繼承而來的屬值,。如路由事件的泡冒行為就是一個典型。DockPanel里面放置了4個按鈕,,我只需在DockPanel的XAML聲明中加入Button.Click="Button_Click",,這樣,子元節(jié)點(diǎn)在的所有按鈕都會捕捉該事件,,并調(diào)用同一個事件處理過程,,這在傳統(tǒng)的WinForm中是做不到的。

4,、本地值,。動畫和3D變換等會改變對象的屬性,但是,,這些改變是臨時,,動畫停止或刪除后,該改變就不存在了,。

5,、數(shù)據(jù)綁定。依賴項(xiàng)自身具備實(shí)時更新功能,,不需要我們手動去編寫代碼實(shí)現(xiàn);WPF的數(shù)據(jù)綁定不僅僅在數(shù)據(jù)源,,資源,、樣式、其它元素的屬性值都可以實(shí)時更新,。

6,、資源。包括動態(tài)資源和靜態(tài)資源的引用,。這個我不多說了,,請自行參考MSDN,上面都詳細(xì)介紹了,。

……

但是,,依賴項(xiàng)屬性并不是沒有缺點(diǎn)的,,它的缺點(diǎn)正是來自于它的優(yōu)點(diǎn)。

由于依賴項(xiàng)屬性是靜態(tài)只讀字段,,所以應(yīng)用程序啟動時就必須創(chuàng)建所有的依賴項(xiàng)屬性(注意,,是創(chuàng)建用DependencyProperty.Register方法注冊的DependencyProperty,不包括屬性值),,這就需要時間了,,這就是為什么WPF程序啟動速度慢的原因,它是用“時間”來換取“空間”,,據(jù)說,,微軟一直在改善這問題,到了.NET3.5 SP1的時候,,就有了明顯的改進(jìn),。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多