1. 程式人生 > 實用技巧 >WPF依賴屬性

WPF依賴屬性

目錄

什麼是屬性

概念:屬性是一種成員,它提供靈活的機制來讀取、寫入或計算私有欄位的值。 屬性可用作公共資料成員,但它們實際上是稱為訪問器的特殊方法。 這使得可以輕鬆訪問資料,還有助於提高方法的安全性和靈活性。

理解:屬性其實就是外界訪問私有欄位的入口,屬性本身不儲存任何資料,在對屬性賦值和讀取的時候其實就是操作的對應私有欄位,通過反編譯工具我們可以看出屬性本質其實就是一個方法,通過get和set方法來操作對應的欄位。

屬性與欄位的相同處:

  • 它是命名的類成員。
  • 它有型別。
  • 它可以被賦值和讀取。

屬性和欄位的不同處:

  • 它不為資料儲存分配記憶體!
  • 它執行程式碼

C#語言規定:對類有意義的欄位和方法使用static關鍵字修飾、稱為靜態成員,通過類名加操作符(即“.”操作符)可以訪問它們;對類的例項有意義的欄位和方法不加static關鍵字,被稱為非靜態成員或例項成員。從語義來看:靜態成員與非靜態成員有很好的對稱性,但從程式在記憶體中的結構來看,這種對稱就被打破了。靜態欄位在記憶體中只會有一個拷貝,非靜態欄位則是每個例項擁有一個拷貝,無論方法是否偽靜態的,在記憶體中只會有一份拷貝,區別只是你能通過類名來訪問存放指令的記憶體,還是通過例項名來訪問存放指令的記憶體。

什麼是依賴屬性

概念:Windows Presentation Foundation(WPF)提供了一組服務,可用於擴充套件型別

屬性的功能。這些服務通常統稱為WPF屬性系統。WPF屬性系統支援的屬性稱為依賴項屬性。

理解:依賴屬性就是一種可以自己沒有值,並且能通過使用Binding從資料來源獲得值(依賴在別人身上)的屬性。擁有依賴屬性的物件被稱為“依賴物件”。與傳統CLR屬性和麵向物件思想相比依賴屬性有很多新穎之處,其中包括:

  • 節省例項對記憶體的開銷。
  • 屬性值可以通過Binding依賴在其他物件上。

依賴屬性對記憶體的使用方式

CLR屬性記憶體使用方式:例項的每個CLR屬性都包裝著一個非靜態的欄位(或者說由一個非靜態的欄位在後臺支援),思考這樣一個問題:TextBox有138個屬性,假設每個CLR屬性都包裝著一個4位元組的欄位,如果程式執行的時候建立了10列1000行的一個TextBox列表,那麼這些欄位將佔用4138

10*1000≈5.26M記憶體!在這100個屬性中最常用的也就是TextBox屬性,這意味著大多數記憶體都會浪費掉。

依賴屬性記憶體使用方式:WPF允許物件在被建立的時候並不包含用於儲存資料的空間(即欄位所佔用的空間)、只保留在需要用到資料時能夠獲得的預設值、借用其他物件資料或實時分配空間的能力—這種物件就被稱為依賴物件(Dependency Object)而它這種實時獲取資料的能力則依靠依賴屬性(Dependency Property)來實現。WPF開發中,必須使用依賴物件作為依賴屬性的宿主,使二者結合起來,才能形成完整的Binding目標被資料所驅動。

理解:一個登山隊員,他的全部裝備有很多,包括登山服、登山靴、登山杖、護目鏡、繩索、無線電、水、食品甚至還有氧氣瓶等。倘若使去登穆朗瑪峰,這些裝備都需要帶上,要是去登香山呢?如果也揹著氧氣瓶起步怪哉!所以,實際一點的辦法使—用得著就帶上,用不著就不帶,有必要得時候可以借別人得用一下。這就是WPF中依賴屬性的原理。

WPF系統中依賴物件的概念被DependencyObject 類所實現,依賴屬性的概念則由DependencyProperty類所實現。DependencyObject具有GetValue和SetValue兩個方法:

