Silverlight的依賴屬性

Silverlight

自定義依賴項對象和依賴項屬性

本主題描述了 Silverlight 的應用程序開發人員和組件的作者可能想要創建自定義依賴項屬性的原因。本主題描述爲依賴項屬性,以及一些可以提高性能、可用性、或屬性的多功能性的執行選項的實施步驟。 

本主題包括下列各節。

本主題假定您從 Silverlight 類的現有依賴項屬性的使用者角度瞭解依賴項屬性,並且已閱讀依賴項屬性概述爲了按照本主題中的示例操作,您還應當瞭解 XAML 並知道如何編寫基本的 Silverlight 應用程序。

依賴項屬性是通過調用 DependencyProperty.Register 方法向 Silverlight 屬性系統註冊並由 DependencyProperty 標識符字段標識的屬性。通過 CLR 屬性可以支持樣式設置、數據綁定、動畫和默認值,而這些功能也可通過將其作爲依賴項屬性實現來獲得。依賴項屬性只能由 DependencyObject 類型使用,但 DependencyObject 在 Silverlight 類層次結構中的級別很高,因此,Silverlight 中的大多數可用於用戶界面和演示的類都可能支持依賴項屬性。有關依賴項屬性和此文檔中用於描述依賴項屬性的某些術語和約定的更多信息,請參見依賴項屬性概述

Windows Presentation Foundation (WPF) 是第一個定義依賴項屬性概念的技術。Silverlight 在很多方面爲 WPF 的子集。此子集原則也適用於各自的屬性系統。下面是 WPF 中存在但 Silverlight 5 Beta 中不存在的屬性系統功能的列表:

  • 強制值回調和 DependencyObject.CoerceValue不過,您通常可以使用屬性更改回調(在 Silverlight 中支持)來滿足您在這一領域的大多數需要。

  • 自定義只讀依賴項屬性。Silverlight 沒有實現使用密鑰的 DependencyPropertyKeySetValue 模式。沒有此鍵技術,就沒有可用的 API 可以防止外部代碼通過 SetValue 設置該依賴項屬性或違反該屬性的只讀用途。

  • 每類型屬性元數據(使用 OverrideMetadata 建立)。Silverlight 中用於依賴項屬性的屬性元數據始終是最初向其所有者類型註冊的元數據。這一注意事項適用於默認值和屬性更改回調,它們是 Silverlight 屬性元數據中兩個支持的信息集。

  • 由框架定義的屬性元數據子類,例如 UIPropertyMetadataFrameworkPropertyMetadata在 Silverlight 中僅存在 PropertyMetadata,但無疑您可以根據它進行派生。WPF FrameworkPropertyMetadata 爲信息傳遞約定提供"標誌"機制,但 Silverlight 5 Beta 不支持此機制。

  • AddOwner 機制以及現有的依賴項屬性可以重新註冊到不同的所有者類型。

  • 所有的可設置屬性集合見 PropertyMetadataPropertyMetadata截止到 Silverlight 3,Silverlight 支持 GetMetadata 方法和一種可讀的 DefaultValue,但不允許更改元數據或從元數據檢索 PropertyChangedCallback。此外沒有 Silverlight 被屬性系統檢索和分析 APIs,如 DependencyObjectType 類。

  • DependencyPropertyHelper 實用工具和 DependencyObject.GetLocalValueEnumerator

  • FreezableFreezableCollection 類。它們並非必須嚴格遵守的依賴項屬性注意事項,但在整個 WPF 屬性系統中涉及這些概念/類並且它們適用於某些方案(如自定義動畫類型)。

Silverlight 依賴項屬性的示例有:Control.BackgroundFrameworkElement.WidthTextBox.Text 等。某個類公開的每個依賴項屬性都有一個對應的、在該相同類上公開的類型爲 DependencyPropertypublic static readonly 字段。該字段是依賴項屬性的標識符。該標識符字段的命名約定爲:依賴項屬性的名稱以及在該名稱後面追加字符串 Property例如,與 Control.Background 屬性對應的 DependencyProperty 標識符字段爲 Control.BackgroundProperty該標識符用於在註冊依賴項屬性時存儲其相關信息,並且在隨後可用於與依賴項屬性有關的其他操作,如調用 SetValue

