繼續聊WPF——依賴項屬性(2)

來源:互聯網
上載者:User

 

在上一文中,我們用傳統物件導向的方法來定義了一個類,而我們同時把該類的執行個體綁定到兩個文字框,第一個文字框用於輸入值,第二個文字框用於根據第一個文字框中的輸入來取得屬性值。

 

在上例中我們已經明了,雖然能做到同步更新,但這同步更新並不是即時的。而是在控制項失去焦點或點擊按鈕之後才發生,因為那個時候是重新進行了綁定,所以,一般的屬性聲明並沒有實現即時更新。

 

下面,我們把Student類進行改動,把Name屬性改為依賴項屬性。

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

 

從定義中我們看到依賴項屬性的定義規則:

1、必須是公開的靜態欄位,public static;

2、因為是靜態成員而且是公開的,有可能被惡意或無意修改,為了保險,加上一個readonly關鍵字;

3、調用靜態方法Register返回一個DependencyProperty執行個體。

 

從上述內容中,可以進一步分析,依賴項屬性是通過註冊到WPF屬性系統來定義的, Register方法有多種重載,樣本中用到的是以下籤名:

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

       //
        // 摘要:
        //     使用指定的屬性名稱、屬性類型、所有者類型和屬性中繼資料註冊依賴項屬性。
        //
        // 參數:
        //   name:
        //     要註冊的依賴項對象的名稱。
        //
        //   propertyType:
        //     屬性的類型。
        //
        //   ownerType:
        //     正註冊依賴項對象的所有者類型。
        //
        //   typeMetadata:
        //     依賴項對象的屬性中繼資料。
        //
        // 返回結果:
        //     一個依賴項物件識別碼,應使用它在您的類中設定 public static readonly 欄位的值。然後,在以後使用該標識符引用依賴項對象,用於某些操作,例如以編程方式設定其值,或者擷取中繼資料。

 

這裡說一下ownerType參數,它只的是註冊依賴項屬性的類型,如本例中就是Student類。

typeMetadata是所謂的中繼資料,就是屬性的預設,當然,它也有N個建構函式,可以同時傳遞事件委託來對屬性的改性或類型轉換時進行事件處理。

 

下面一點很重要,就是屬性的命名格式,根據約定,必須為以下格式:

XXXProperty,你的屬性名稱後面緊跟Property,記住!

 

我們把前面的樣本改一下,作以下綁定

 

        //聲明一個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)                });        }

這時候你運行程式,當你在第一個文字框中輸入內容時,第二個的文字框就會立即發生改變,這就說明,在雙向繫結的模式下,屬性的更新是即時的。

 

 

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

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

(1)GetValue用於擷取值;

(2)SetValue用於設定值。

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

 

    public class Student:DependencyObject    {        //註冊依賴項屬性        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);            }        }    }

雖然現在已經知道如何使用依賴項屬性了,但是,依賴項屬性是如何註冊到WPF的屬性系統中的,我們似乎一頭霧水。

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

 

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

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;}

其它的不用看,我們只找重點語句,上面程式碼片段中,加粗斜體部分為重點,也就是說,所以的依賴項屬性的註冊,都是調用了RegisterCommon方法的。

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

這段代碼有點長,我就不全部複製過來了,我把重要的語句拿過來。

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;}

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

好,帶著疑問,繼續跟蹤代碼,在DependencyProperty類中發現了它的定義:

private static Hashtable PropertyFromName;

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

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

    FromNameKey key =
new FromNameKey(name, ownerType);

那麼,這個FromNameKey又是如何計算的呢?繼續反編譯,我找到了它的定義:

private class FromNameKey{
    // Fields    private int _hashCode;
    private string _name; 
   private Type _ownerType;
    // Methods
    public FromNameKey(string name, Type ownerType)
    {
        this._name = name;
        this._ownerType = ownerType;
        this._hashCode =
        this._name.GetHashCode() ^
 this._ownerType.GetHashCode();
   }    //省略代碼...............}
希哈值的計算方法是:(類名 + 屬性名稱).HashCode();
這樣一來,就可以保證依賴項屬性只能註冊一次,因為雜湊值是全域唯一的。
而且,我們知道DependencyProperty類有個GlobalIndex,這個索引就是依賴項屬性在雜湊表中的位置,當我們要擷取屬性的值或要設屬性的值,
就從這個索引表裡尋找。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.