共用方式為


自訂相依性屬性

在這裡,我們將說明如何使用 C++、C# 或 Visual Basic 定義和實作 Windows 執行階段應用程式的相依性屬性。 我們列出應用程式開發人員和元件作者可能想要建立自訂相依性屬性的原因。 我們說明自訂相依性屬性的實作步驟,以及一些可改善相依性屬性效能、可用性或多功能性的最佳實務。

先決條件

我們假設您已閱讀 相依性屬性概觀,並且您從現有相依性屬性的使用者觀點瞭解相依性屬性。 若要遵循本主題中的範例,您也應該瞭解 XAML,並知道如何使用 C++、C# 或 Visual Basic 撰寫基本的 Windows 執行階段應用程式。

什麼是相依性屬性?

若要透過相依性屬性支援屬性的樣式、資料繫結、動畫和預設值,則應該如此實作。 相依性屬性值不會儲存為類別上的欄位,它們會由 xaml 架構儲存,並使用索引鍵來參考,當呼叫 DependencyProperty.Register 方法向 Windows 執行階段屬性系統註冊屬性時,會擷取索引鍵。 相依性屬性只能由衍生自 DependencyObject 的類型使用。 但 DependencyObject 在類別階層中相當高,因此大部分用於 UI 和簡報支援的類別都可以支援相依性屬性。 如需相依性屬性的詳細資訊,以及本檔中用來描述它們的一些術語和慣例,請參閱 相依性屬性概觀

Windows 執行階段中相依性屬性的範例包括: Control.BackgroundFrameworkElement.WidthTextBox.Text 等。

慣例上,每個由類別公開的相依性屬性都會有一個相應的 public static readonly 屬性,其類型是 DependencyProperty,並在該類別上公開,以提供該相依性屬性的識別碼。 識別碼的名稱遵循此慣例:相依性屬性的名稱,並將字串「Property」新增至名稱結尾。 例如,Control.Background 屬性的對應 DependencyProperty 識別碼是 Control.BackgroundProperty。 識別碼會儲存相依性屬性的註冊相關資訊,然後可用於涉及相依性屬性的其他作業,例如呼叫 SetValue

屬性包裝器

相依性屬性通常具有包裝函式實作。 如果沒有包裝函式,取得或設定屬性的唯一方式是使用相依性屬性公用程式方法 GetValueSetValue ,並將識別碼作為參數傳遞給它們。 對於名義上屬性的事物來說,這樣的用法相當不自然。 但是,使用包裝函式時,您的程式碼和任何其他參考相依性屬性的程式碼都可以使用簡單的物件屬性語法,這對於您使用的語言來說是自然的。

如果您自己實作自訂相依性屬性,並希望它是公用且易於呼叫的,請也定義屬性包裝函式。 屬性包裝函式也可用於將相依性屬性的基本資訊報告給反射或靜態分析程序。 具體來說,包裝函式是您放置屬性( 例如 ContentPropertyAttribute)的位置。

何時將屬性實作為相依性屬性

每當您在類別上實作公用讀/寫屬性時,只要您的類別衍生自 DependencyObject,您就可以選擇讓您的屬性作為相依性屬性運作。 有時,用私人場地支持您的財產的典型技術就足夠了。 將自訂屬性定義為相依性屬性並不一定必要或適當。 選擇將取決於您希望財產支持的場景。

當您想要將屬性實作為相依性屬性時,您可以考慮將屬性實作為相依性屬性,以支援 Windows 執行階段或 Windows 執行階段應用程式的一或多個功能:

  • 透過樣式設定屬性
  • 作為使用 {Binding} 進行資料繫結的有效目標屬性之一
  • 透過動畫板支援動畫值
  • 當財產價值因以下原因發生變化時進行報告:
    • 財產系統本身採取的行動
    • 環境
    • 使用者動作
    • 閱讀和寫作風格

定義相依性屬性的檢查清單

