相依性屬性概觀

本主題說明當您使用 C++、C# 或 Visual Basic 搭配 UI 的 XAML 定義來撰寫 Windows 執行階段應用程式時,可供使用的相依性屬性系統。

什麼是相依性屬性?

相依性屬性是特殊類型的屬性。 具體來說,它是屬性的屬性,其中屬性的值會受到屬於 Windows 執行階段一部分的專用屬性系統所影響。

為了支援相依性屬性,定義屬性的物件必須是 DependencyObject (換句話說,其繼承中某個位置有 DependencyObject 基底類別的類別)。 您針對具有 XAML 之 UWP 應用程式的 UI 定義所使用的許多類型都是 DependencyObject 子類別,而且將支援相依性屬性。 不過,來自名稱中沒有「XAML」之Windows 執行階段命名空間的任何類型,都不支援相依性屬性;這類類型的屬性是沒有屬性系統相依性行為的一般屬性。

相依性屬性的目的是提供系統方式,根據其他輸入來計算屬性的值 (在應用程式執行時發生的其他屬性、事件和狀態)。 這些其他輸入可能包括:

  • 外部輸入,例如使用者喜好設定
  • 即時屬性判斷機制,例如資料繫結、動畫和分鏡腳本
  • 多用途範本化模式,例如資源和樣式
  • 透過上層下層關係與物件樹狀結構中其他元素已知的值

相依性屬性代表或支援程式設計模型的特定功能,以使用適用於 UI 和 C# 的 XAML、Microsoft Visual Basic 或 Visual C++ 元件延伸模組 (C++/CX) 定義 Windows 執行階段應用程式。 這些功能包括:

  • 資料繫結
  • 樣式
  • 腳本動畫
  • 「PropertyChanged」行為;您可以實作相依性屬性,以提供可將變更傳播至其他相依性屬性的回呼
  • 使用來自屬性中繼資料的預設值
  • 一般屬性系統公用程式,例如 ClearValue 和中繼資料查閱

相依性屬性和 Windows 執行階段屬性

相依性屬性提供全域的內部屬性存放區,以在執行階段支援應用程式中的所有相依性屬性,藉此擴充基本 Windows 執行階段屬性功能。 這是使用屬性定義類別中私用欄位支援屬性的標準模式替代方案。 您可以將此內部屬性儲存視為任何特定物件 (只要它是 DependencyObject) 都存在的一組屬性識別碼和值。 存放區中的每個屬性都會由 DependencyProperty 執行個體來識別,而不是依名稱來識別。 不過,屬性系統大多會隱藏此實作詳細資料:您通常可以使用簡單名稱來存取相依性屬性 (您所使用的程式碼語言中的程式設計屬性名稱,或撰寫 XAML 時的屬性名稱)。

提供相依性屬性系統基礎的基底類型為 DependencyObjectDependencyObject 定義了可以存取相依性屬性的方法,而 DependencyObject 衍生類別的執行個體在內部支援我們前面提到的屬性存放區概念。

以下是我們在討論相依性屬性時在文件中使用的術語總結:

詞彙 描述
相依性屬性 存在於 DependencyProperty 識別碼上的屬性 (見下文)。 此識別碼通常可做為定義 DependencyObject 衍生類別的靜態成員。
相依性屬性的識別碼 識別屬性的常數值,通常是公用且唯讀的。
屬性包裝函式 Windows 執行階段屬性的可呼叫 getset 實作。 或者,原始定義的語言特定投影。 get 屬性包裝函式實作會呼叫 GetValue,傳遞相關的相依性屬性識別碼。

屬性包裝函式不僅方便呼叫者,也會將相依性屬性公開給任何使用屬性 Windows 執行階段定義的程序、工具或投影。

下列範例會定義 C# 所定義的自訂相依性屬性,並顯示相依性屬性識別碼與屬性包裝函式的關聯性。

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(string),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);