依賴項屬性概述中所述,由於包裝實現,Silverlight 中的所有依賴項屬性(大多數附加屬性除外)也是 CLR 屬性。因此,可以從代碼中獲取或設置依賴項屬性,方法是按照與使用其他 CLR 屬性相同的方式來調用充當包裝的 CLR 訪問器。作爲確定的依賴項屬性的使用者,您通常不會使用 DependencyObject 方法 GetValueSetValue,這兩種方法是基礎屬性系統的連接點。相反,Silverlight 依賴項屬性的現有實現將已經相應地使用標識符字段在該屬性的 getset 訪問器內調用了 GetValueSetValue如果您要親自實現自定義的依賴項屬性,則需要用類似的方法爲 CLR 類型系統定義訪問器。

除了向調用方提供易於訪問的權限以外,屬性包裝也有助於將有關依賴項屬性的基本信息報告給反射或靜態分析。屬性包裝的屬性定義也是您放置與 XAML 有關的 CLR 屬性的位置, 如ContentPropertyAttribute 或  TypeConverterAttribute;當 Silverlight XAML 分析器使用自定義依賴項屬性時,這是那特性所期望的位置。

在類上實現讀/寫屬性時,只要類派生自 DependencyObject,便可以選擇使用 DependencyProperty 標識符來支持該屬性,從而使其成爲依賴項屬性。使 Silverlight 屬性成爲依賴項屬性並不始終必要或適當,具體取決於您的需要。有時,使用私有 CLR 字段備份屬性的典型方法便能滿足要求。但是,只要您希望屬性支持以下一種或多種 Silverlight 屬性系統功能,就應將您的屬性作爲依賴項屬性實現:

  • 您希望可在樣式中設置此屬性。有關更多信息,請參見 通過使用 ControlTemplate 自定義現有控件的外觀

  • 您要此屬性是一個支持數據綁定的屬性目標。有關數據綁定依賴項屬性的更多信息,請參見數據綁定

  • 在使用 Silverlight 動畫系統時,您要此屬性來支持動畫值。有關更多信息,請參見 動畫概述

  • 您希望 Silverlight 屬性系統在屬性系統本身、環境或用戶執行的操作或者讀取並使用樣式而更改了屬性以前的值時進行報告。您的屬性可以指定在每次屬性系統確定屬性值已被明確更改時將調用的回調方法。

定義依賴項屬性包括四個不同的概念。這些概念並不一定是嚴格的過程步驟,因爲其中某些概念在實現中被組合爲單行代碼:

  • (可選)爲依賴項屬性創建屬性元數據。僅當您需要屬性更改行爲或基於元數據的默認值(可通過調用 ClearValue 來恢復)時,才需要屬性元數據。

  • 在屬性系統中註冊屬性名稱,並指定所有者類型和屬性值的類型。還可以指定屬性元數據(如果使用);或者,如果您不需要屬性元數據,則指定 null

  • 在所有者類型上將 DependencyProperty 標識符定義爲 public static readonly 字段。

  • 定義其名稱與依賴項屬性字段的名稱匹配的 CLR 包裝屬性,減去 Property 後綴,和從 Register(String, Type, Type, PropertyMetadata) 調用的 name 參數也匹配。實現 CLR getset 訪問器,以便將提供 CLR 公開的包裝與支持它的依賴項屬性進行連接。(如果您定義一個附加屬性,則通常將忽略此包裝,而是編寫 XAML 處理器可以使用的不同風格的訪問器。有關更多信息,請參見 附加屬性概述。)

  • (可選)將任何與 XAML 有關的 CLR 屬性,如ContentPropertyAttributeTypeConverterAttribute,放入屬性定義(外部定義,而不是獲取 - 設置實現)。

在屬性系統中註冊屬性

爲使屬性成爲依賴項屬性,必須在由 Silverlight 屬性系統維護的屬性存儲區中註冊該屬性,並且必須爲其指定一個唯一標識符,以用作屬性系統後續操作的限定符。這些操作可能是內部操作,也可能是調用屬性系統 API 的您自己的代碼。若要註冊屬性,請在類體中(在類的內部,但在任何成員定義的外部)調用 DependencyProperty.Register 方法。DependencyProperty.Register 方法調用也提供此標識符字段作爲返回值。通常在其他成員定義的外部執行 DependencyProperty.Register 調用的原因在於:使用此返回值可以分配和創建一個類型爲 DependencyPropertypublic static readonly 字段,以作爲類的一部分。此字段將成爲依賴項屬性的標識符。

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnUriChanged)))

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(  "AquariumGraphic",  typeof(Uri),  typeof(AquariumObject),  new PropertyMetadata(null,      new PropertyChangedCallback(OnUriChanged)  ) );