定義相依性屬性可以視為一組概念。 這些概念不一定是程序步驟,因為實作中的單一程式碼行可以解決數個概念。 此列表僅提供快速概述。 我們將在本主題稍後更詳細地說明每個概念,並以數種語言向您展示範例程式碼。

  • 向屬性系統註冊屬性名稱 (呼叫 Register) ,指定擁有者類型和屬性值的類型。
    • Register 有一個必須的參數,預期需要屬性中繼資料。 為此指定 null,或者如果您需要屬性更改行為,或是需要藉由呼叫 ClearValue 來還原的以中繼資料為基礎的預設值,請指定 PropertyMetadata 的實例。
  • DependencyProperty 識別碼定義為擁有者類型的 公用靜態唯讀 屬性成員。
  • 定義包裝屬性,遵循您正在實作的語言中使用的屬性存取器模型。 包裝函式屬性名稱應該符合您在 Register 中使用的名稱字串。 實作 getset 存取子,以連接包裝函式與它包裝的相依性屬性,方法是呼叫 GetValueSetValue,並將您自己的屬性識別碼傳遞為參數。
  • (選用)將 ContentPropertyAttribute 等屬性放在包裝函式上。

備註

如果您要定義自訂附加屬性,通常會省略包裝函式。 相對地,您會撰寫 XAML 處理器可以使用的不同形式的存取子。 請參閱自訂附加屬性。

註冊財產

若要讓您的屬性成為相依性屬性,您必須將屬性註冊到 Windows 執行階段屬性系統所維護的屬性存放區。 若要註冊屬性,請呼叫 Register 方法。

針對 Microsoft .NET 語言 (C# 和 Microsoft Visual Basic) ,您可以在類別主體內呼叫 Register (在類別內部,但在任何成員定義外部)。 識別碼是由 Register 方法呼叫提供,做為傳回值。 Register 呼叫通常會做為靜態建構函式,或做為類別中 DependencyProperty 類型的公用靜態唯讀屬性初始化的一部分。 此屬性會公開您相依性屬性的識別碼。 以下是 Register 呼叫的範例。

備註

將相依性屬性註冊為識別碼屬性定義的一部分是一般實作,但您也可以在類別靜態建構函式中註冊相依性屬性。 如果您需要多行程式碼來初始化相依性屬性,此方法可能有意義。

針對 C++/CX,您可以選擇如何在標頭和程式碼檔之間分割實作。 典型的分割是在標頭中將標識符本身宣告為 公共靜態 屬性,並具有 get 實作,但沒有 設定get實作是指私有欄位,這是未初始化的DependencyProperty實例。 您還可以宣告包裝器及其get和set方法的實作。 在此情況下,標題中包含一些基本的實作。 如果封裝函式需要「Windows 執行階段」屬性,也請在標頭中添加屬性。 將 Register 呼叫放在程式碼檔中,在只有在應用程式第一次初始化時才會執行的協助程式函式內。 使用 Register 的傳回值來填入您在標頭中宣告的靜態但未初始化的識別碼,您最初會在實作檔案的根範圍中將其設定為 nullptr

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
    static Windows::UI::Xaml::DependencyProperty LabelProperty()
    {
        return m_labelProperty;
    }

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{
    if (_LabelProperty == nullptr)
    {
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    }
}

備註

針對 C++/CX 程式碼,您有私有欄位和公用唯讀屬性來顯示 DependencyProperty 的原因是,使用您的相依性屬性的其他呼叫端也可以使用需要公用識別碼的屬性系統實用工具 API。 如果您將識別碼設為私人,使用者就無法使用這些公用程式 API。 這類 API 和案例的範例包括 GetValueSetValue by choice、 ClearValueGetAnimationBaseValueSetBindingSetter.Property。 您無法為此使用公用欄位,因為 Windows 執行階段中繼資料規則不允許公用欄位。

相依性屬性名稱慣例

相依性屬性有命名慣例;除了特殊情況外,在所有情況下都遵循它們。 相依性屬性本身有一個基本名稱 (上述範例中的「標籤」),該名稱會作為 Register 的第一個參數提供。 名稱在每個註冊類型中必須是唯一的,而且唯一性需求也適用於任何繼承的成員。 透過基底類型繼承的相依性屬性已被視為註冊類型的一部分;繼承的屬性名稱無法再次註冊。

警告

雖然您在此處提供的名稱可以是您選擇的語言在程式設計中有效的任何字串識別碼,但您通常也希望能夠在 XAML 中設定相依性屬性。 若要在 XAML 中設定,您選擇的屬性名稱必須是有效的 XAML 名稱。 如需詳細資訊,請參閱 XAML 概觀

當您建立識別碼屬性時,請將註冊屬性時的屬性名稱與尾碼 “Property” (例如“LabelProperty”) 結合。 此屬性是相依性屬性的識別碼,而且會用來做為您在自己的屬性包裝函式中進行的 SetValueGetValue 呼叫的輸入。 屬性系統和其他 XAML 處理器也會使用它,例如 {x:Bind}

實作包裝函式

您的屬性包裝函式應該在 get 實作中呼叫 GetValue,並在集合實作中呼叫 SetValue

警告

除了特殊情況外,您的包裝函式實作應該只執行 GetValueSetValue 作業。 否則,當您的屬性透過 XAML 設定時,與透過程式碼設定時,您會得到不同的行為。 為了提高效率,XAML 剖析器會在設定相依性屬性時略過包裝函式;並透過 SetValue 與支援存放區交談。

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String)
    End Get
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