//
// 摘要:
//     表示參與依賴屬性系統的物件。
[NameScopeProperty("NameScope", typeof(NameScope))]
public class DependencyObject : DispatcherObject
{
    //
    // 摘要:
    //     對 System.Windows.DependencyObject 的此例項返回依賴屬性的當前有效值。
    //
    // 引數:
    //   dp:
    //     要檢索其值的屬性的 System.Windows.DependencyProperty 識別符號。
    //
    // 返回結果:
    //     返回當前有效值。
    //
    // 異常:
    //   T:System.InvalidOperationException:
    //     指定 dp 或其值無效,或者指定 dp 不存在。
    public object GetValue(DependencyProperty dp);
    //
    // 摘要:
    //     設定依賴屬性的本地值,該值由其依賴屬性識別符號指定。
    //
    // 引數:
    //   dp:
    //     要設定的依賴項屬性的識別符號。
    //
    //   value:
    //     新的本地值。
    //
    // 異常:
    //   T:System.InvalidOperationException:
    //     嘗試修改只讀依賴屬性或密封 System.Windows.DependencyObject 上的屬性。
    //
    //   T:System.ArgumentException:
    //     value 的型別不是為 dp 屬性註冊時使用的正確型別。
    public void SetValue(DependencyProperty dp, object value);
}

這兩個方法都以DependencyProperty物件作為引數,GetValue方法通過DependencyProperty物件獲取資料;SetValue通過DependencyProperty物件儲存值—正式這兩個方法把DependencyObject和DependencyProperty緊密結合在一起。

DependencyObject是WPF系統中相當底層的一個基類,WPF的所有UI控制元件都是依賴物件,UI控制元件的絕大多數屬性都已經依賴化了。

宣告和使用依賴屬性

  1. 宣告依賴屬性,定義表示屬性的物件,它是DependencyProperty的例項。屬性資訊應該始終保持可用,甚至可能需要在多個類之間共享這些資訊(在WPF元素中這是十分普遍的)。因此,必須將DependencyProperty物件定義為與其相關聯的類的靜態欄位。依賴屬性就是這個由 public static readonly修飾的DependencyProperty例項。
  2. 註冊依賴項屬性,這一步驟需要在任何使用屬性的程式碼之前完成,因此必須在與其關聯的類的靜態建構函式中進行。
  3. 新增CLR屬性包裝器

語法糖:propdp

DependencyProperty例項的宣告特點很鮮明—引用變數由public static readonly三個修飾符修飾,例項並非使用new操作符得到而是使用DependencyProperty.Register方法生成

//宣告、註冊依賴屬性、新增CLR屬性包裝器
public class Student:DependencyObject
{
    //01 宣告依賴屬性
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register("Name",typeof(string),typeof(Student)); //02 註冊依賴屬性
    //03 新增CLR包裝器
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty,value); }
    }
}
//使用自定義依賴屬性
private void Button_Click(object sender, RoutedEventArgs e)
{
    Student stu = new Student();
    stu.Name = this.textBox1.Text;
    this.textBox2.Text = stu.Name;           
}

 //
 // 摘要:
 //     使用指定的屬性名稱、屬性型別、所有者型別和屬性元資料註冊依賴屬性。
 //
 // 引數:
 //   name:
 //     要註冊的依賴屬性的名稱。	用這個引數指明以那個CLR屬性作為這個依賴屬性的包裝器,或者說此依賴屬性支援(back)的是哪個CLR屬性。因此引數值為Name
 //
 //   propertyType:
 //     屬性的型別。	依賴屬性用來儲存什麼型別的值,學生的姓名是Steing型別,因以引數為typeof(string)
 //
 //   ownerType:
 //     正在註冊依賴屬性的所有者型別。	依賴屬性的宿主是什麼型別,或者說DependencyProperty.Register方法將把這個依賴屬性註冊關聯到那個型別上。本例中的意圖是為Student類準備一個可依賴的名稱屬性,所以需要把NameProperty註冊成與Student關聯,因此引數值為typeof(Student)
 //
 //   typeMetadata:
 //     依賴屬性的屬性元資料。	這個引數的型別是PropertyMetadata,作用是給依賴屬性的DefaultMetadata屬性賦值。顧名思義,DefaultMetadata的作用是向依賴屬性的呼叫者提供一些基本資訊,這些資訊包括:
