共用方式為


自訂相依性屬性 (WPF .NET)

Windows Presentation Foundation (WPF) 應用程式開發人員和元件作者可以建立自定義相依性屬性,以擴充其屬性的功能。 與 Common Language Runtime (CLR) 屬性不同,相依性屬性新增樣式、數據系結、繼承、動畫和預設值的支援。 BackgroundWidthText 是 WPF 類別中現有相依性屬性的範例。 本文說明如何實作自定義相依性屬性,並提供改善效能、可用性和多功能性的選項。

必要條件

本文假設您具備相依性屬性的基本知識,而且您已閱讀 相依性屬性概觀。 若要遵循本文中的範例,如果您熟悉可延伸的應用程式標記語言(XAML),並知道如何撰寫 WPF 應用程式,它很有説明。

相依性屬性的識別碼

相依性屬性是透過 RegisterRegisterReadOnly 呼叫向 WPF 屬性系統註冊的屬性。 方法 Register 會傳 DependencyProperty 回實例,這個實例會保存相依性屬性的已註冊名稱和特性。 您會將 DependencyProperty 實體指派給靜態唯讀欄位,稱為 相依性屬性識別碼,依慣例命名 <property name>Property為 。 例如,屬性的 Background 識別元欄位一律 BackgroundProperty為 。

相依性屬性標識碼是用來取得或設定屬性值的備份欄位,而不是使用私用欄位支援屬性的標準模式。 屬性系統不僅會使用標識碼,XAML 處理器也可以使用標識碼,而且您的程式代碼(以及可能的外部程式代碼)可以透過標識元存取相依性屬性。

相依性屬性只能套用至衍生自 DependencyObject 類型的類別。 大部分的 WPF 類別都支援相依性屬性,因為 DependencyObject 接近 WPF 類別階層的根目錄。 如需相依性屬性的詳細資訊,以及用來描述它們的術語和慣例,請參閱 相依性屬性概觀

相依性屬性包裝函式

未附加屬性的 WPF 相依性屬性是由實 get 作 和 set 存取子的 CLR 包裝函式公開。 藉由使用屬性包裝函式,相依性屬性的取用者可以取得或設定相依性屬性值,就像任何其他 CLR 屬性一樣。 getset 存取子會透過 DependencyObject.GetValueDependencyObject.SetValue 呼叫與基礎屬性系統互動,並傳入相依性屬性標識碼做為參數。 相依性屬性的取用者通常不會呼叫或SetValue直接呼叫GetValue,但如果您要實作自定義相依性屬性,您會在包裝函式中使用那些方法。

實作相依性屬性的時機

當您在衍生自 DependencyObject的類別上實作屬性時,您可以使用標識符來備份屬性 DependencyProperty ,使其成為相依性屬性。 建立相依性屬性是否有幫助,取決於您的案例。 雖然使用私用欄位支援屬性適用於某些案例,但如果您想要讓屬性支援下列一或多個 WPF 功能,請考慮實作相依性屬性:

  • 可在樣式中設定的屬性。 如需詳細資訊,請參閱樣式和範本

  • 支援數據系結的屬性。 如需數據系結相依性屬性的詳細資訊,請參閱 系結兩個控件的屬性。

  • 可透過動態資源參考設定的屬性。 如需詳細資訊,請參閱 XAML 資源

  • 從專案樹狀結構中父元素自動繼承其值的屬性。 為此,即使您也建立 CLR 存取的屬性包裝函式,您也必須使用 RegisterAttached註冊 。 如需詳細資訊,請參閱 屬性值繼承

  • 可產生動畫效果的屬性。 如需詳細資訊,請參閱動畫概觀

  • 當屬性值變更時,WPF 屬性系統的通知。 變更可能是由於屬性系統、環境、用戶或樣式的動作所造成。 您的屬性可以在屬性元數據中指定回呼方法,每次屬性系統判斷屬性值變更時,都會叫用這個方法。 相關的概念是屬性值強制型轉。 如需詳細資訊,請參閱 相依性屬性回呼和驗證

  • 存取 WPF 進程所讀取的相依性屬性元數據。 例如,您可以使用屬性元資料來:

    • 指定變更的相依性屬性值是否應該讓版面配置系統重新編譯元素的視覺效果。

    • 藉由覆寫衍生類別上的元數據,設定相依性屬性的預設值。

  • Visual Studio WPF 設計工具支援,例如在 [屬性] 視窗中編輯自定義控件的屬性。 如需詳細資訊,請參閱 控制撰寫概觀

在某些情況下,覆寫現有相依性屬性的元數據比實作新的相依性屬性更好。 元數據覆寫是否實際取決於您的案例,以及該案例與現有 WPF 相依性屬性和類別的實作相似程度。 如需覆寫現有相依性屬性元數據的詳細資訊,請參閱 相依性屬性元數據

建立相依性屬性的檢查清單

