在上一文中,我們用傳統物件導向的方法來定義了一個類,而我們同時把該類的執行個體綁定到兩個文字框,第一個文字框用於輸入值,第二個文字框用於根據第一個文字框中的輸入來取得屬性值。
在上例中我們已經明了,雖然能做到同步更新,但這同步更新並不是即時的。而是在控制項失去焦點或點擊按鈕之後才發生,因為那個時候是重新進行了綁定,所以,一般的屬性聲明並沒有實現即時更新。
下面,我們把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,這個索引就是依賴項屬性在雜湊表中的位置,當我們要擷取屬性的值或要設屬性的值, 就從這個索引表裡尋找。 |