自訂相依性屬性的屬性中繼資料

將屬性中繼資料指派給相依性屬性時,會針對屬性擁有者類型或其子類別的每個實例,將相同的中繼資料套用至該屬性。 在屬性中繼資料中,您可以指定兩種行為:

  • 屬性系統指派給屬性所有案例的預設值。
  • 每當偵測到屬性值變更時,就會在屬性系統內自動叫用的靜態回呼方法。

以屬性中繼資料調用 Register

在先前呼叫 DependencyProperty.Register 的範例中,我們傳遞了 propertyMetadata 參數的 Null 值。 若要讓相依性屬性提供預設值或使用屬性變更的回呼,您必須定義提供其中一項或兩項功能的 PropertyMetadata 執行個體。

一般而言,您會在 DependencyProperty.Register 的參數內,提供 PropertyMetadata 作為內嵌建立的實例。

備註

如果您要定義 CreateDefaultValueCallback 實作,則必須使用公用程式方法 PropertyMetadata.Create ,而不是呼叫 PropertyMetadata 建構函式來定義 PropertyMetadata 實例。

下一個範例會參考具有 PropertyChangedCallback 值的 PropertyMetadata 實例,以修改先前顯示的 DependencyProperty.Register 範例。 「OnLabelChanged」回呼的實作將在本節稍後顯示。

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

預設值

您可以指定相依性屬性的預設值,讓屬性在未設定時一律傳回特定的預設值。 此值可能與該屬性類型的固有預設值不同。

如果未指定預設值,則相依性屬性的預設值是參考類型的 Null,或值類型或語言基本類型的類型預設值 (例如,整數為 0,字串為空字串)。 建立預設值的主要原因是當您在屬性上呼叫 ClearValue 時,會還原此值。 以每個屬性為基礎建立預設值可能比在建構函式中建立預設值更方便,特別是針對值類型。 不過,針對參考類型,請確定設定預設值不會建立無意的單例模式。 如需詳細資訊,請參閱本主題稍後的最佳做法

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

備註

請勿使用預設值 UnsetValue 註冊。 如果這樣做,會讓財產消費者感到困惑,並在財產系統內產生意想不到的後果。

CreateDefaultValue回呼函數

在某些情況下,您會定義在多個 UI 執行緒上使用的物件相依性屬性。 如果您要定義多個應用程式所使用的資料物件,或您在多個應用程式中使用的控制項,則可能會出現這種情況。 您可以藉由提供 CreateDefaultValueCallback 實作,而不是繫結至註冊屬性之執行緒的預設值實例,來啟用不同 UI 執行緒之間的物件交換。 基本上, CreateDefaultValueCallback 會定義預設值的 Factory 。 CreateDefaultValueCallback 所傳回的值一律會與使用物件的目前 UI CreateDefaultValueCallback 執行緒相關聯。

若要定義指定 CreateDefaultValueCallback 的中繼資料,您必須呼叫 PropertyMetadata.Create 來傳回中繼資料實例; PropertyMetadata 建構函式沒有包含 CreateDefaultValueCallback 參數的簽章。

CreateDefaultValueCallback 的一般實作模式是建立新的 DependencyObject 類別,將 DependencyObject 每個屬性的特定屬性值設定為預期的預設值,然後透過 CreateDefaultValueCallback 方法的傳回值,將新類別傳回為 Object 參考。

屬性變更的回呼方法