依賴項屬性名稱約定

您應完全遵循爲依賴項屬性建立的有關命名約定,但例外情況除外。

依賴項屬性本身將具有一個基本名稱(在前一示例中爲 AquariumGraphic),該名稱是作爲 DependencyProperty.Register 的第一個參數提供的。該名稱在每個註冊類型中必須唯一,並且唯一性範圍包括任何繼承的成員。通過基類型繼承的依賴項屬性將被視爲已是註冊類型的一部分;無法再次註冊已繼承屬性的名稱。

警告說明警告:

雖然您在此處提供的名稱可以是在 CLR 領域實體的 CLR 編程中接受的任何標識符,您應該預測依賴項屬性可能還需要在 XAML 中可設置。爲使其在 XAML 中可設置,所選擇的名稱必須是有效的 XAML 名稱。有關更多信息,請參見 XamlName 語法

當您創建標識符字段時,請將您註冊該屬性時的屬性名稱和後綴 Property 組合起來(例如,AquariumGraphicProperty)。此字段是依賴項屬性的標識符;並且對於在您自己的 CLR 包裝中作出的、由屬性系統作出的以及可能由 XAML 處理器作出的 SetValueGetValue 調用,此字段將用作輸入。

注意說明:

在類體中定義依賴項屬性是典型的實現,但也可以在類靜態構造函數中定義依賴項屬性。如果您需要多行代碼來初始化依賴項屬性,則此方法可能會很有意義。

實現包裝

包裝實現應調用 get 實現中的 GetValueset 實現中的 SetValue(爲清楚起見,此代碼示例中還演示原始註冊調用和字段)。

除例外情況以外,包裝實現在其他情況中應只分別執行 GetValueSetValue 操作。有關這一點的原因將在本主題後面的 XAML 加載和依賴項屬性中討論。

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnUriChanged))) Public Property AquariumGraphic() As Uri     Get         Return DirectCast(GetValue(AquariumGraphicProperty), Uri)     End Get     Set(ByVal value As Uri)         SetValue(AquariumGraphicProperty, value)     End Set  End Property

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(  "AquariumGraphic",  typeof(Uri),  typeof(AquariumObject),  new PropertyMetadata(null,      new PropertyChangedCallback(OnUriChanged)  ) ); public Uri AquariumGraphic {    get { return (Uri)GetValue(AquariumGraphicProperty); }    set { SetValue(AquariumGraphicProperty, value); } }

此外,按照約定,包裝屬性的名稱應該與所選擇並指定爲註冊該屬性的 DependencyProperty.Register 調用的第一個參數的名稱相同。如果您的屬性未能遵循該約定,儘管不一定所有可能的用法都失效,但您將會遇到以下幾個顯而易見的問題:

  • 樣式和模板的某些環節可能不起作用。

  • 大多數工具和設計器都必須依賴於命名約定,以便在每個屬性級別上都提供設計器環境幫助。

  • XAML 處理器可能會完全跳過包裝,並且在處理屬性值時依賴於命名約定。請參見下一節。

XAML 加載和依賴項屬性

XAML 處理器的 Silverlight 5 Beta 實現在本質上能夠識別依賴項屬性。在加載 XAML 內容並處理作爲依賴項屬性 (Property) 的屬性 (Attribute) 時,如果依賴項屬性是使用樣式 Setter 進行設置的,則 Silverlight 5 Beta XAML 處理器針對依賴項屬性使用屬性系統方法。此操作將跳過 CLR 包裝。在實現自定義依賴項屬性時,必須考慮此行爲,而且應當避免將任何其他代碼放在除屬性系統方法 GetValueSetValue 以外的屬性包裝中。

同樣,在 XAML 處理器中,從 XAML 處理中獲取屬性值的其他環節也可能使用 GetValue,而不是使用包裝。因此,您還應當避免在 get 定義中使用除 GetValue 調用以外的任何其他實現。