public string Label
{
    get { return (string)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}

注意

上述範例並非做為如何建立自訂相依性屬性的完整範例。 它的目的是要針對任何偏好透過程式碼學習概念的人顯示相依性屬性概念。 如需此範例的更完整說明,請參閱自訂相依性屬性

相依性屬性值優先順序

當您取得相依性項屬性的值時,您將取得透過參與 Windows 執行階段屬性系統的任一輸入為該屬性所決定的值。 存在相依性屬性值優先順序,以便 Windows 執行階段屬性系統可以以可預測的方式計算值,並且熟悉基本優先順序也很重要。 否則,您可能會發現自己處於這樣一種情況:您嘗試在一個優先順序設定屬性,但其他方面 (系統、第三方呼叫者、您自己的一些程式碼) 正在將其設定在另一個優先順序,並且您試圖找出使用了哪個屬性值以及該值來自何處時會感到沮喪。

例如,樣式和範本是用來建立屬性值以及控制項外觀的共用起點。 但在特定的控制項執行個體上,您可能想要變更其值與常見範本值的比較,例如為該控制項提供不同的背景色彩或不同的文字字串做為內容。 Windows 執行階段屬性系統會將優先順序高於樣式和範本所提供值的本機值。 這可讓應用程式特定值覆寫範本的案例,讓控制項在應用程式 UI 中使用它們很有用。

相依性屬性優先順序清單

以下是屬性系統在為相依性屬性指派執行階段值時所使用的明確順序。 最高優先順序會先列出。 您將在此清單中找到更詳細的解釋。

  1. 動畫值:使用中動畫、視覺狀態動畫或具有 HoldEnd 行為的動畫。 為了產生任何實際效果,套用於屬性的動畫必須優先於基底 (無動畫) 值,即使該值是在本機設定的。
  2. 本機值:可以透過屬性包裝函式的便利性來設定本機值,這也相當於在 XAML 中設定為屬性或屬性元素,或透過使用特定執行個體的屬性來呼叫 SetValue 方法來設定。 如果您使用繫結或靜態資源來設定本機值,則這些值在優先順序中會如同已設定本機值一樣,而且如果設定新的本機值,則會清除繫結或資源參考。
  3. 樣板化屬性:如果元素是做為範本 (來自 ControlTemplateDataTemplate) 的一部分建立的,則元素具有這些屬性。
  4. 樣式設定器:來自頁面或應用程式資源的樣式內的設定器的值。
  5. 預設值:相依性屬性可以有預設值做為其中繼資料的一部分。

樣板化屬性

做為優先順序項目的樣板化屬性不適用於直接在 XAML 頁面標記中宣告的元素的任何屬性。 樣板化屬性概念僅適用於 Windows 執行階段將 XAML 範本套用至 UI 元素時所建立的物件,因此會定義其視覺效果。

從控制項模板設定的所有屬性都具有某種類型的值。 這些值幾乎就像控制項的一組擴充預設值,而且通常與稍後您可以藉由直接設定屬性值來重設的值相關聯。 因此,範本集值必須與真正的本機值區別,讓任何新的本機值都可以覆寫它。

注意

在某些情況下,如果範本無法針對執行個體上設定的屬性公開 {TemplateBinding} 標記延伸參考,範本可能會覆寫甚至本機值。 這通常只有在屬性真的不適合在執行個體上設定時,例如,如果它只與視覺效果和範本行為有關,而且與使用範本之控制項的預期函式或執行階段邏輯無關時,才會這麼做。

繫結和優先順序

繫結作業對於其所使用的範圍具有適當的優先順序。 例如,套用於本機值的 {Binding} 充當本機值,而屬性設定器的 {TemplateBinding} 標記延伸功能則像樣式設定器一樣套用。 由於繫結必須等到執行階段才能從資料來源取得值,因此判斷任何屬性的屬性值優先順序的程式也會延伸到執行階段。

繫結不僅以與本機值相同的優先順序運作,而且它們實際上是本機值,其中繫結是延後值的預留位置。 如果您有屬性值的繫結,並在執行階段設定其本機值,則會完全取代繫結。 同樣地,如果您呼叫 SetBinding 來定義只存在於執行階段的繫結,您可以取代您在 XAML 或先前執行的程式碼中套用的任何本機值。

分鏡腳本動畫和基底值

分鏡腳本動畫是依據基底值的概念。 基底值是屬性系統使用其優先順序所決定的值,但省略尋找動畫的最後一個步驟。 例如,基底值可能來自控制項的範本,或可能來自在控制項執行個體上設定本機值。 無論哪種方式,套用動畫都會覆寫這個基底值,只要動畫繼續執行,就會套用動畫值。

對於動畫屬性,如果動畫未明確指定 FromTo,或者動畫在完成時將屬性恢復為其基底值,則基底值仍會對動畫的行為產生影響。 在這些情況下,一旦動畫不再執行,則會再次使用其餘優先順序。

但是,指定具有 HoldEnd 行為的 To 的動畫可以覆蓋本地值,直到動畫被刪除,即使它在視覺上看起來已停止。 從概念上講,這就像是永遠執行的動畫,即使 UI 中沒有視覺效果動畫也一樣。

多個動畫可以套用至單一屬性。 這些動畫可能已定義,以取代值優先順序中不同點的基底值。 不過,這些動畫都會在執行階段同時執行,這通常表示它們必須結合其值,因為每個動畫都對值有相等的影響。 這完全取決於動畫的定義方式,以及要顯示為動畫之值的類型。

如需詳細資訊,請參閱分鏡腳本動畫

預設值

自訂相依性屬性主題中更詳細地解釋了使用 PropertyMetadata 值建立相依性屬性的預設值。

即使這些預設值未明確定義於該屬性的中繼資料中,相依性屬性仍具有預設值。 除非中繼資料已變更它們,否則 Windows 執行階段相依性屬性的預設值通常是下列其中一項:

  • 使用執行階段物件或基本物件類型 (參考類型) 的屬性的預設值為 null。 例如,DataContextnull,直到故意設定或繼承為止。
  • 使用數字或布林值 (值類型) 等基本值的屬性使用該值的預期預設值。 例如,0 表示整數和浮點數,false 表示布林值。
  • 使用 Windows 執行階段結構的屬性具有藉由呼叫該結構的隱含預設建構函式所取得的預設值。 這個建構函式會針對結構的每個基本值欄位使用預設值。 例如,Point 值的預設值將其 XY 值初始化為 0。
  • 使用列舉的屬性具有該列舉中第一個定義成員的預設值。 檢查特定列舉的參考,以查看預設值是什麼。
  • 使用字串的屬性 (.NET 為 System.String,C++/CX 為 Platform::String) 的預設值為空字串 ("")。
  • 基於本主題中進一步討論的原因,集合屬性通常不會實作為相依性屬性。 但是,如果您實作自訂集合屬性並且希望它成為相依性屬性,請確保避免出現非預期的單一,如自訂相依性屬性末端所述。

相依性屬性提供的屬性功能

資料繫結

相依性屬性可以通過套用資料繫結來設定其值。 資料繫結在 XAML 中使用 {Binding} 標記延伸語法、{x:Bind} 標記延伸或程式碼中的 Binding 類別。 對於資料繫結屬性,最終屬性值判斷會延遲到執行階段。 此時,此值會從資料來源取得。 相依性屬性系統在此處扮演的角色,是在尚未知道值時為載入 XAML 等作業啟用預留位置行為,然後藉由與 Windows 執行階段資料繫結引擎互動,在執行階段提供值。

以下範例使用 XAML 中的繫結設定 TextBlock 元素的 Text 值。 繫結使用繼承的資料內容和物件資料來源。 (這兩者均沒有在簡化的範例中顯示;有關顯示內容和來源的更完整的範例,請參閱深入瞭解資料繫結。)

<Canvas>
  <TextBlock Text="{Binding Team.TeamName}"/>
</Canvas>

您也可以使用程式碼而不是 XAML 來建立繫結。 請參閱 SetBinding

注意

針對相依性屬性值優先順序的目的,會將這類繫結視為本機值。 如果為最初保存 Binding 值的屬性設定另一個本機值,則將完全覆寫該繫結,而不僅僅是該繫結的執行階段值。 {x:Bind} 繫結是使用產生的程式碼來實作,以設定屬性的本機值。 如果您為使用 {x:Bind} 的屬性設定本機值,則下次評估繫結時將會取代該值,例如當它觀察其來源物件上的屬性變更時。

繫結來源、繫結目標、FrameworkElement 的角色

若要成為繫結的來源,屬性不需要是相依性屬性;您通常可以使用任何屬性做為繫結來源,但這取決於您的程式設計語言,而且每個屬性都有特定的邊緣案例。 但是,要成為 {Binding} 標記延伸或 Binding 的目標,該屬性必須是相依性屬性。 {x:Bind} 沒有此需求,因為它使用產生的程式碼來套用其繫結值。

如果您在程式碼中建立繫結,請注意 SetBinding API 僅為 FrameworkElement 定義。 但是,您可以使用 BindingOperations 建立繫結定義,進而參考任何 DependencyObject 屬性。

對於程式碼或 XAML,請記住 DataContextFrameworkElement 屬性。 透過使用上層下層屬性繼承的形式 (通常在 XAML 標記中建立),繫結系統可以解析上層元素上存在的 DataContext。 即使下層物件 (具有目標屬性) 不是 FrameworkElement,因此不保留其自己的 DataContext 值,此繼承也可以進行評估。 但是,繼承的上層元素必須是 FrameworkElement 才能設定和保留 DataContext。 或者,您必須定義繫結,以便它可以使用 DataContextnull 值執行。

連接繫結並不是大部分資料繫結案例所需的唯一項目。 若要讓單向或雙向繫結生效,來源屬性必須支援傳播至繫結系統的變更通知,因而成為目標。 對於自訂繫結來源,這表示屬性必須是相依性屬性,或者物件必須支援 INotifyPropertyChanged。 集合應該支援 INotifyCollectionChanged。 某些類別在其實作中支援這些介面,因此它們可用做資料繫結案例的基底類別;ObservableCollection<T> 就是此類的範例。 有關資料繫結以及資料繫結如何與屬性系統相關的更多資訊,請參閱深入瞭解資料繫結

注意

此處列出的類型支援 Microsoft .NET 資料來源。 C++/CX 資料來源使用不同的介面來進行變更通知或可觀察行為,請參閱深入瞭解資料繫結

樣式及範本

樣式和範本是定義為相依性屬性之屬性的兩種案例。 樣式可用於設定定義應用 UI 的屬性。 樣式在 XAML 中定義為資源,可以做為資源集合中的項目,也可以在單獨的 XAML 檔案 (例如主題資源字典) 中定義。 樣式會與屬性系統互動,因為它們包含屬性的 setter。 此處最重要的屬性是 ControlControl.Template 屬性:它會定義 Control 的大部分視覺外觀和視覺狀態。 如需樣式的詳細資訊,以及定義 Style 和使用 setter 的一些範例 XAML,請參閱設定控制項的樣式

來自樣式或範本的值是延遲的值,類似於繫結。 如此一來,控制項使用者就可以重新範本控制項或重新定義樣式。 這就是為什麼樣式中的屬性 setter 只能對相依性屬性採取行動,而不是一般屬性。

腳本動畫

您可以使用分鏡腳本動畫建立相依性屬性值的動畫效果。 Windows 執行階段中的分鏡腳本動畫不僅僅是視覺裝飾。 將將動畫視為一種狀態機技術更有用,它可以設定控制項的單一屬性或所有屬性和視覺效果的值,並隨著時間的推移變更這些值。

若要產生動畫效果,動畫的目標屬性必須是相依性屬性。 此外,若要產生動畫效果,目標屬性的值類型必須由其中一個現有的 Timeline 衍生動畫類型支援。 ColorDoublePoint 的值可以使用插補或主要畫面格技術來產生動畫效果。 大部分的其他值都可以使用離散物件主要畫面格產生動畫效果。

套用並執行動畫時,動畫顯示值的運作優先於屬性可能執行的任何值 (例如本機值)。 動畫也具有可選的 HoldEnd 行為,即使動畫在視覺上看起來已停止,該行為也會導致動畫套用於屬性值。

狀態機器原則是使用分鏡腳本動畫做為控制項之 VisualStateManager 狀態模型的一部分所體現。 如需分鏡腳本動畫的詳細資訊,請參閱分鏡腳本動畫。 如需 VisualStateManager 和定義控制項視覺狀態的詳細資訊,請參閱視覺狀態控制項範本的分鏡腳本動畫。

屬性變更的行為

屬性變更行為是相依性屬性術語「相依性」部分的來源。 當另一個屬性會影響第一個屬性的值時,維護屬性的有效值,在許多架構中是一個困難的開發問題。 在 Windows 執行階段屬性系統中,每個相依性屬性都可以指定每當其屬性值變更時叫用的回呼。 此回呼可用來以一般同步方式通知或變更相關的屬性值。 許多現有的相依性屬性都有屬性變更的行為。 您也可以將類似的回呼行為新增至自訂相依性屬性,並實作您自己的屬性變更回呼。 如需範例,請參閱自訂相依性屬性

Windows 10 引進 RegisterPropertyChangedCallback 方法。 這可讓應用程式程式碼在 DependencyObject 執行個體上,變更指定的相依性屬性時註冊變更通知。

預設值和 ClearValue

相依性屬性可以有定義為其屬性中繼資料一部分的預設值。 對於相依性屬性,其預設值不會在第一次設定屬性之後變成無關緊要。 每當值優先順序中的某些其他行列式消失時,預設值可能會在執行階段再次套用。 (下一節將討論相依性屬性值優先順序。例如,您可能會刻意移除套用至屬性的樣式值或動畫,但您希望該值在您執行此動作之後成為合理的預設值。 相依性屬性預設值可以提供此值,而不需要特別將每個屬性的值設定為額外的步驟。

即使您已經使用本機值來設定屬性,您還是可以刻意將屬性設定為預設值。 若要將值重設為預設值,並同時啟用可能覆寫預設值而非本機值之優先順序的其他參與者,請呼叫 ClearValue 方法 (參考屬性以清除為方法參數)。 您不一定想要讓 屬性字面使用預設值,但清除本機值並還原為預設值可能會啟用您想要立即採取行動的另一個項目,例如使用控制項範本中樣式 setter 所產生的值。

DependencyObject 和執行緒

所有 DependencyObject 執行個體,都必須建立在與 Windows 執行階段應用程式所顯示目前視窗相關聯的 UI 執行緒上。 儘管每個 DependencyObject 必須在主 UI 執行緒上建立,但可以透過存取 Dispatcher 屬性,使用來自其他執行緒的調度員參考來存取這些物件。 然後,您可以在 CoreDispatcher 物件上呼叫 RunAsync 之類的方法,並在 UI 執行緒上的執行緒限制規則內執行程式碼。

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

概念材料