您可以定義屬性變更的回呼方法,以定義屬性與其他相依性屬性的互動,或在屬性變更時更新物件的內部屬性或狀態。 如果叫用回呼,屬性系統已判斷有有效的屬性值變更。 因為回呼方法是靜態的,所以回呼的 d 參數很重要,因為它會告訴您類別的哪個實例報告了變更。 一般實作會使用事件資料的 NewValue 屬性,並以某種方式處理該值,通常是在傳遞為 d 的物件上執行一些其他變更。 屬性變更的其他回應是拒絕 NewValue 所報告的值、還原 OldValue,或將值設定為套用至 NewValue 的程式設計條件約束。

下一個範例顯示 PropertyChangedCallback 實作。 它會實作您在先前 Register 範例中看到的參考方法,作為 PropertyMetadata 建構引數的一部分。 此回呼所解決的案例是,類別也有名為 「HasLabelValue」 的計算唯讀屬性 (未顯示實作) 。 每當重新評估 “Label” 屬性時,都會叫用此回呼方法,而回呼可讓相依計算值與相依性屬性的變更保持同步。

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

結構和列舉的屬性變更行為

如果 DependencyProperty 的類型是列舉或結構,即使結構或列舉值的內部值未變更,也可以叫用回呼。 這與系統基本類型(例如字串)不同,後者只有在值變更時才會呼叫它。 這是內部在這些值上完成的 box 和 unbox 操作所引起的副作用。 如果您有值是列舉或結構的屬性的 PropertyChangedCallback 方法,您必須自行轉換值,並使用可供現在轉換值的重載比較運算子來比較 OldValueNewValue 。 或者,如果沒有這類運算子可用 (自訂結構可能就是這種情況),您可能需要比較個別值。 如果結果是值未變更,您通常會選擇不執行任何動作。

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    }
    // else this was invoked because of boxing, do nothing
    }
}

最佳做法

當您定義自訂相依性屬性時,請記住下列考量,作為最佳實務。

DependencyObject 和執行緒的關係

所有 DependencyObject 實例都必須在 UI 執行程上建立,該執行緒與 Windows 執行階段應用程式所顯示的目前 Window 相關聯。 雖然每個 DependencyObject 都必須在主要 UI 執行線上建立,但您可以呼叫 Dispatcher,使用來自其他執行序的 Dispatcher 參考來存取物件。

DependencyObject 的執行緒層面是相關的,因為它通常表示只有在 UI 執行緒上執行的程式碼才能變更甚至讀取相依性屬性的值。 在正確使用 非同步 模式和背景背景工作執行緒的典型 UI 程式碼中,通常可以避免執行緒問題。 如果您正在定義自己的 DependencyObject 類型,並嘗試將它們做為資料來源或其他不一定合適的案例使用,通常只會遇到 DependencyObject 相關的執行緒問題。

避免無意的單例

如果您要宣告採用引用類型的依賴性屬性,並在建立 PropertyMetadata 的程式碼中呼叫該引用類型的建構函式,則可能會發生非預期的單例。 發生的情況是,相依性屬性的所有使用方式都只共用 PropertyMetadata 的一個實例,因此嘗試共用您建構的單一參考類型。 然後,您透過相依性屬性設定的該值類型的任何子屬性都會以您可能不想要的方式傳播到其他物件。

如果您想要非 Null 值,可以使用類別建構函式來設定引用類型相依性屬性的初始值,但請注意,這樣做會將其視為 相依性屬性概觀 中的本機值。 如果您的類別支援範本,則使用範本來達到此目的可能更合適。 避免單一模式但仍提供有用預設值的另一種方法是在參考類型上公開靜態屬性,為該類別的值提供適當的預設值。

集合類型相依性屬性

集合類型相依性屬性有一些額外的實作問題需要考慮。

集合類型相依性屬性在 Windows 執行階段 API 中相對罕見。 在大部分情況下,您可以使用項目是 DependencyObject 子類別的集合,但集合屬性本身實作為傳統的 CLR 或 C++ 屬性。 這是因為集合不一定適合某些涉及相依性屬性的典型案例。 例如:

  • 通常,您不會對一個集合進行動畫處理。
  • 您通常不會使用樣式或範本預先填入集合中的項目。
  • 雖然繫結至集合是主要案例,但集合不需要是相依性屬性,才能成為繫結來源。 針對系結目標,更典型的是使用 ItemsControlDataTemplate 的子類別來支援集合專案,或使用檢視模型模式。 如需有關對集合進行繫結的詳細資訊,請參閱 深入資料繫結
  • 集合變更的通知會透過 INotifyPropertyChangedINotifyCollectionChanged 等介面,或從 ObservableCollection<T> 衍生集合類型來妥善處理。