當將屬性元數據分配給某個依賴項屬性時,相同元數據將應用於所有對象實例上出現這一屬性的所有情形。在屬性元數據中,您可以指定該屬性的兩種行爲:

  • 屬性系統在出現這一屬性的所有情形下分配給此屬性的默認值。

  • 只要更改了屬性值,就會在屬性系統中調用的靜態回調方法。

默認值

如果未指定,則對於引用類型,依賴項屬性的默認值爲 null;或者,對於值類型,則爲默認類型。您可能希望確定默認值的主要原因是:如果您對屬性調用了 ClearValue,則將恢復此值。在每個屬性的基礎之上確定默認值與在構造函數中確定默認值的模式相比,前者可能更爲方便,對於值類型更是如此。不過,對於引用類型,應確保確定默認值不會導致意外的單一實例模式。如果您需要非空值,則可以使用類構造函數來爲引用類型依賴項屬性設置初始值,但請注意,這會被視爲一個旨在體現依賴項屬性優先性的本地值。如果您的類可模板化,則使用模板來實現此目的可能更恰當。另一種避免單一實例模式但仍提供有用默認值的方法是對引用類型公開一個靜態屬性,以便爲該類的各個值提供合適的默認值。

重要說明重要說明:

不要使用 UnsetValue 的特定默認值註冊依賴項屬性。這將使屬性使用方產生困擾,並且將在屬性系統中產生意外後果。

屬性更改回調

如果依賴項屬性與其他依賴項屬性進行交互,或者如果它用於設置對象的內部屬性或狀態,則應使用屬性更改回調。您不需要比較 OldValueNewValue 來確定是否發生了更改;如果調用了回調,則屬性系統已經確定發生了有效的屬性值更改。由於此方法是靜態的,因此回調的 d 參數很重要,因爲它通知您類的哪個實例已報告了更改。

典型的實現使用事件數據類的 NewValue 並以某種方式處理該值(通常是通過對作爲 d 傳遞的對象執行某項其他更改)。其他可能的方案是拒絕所嘗試的 NewValue 集,並恢復 OldValue 或將值設置爲某個已知約束(該約束的含義取決於所嘗試的 NewValue)。

PropertyChangedCallback 實現示例

下面的示例按照在前面演示的 Register 示例中進行註冊的方式實現 OnUriChanged 回調。在此情況下,實現使用更改後的 Uri 值,以便重新構建其內部屬性之一,而該內部屬性根據 Uri 構造 BitmapImage

Private Shared Sub OnUriChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)         Dim sh As Shape = DirectCast(d, Shape)         Dim ib As New ImageBrush()         ib.ImageSource = New BitmapImage(TryCast(e.NewValue, Uri))         sh.Fill = ib     End Sub  End Class

private static void OnUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {    Shape sh = (Shape)d;    ImageBrush ib = new ImageBrush();    ib.ImageSource = new BitmapImage(e.NewValue as Uri);    sh.Fill = ib; }

集合類型依賴項屬性

集合類型依賴項屬性具有一些需要考慮的其他實現問題。有關詳細信息,請參見集合類型依賴項屬性

依賴項屬性安全注意事項

依賴項屬性應聲明爲 public 屬性。依賴項屬性標識符字段應聲明爲 publicstaticreadonly 字段。即使您試圖聲明其他訪問級別(如 protected),也始終可通過標識符並結合屬性系統 API 來訪問依賴項屬性。有關更多信息,請參見 依賴項屬性的安全性

依賴項屬性和類構造函數

託管代碼編程(通常通過 FxCop 等代碼分析工具來強制執行)的一般原則是:類構造函數不應調用虛方法。這是因爲構造函數可作爲派生的類構造函數的基本初始化來調用,並且可能會在所構造的對象實例的不完全初始化狀態下通過構造函數輸入虛方法。當從已派生自 DependencyObject 的任何類派生時請注意,屬性系統本身會在內部將實質方法作爲 Silverlight 屬性系統服務的一部分進行調用和公開。爲了避免運行時初始化出現潛在問題,不應在類的構造函數中設置依賴項屬性值,除非您遵循非常明確的構造函數模式。有關詳細信息,請參見依賴項對象的安全構造函數模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章