//	CoercerValueCallback:依賴屬性被強制改變時,此委託會被呼叫,此委託可關聯一個影響的函式。
//	DefaultValue:依賴屬性未被顯示賦值時,若讀取之則獲得此預設值,不設此值會拋異常。
//	IsSealed:控制PropertyMetadata的屬性值是否可以更改,預設值為true。
//	PropertyChangedCallback:依賴屬性的值被改變之後此委託會被呼叫,此委託可關聯一個影響函式。
 //
 // 返回結果:
 //     一個依賴屬性識別符號,應使用它來設定類中 public static readonly 欄位的值。 稍後將此識別符號用來引用依賴屬性,從而實現以程式設計方式設定其值或獲取元資料等操作。
 public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata);

依賴屬性值存取的祕密

一句話概括DependencyProperty物件的建立與註冊,那就是:建立也給DependencyProperty例項並用它的CLR屬性名和宿主型別名生成hash code,最後把hash code和DependencyProperty例項作為Key-Value對存入全域性的、名為PropertyFromName的Hashtable中。這樣,WPF屬性系統通過CLR屬性名和宿主型別名就可以從這個全域性的Hashtable中檢索處對應的DependencyProperty例項。最後,生成的DependencyProperty例項被當作返回值交還。

其中被static關鍵字所修飾的依賴屬性物件其作用是用來檢索真正的屬性值而不是儲存值;被用作檢索鍵值的實際上是依賴屬性的GlobalIndex屬性(本質是其hash code,而hash code又被CLR包裝器名和宿主型別名共同決定)為了保證GlobalIndex屬性值的穩定性,我們宣告的時候又使用了readonly關鍵字進行修飾。

WPF系統的設計理念,即以public static 型別的變數作為標記,並以這個標記為索引進行物件的儲存訪問、修改、刪除等操作。(後面的路由事件、命令系統等也都會用到這樣的理念)。同時,我們也可以理解為什麼WPF在效能上還不盡如意。

什麼是附加屬性

概念:附加屬性是XAML定義的概念。附加屬性旨在用作可在任何物件上設定的全域性屬性的型別。在Windows Presentation Foundation(WPF)中,通常將附加屬性定義為不具有常規屬性“包裝器”的依賴項屬性的一種特殊形式。

理解:附加屬性的本質就是依賴屬性,二者盡在註冊和包裝器上有一點區別。附加屬性是說一個屬性本來不屬於某個物件,但由於某種需求而被後來附加上。也就是把物件放入一個特定環境後物件才具有的屬性(表現出來的就是被環境賦予的屬性)就稱為附加屬性。

作用:附加屬性的作用就是將屬性與資料型別(宿主)解耦,讓資料型別的設計更加靈活。

語法糖:propa

應用場景:附加屬性的目的之一是允許不同的子元素為在父元素中定義的屬性指定唯一值。此場景的特定應用是讓子元素通知父元素如何在使用者介面(UI)中呈現它們。一個示例是DockPanel.Dock屬性。所述DockPanel.Dock因為它被設計為在其被包含在內的元件設定場所被作為附加屬性建立DockPanel中,而不是在DockPanel中本身。該DockPanel中類定義靜態的DependencyProperty命名的欄位DockProperty,然後提供GetDockSetDock方法作為附加屬性的公共訪問器。

示例:

// 使用語法糖propa	然後完形填空即可
public static int GetGrade(DependencyObject obj)
{
    return (int)obj.GetValue(GradeProperty);
}

public static void SetGrade(DependencyObject obj, int value)
{
    obj.SetValue(GradeProperty, value);
}

// Using a DependencyProperty as the backing store for Grade.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty GradeProperty =
    DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new PropertyMetadata(0));

可以明顯地看出,GradeProperty就是一個DependencyProperty型別成員變數,宣告式一樣使用public static readonly 三個關鍵字共同修飾。唯一的不同就是註冊附加屬性使用的是名為RegisterAttached的方法,但引數卻與使用Register方法無異。附加屬性的包裝器也與依賴屬性不同—依賴屬性使用CLR屬性對GetValue和SetValue兩個方法進行包裝,附加屬性則使用兩個方法分別進行包裝—這樣做完全是為了在使用的時候保持語句行文上的通暢。