不過,集合類型相依性屬性的案例確實存在。 接下來的三個節提供如何實作集合類型相依性屬性的一些指引。

初始化集合

當您建立相依性內容時,您可以透過相依性內容中繼資料來建立預設值。 但請注意不要使用單一靜態集合作為預設值。 相反地,您必須刻意將集合值設定為唯一的(實例)集合,作為集合屬性擁有者類別建構函式邏輯的一部分。

// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(new List<object>())
);

// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);

public ImageWithLabelControl()
{
    // Need to initialize in constructor instead
    Items = new List<object>();
}

DependencyProperty 及其 PropertyMetadata 的預設值是 DependencyProperty 靜態定義的一部分。 藉由提供預設集合 (或其他實例化) 值作為預設值,它會在類別的所有實例之間共用,而不是每個類別都有自己的集合,如一般預期。

變更通知

將集合定義為相依性屬性,不會因為屬性系統叫用 「PropertyChanged」 回呼方法,自動為集合中的專案提供變更通知。 如果您想要集合或集合項目通知(例如,針對資料繫結情境),請實作 INotifyPropertyChangedINotifyCollectionChanged 介面。 如需詳細資訊,請參閱 深入數據綁定

相依性屬性安全性考量

將相依性屬性宣告為公用屬性。 將相依性屬性識別碼宣告為 公用靜態唯讀 成員。 即使您嘗試宣告語言允許的其他存取層級 (例如 protected),相依性屬性一律可以透過識別碼搭配屬性系統 API 來存取。 將相依性屬性識別碼宣告為內部或私人將無法運作,因為這樣屬性系統就無法正常運作。

包裝函式屬性實際上只是為了方便起見,可以呼叫 GetValueSetValue 來略過套用至包裝函式的安全性機制。 因此,請保持包裝屬性公開;否則,您只會使您的屬性更難被合法呼叫者使用,而不會提供任何真正的安全利益。

Windows 執行階段不提供將自訂相依性屬性註冊為唯讀的方法。

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

有一個一般原則,即類別建構函式不應呼叫虛擬方法。 這是因為可以呼叫建構函式來完成衍生類別建構函式的基底初始化,而且當建構的物件實例尚未完全初始化時,可能會透過建構函式進入虛擬方法。 當您衍生自已衍生自 DependencyObject 的任何類別時,請記住,屬性系統本身會在內部呼叫並公開虛擬方法,作為其服務的一部分。 若要避免執行階段初始化的潛在問題,請勿在類別的建構函式內設定相依性屬性值。

註冊 C++/CX 應用程式的相依性屬性

在 C++/CX 中註冊屬性的實作比 C# 更棘手,因為標頭和實作檔案的分離,也因為實作檔案根範圍的初始化是一種不好的做法。 (Visual C++ 元件延伸模組 (C++/CX) 會將靜態初始化運算式程式碼從根範圍直接放入 DllMain,而 C# 編譯器會將靜態初始化運算式指派給類別,從而避免 DllMain 負載鎖定問題。 這裡的最佳做法是宣告一個輔助函式,用於為每個類別執行相依性屬性註冊,每個類別對應一個函式。 然後,針對應用程式取用的每個自訂類別,您必須引用您要使用的每個自訂類別所公開的協助註冊函式。 在InitializeComponent之前,先在應用程式建構函式()App::App()中呼叫每個協助程式註冊函式一次。 該建構函式只會在第一次實際參考應用程式時執行,例如,如果暫停的應用程式繼續,則不會再次執行。 此外,如前面的 C++ 註冊範例所示,每次呼叫 Register 時進行 nullptr 檢查非常重要:這能保證函式的呼叫者無法重複註冊屬性。 如果沒有這類檢查,第二次註冊呼叫可能會讓您的應用程式當機,因為屬性名稱會重複。 如果您查看範例的 C++/CX 版本程式碼,您可以在 XAML 使用者和自訂控制項範例 中看到此實作模式。