請遵循下列步驟來建立相依性屬性。 某些步驟可以在單行程式代碼中合併和實作。

  1. (選擇性)建立相依性屬性元數據。

  2. 向屬性系統註冊相依性屬性,並指定屬性名稱、擁有者類型、屬性值類型,以及選擇性的屬性元數據。

  3. DependencyProperty將標識元定義為public static readonly擁有者類型上的欄位。 標識元功能變數名稱是附加後綴 Property 的屬性名稱。

  4. 使用與相依性屬性名稱相同的名稱定義 CLR 包裝函式屬性。 在 CLR 包裝函式中,實 get 作 和 set 存取子,以連接包裝函式的相依性屬性。

註冊屬性

為了讓屬性成為相依性屬性,您必須向屬性系統註冊它。 若要註冊屬性,請從類別主體內部呼叫 Register 方法,但在任何成員定義之外。 方法 Register 會傳回呼叫屬性系統 API 時將使用的唯一相依性屬性標識碼。 呼叫在成員定義之外的原因 Register ,是因為您將傳回值指派給 public static readonly 類型的 DependencyProperty欄位。 您將在類別中建立的這個欄位是相依性屬性的識別碼。 在下列範例中,將相依性屬性AquariumGraphic命名為 Register 的第一個自變數。

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

注意

在類別主體中定義相依性屬性是典型的實作,但也可以在類別靜態建構函式中定義相依性屬性。 如果您需要多行程式碼來初始化相依性屬性,這個方式可能有意義。

相依性屬性命名

相依性屬性的已建立命名慣例對於屬性系統的一般行為而言是必要的。 您要建立的識別元字段名稱必須是屬性的註冊名稱,後綴 Property為 。

相依性屬性名稱在註冊類別內必須是唯一的。 透過基底類型繼承的相依性屬性已經註冊,且無法由衍生型別註冊。 不過,您可以藉由將類別新增為相依性屬性的擁有者,使用由不同類型註冊的相依性屬性,甚至是類別不繼承自的類型。 如需將類別新增為擁有者的詳細資訊,請參閱 相依性屬性元數據

實作屬性包裝函式

依照慣例,包裝函式屬性的名稱必須與呼叫的第一個參數 Register 相同,也就是相依性屬性名稱。 您的包裝函式實作會在 存取子和 SetValue 存取子中set呼叫 GetValue get (用於讀寫屬性)。 下列範例顯示包裝函式,其遵循註冊呼叫和標識符字段宣告。 WPF 類別上的所有公用相依性屬性都會使用類似的包裝函式模型。

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );

// Declare a read-write property wrapper.
public Uri AquariumGraphic
{
    get => (Uri)GetValue(AquariumGraphicProperty);
    set => SetValue(AquariumGraphicProperty, value);
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

' Declare a read-write property wrapper.
Public Property AquariumGraphic As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set
        SetValue(AquariumGraphicProperty, Value)
    End Set
End Property

除了罕見的情況下,您的包裝函式實作應該只包含 GetValueSetValue 程序代碼。 如需此原因,請參閱 自定義相依性屬性的含意。

如果您的屬性未遵循已建立的命名慣例,您可能會遇到下列問題:

  • 樣式和範本的某些層面將無法運作。

  • 大部分的工具和設計工具都依賴命名慣例來正確串行化 XAML,並在每個屬性層級提供設計工具環境協助。

  • WPF XAML 載入器目前的實作會完全略過包裝函式,並依賴命名慣例來處理屬性值。 如需詳細資訊,請參閱 XAML 載入和相依性屬性

相依性屬性中繼資料

當您註冊相依性屬性時,屬性系統會建立元數據對象來儲存屬性特性。 方法的多 Register 載可讓您在註冊期間指定屬性元資料,例如 Register(String, Type, Type, PropertyMetadata)。 屬性元數據的常見用法是針對使用相依性屬性的新實例套用自定義預設值。 如果您沒有提供屬性元數據,則屬性系統會將預設值指派給許多相依性屬性特性。

如果您要在衍生自 FrameworkElement的類別上建立相依性屬性,您可以使用更特製化的元資料類別 FrameworkPropertyMetadata ,而不是其基類 PropertyMetadata。 數 FrameworkPropertyMetadata 個建構函式簽章可讓您指定不同的元數據特性組合。 如果您只想要指定預設值,請使用 FrameworkPropertyMetadata(Object) 並將預設值傳遞至 Object 參數。 確定實值型別符合 propertyType 呼叫中指定的 Register

某些 FrameworkPropertyMetadata 多載可讓您指定 屬性的元數據選項旗標 。 屬性系統會將這些旗標轉換成離散屬性,而且 WPF 進程會使用旗標值,例如配置引擎。

設定元數據旗標

設定元數據旗標時,請考慮下列事項:

  • 如果您的屬性值(或變更)會影響版面配置系統轉譯 UI 元素的方式,請設定下列一或多個旗標:

    • AffectsMeasure,表示屬性值的變更需要UI轉譯的變更,特別是物件在其父代內佔用的空間。 例如,設定屬性的 Width 這個元數據旗標。

    • AffectsArrange,表示屬性值的變更需要UI轉譯的變更,特別是物件在其父系中的位置。 一般而言,物件也不會變更大小。 例如,設定屬性的 Alignment 這個元數據旗標。

    • AffectsRender,表示已發生不會影響版面配置和量值的變更,但仍需要另一個轉譯。 例如,設定 Background 屬性的這個旗標,或任何其他會影響元素色彩的屬性。

    您也可以使用這些旗標作為屬性系統(或配置)回呼覆寫實作的輸入。 例如,當實例的 屬性報告值變更並在AffectsArrange元數據中設定時,您可以使用OnPropertyChanged回呼來呼叫InvalidateArrange

  • 某些屬性會以其他方式影響其父元素的轉譯特性。 例如,屬性的變更 MinOrphanLines 可以變更流程文件的整體轉譯。 使用 AffectsParentArrangeAffectsParentMeasure 來向您自己的屬性中的父動作發出訊號。

  • 相依性屬性預設支援資料繫結。 不過,當數據系結沒有實際的情況,或數據系結效能有問題時,您可以使用 IsDataBindingAllowed 來停用數據系結,例如大型物件。

  • 雖然相依性屬性的預設數據系結 模式OneWay,但您可以將特定系結的系結模式變更為 TwoWay。 如需詳細資訊,請參閱 系結方向。 身為相依性屬性作者,您甚至可以選擇將雙向系結設為預設模式。 使用雙向數據系結的現有相依性屬性範例是 MenuItem.IsSubmenuOpen,其狀態是以其他屬性和方法呼叫為基礎。 的案例 IsSubmenuOpen 是其設定邏輯和 的 MenuItem組成,會與默認主題樣式互動。 TextBox.Text 是預設使用雙向系結的另一個 WPF 相依性屬性。

  • 您可以藉由設定 Inherits 旗標來啟用相依性屬性的屬性繼承。 屬性繼承適用於父元素和子元素具有共同屬性的案例,而且子元素繼承通用屬性的父值很合理。 可繼承屬性的範例是 DataContext,它支援使用數據呈現的主要詳細數據案例系結作業。 屬性值繼承可讓您在頁面或應用程式根目錄指定數據內容,以儲存必須指定子項目系結的數據內容。 雖然繼承的屬性值會覆寫預設值,但屬性值可以在本機在任何子項目上設定。 請謹慎使用屬性值繼承,因為它具有效能成本。 如需詳細資訊,請參閱 屬性值繼承

  • Journal設定旗標,指出流覽日誌服務應該偵測或使用您的相依性屬性。 例如,屬性會 SelectedIndex 設定 Journal 旗標,以建議應用程式保留選取專案的日誌記錄。

唯讀相依性屬性

您可以定義唯讀的相依性屬性。 典型的案例是儲存內部狀態的相依性屬性。 例如,是只讀的 IsMouseOver ,因為它的狀態應該只由滑鼠輸入來決定。 如需詳細資訊,請參閱 唯讀相依性屬性

集合類型相依性屬性

集合類型相依性屬性有額外的實作問題需要考慮,例如設定參考型別的預設值,以及集合元素的數據系結支援。 如需詳細資訊,請參閱 集合類型相依性屬性

相依性屬性的安全性

一般而言,您會將相依性屬性宣告為公用屬性,並將 DependencyProperty 標識符字段宣告為 public static readonly 欄位。 如果您指定更嚴格的存取層級,例如 protected,相依性屬性仍可透過其標識碼與屬性系統 API 搭配存取。 即使是受保護的識別符字段也可以透過 WPF 元數據報告或值判斷 API 來存取,例如 LocalValueEnumerator。 如需詳細資訊,請參閱 相依性屬性安全性

若為只讀相依性屬性,從 RegisterReadOnly 傳回的值是 DependencyPropertyKey,而且您通常不會成為 DependencyPropertyKey public 類別的成員。 因為 WPF 屬性系統不會傳播 DependencyPropertyKey 程式碼外部,因此唯讀相依性屬性的安全性比讀寫相依性屬性更好 set

相依性屬性和類別建構函式

Managed 程式代碼程式設計有一個一般原則,通常是由程式代碼分析工具強制執行,類別建構函式不應該呼叫虛擬方法。 這是因為基底建構函式可以在衍生類別建構函式初始化期間呼叫,而基底建構函式所呼叫的虛擬方法可能會在完成衍生類別的初始化之前執行。 當您衍生自已經衍生自 DependencyObject的類別時,屬性系統本身會在內部呼叫並公開虛擬方法。 這些虛擬方法是 WPF 屬性系統服務的一部分。 覆寫方法可讓衍生的類別參與值判斷。 若要避免運行時間初始化的潛在問題,除非您遵循特定的建構函式模式,否則不應該在類別的建構函式內設定相依性屬性值。 如需詳細資訊,請參閱 DependencyObjects的安全建構函式模式。

另請參閱