相依性屬性值優先順序
本主題說明 Windows Presentation Foundation (WPF) 屬性系統的運作方式如何影響相依性屬性的值,並描述屬性系統套用到屬性有效值的優先順序。
必要條件
本主題假設您已從 WPF 類別的現有相依性屬性消費者角度了解相依性屬性,並已閱讀相依性屬性概觀。 為了遵循本主題中的範例,您也應該了解 XAML 並知道如何撰寫 WPF 應用程式。
WPF 屬性系統
WPF 屬性系統提供一個強大的方法,透過各種因素來決定相依性屬性的值,並啟用即時屬性驗證、晚期繫結,以及將其他屬性的值變更通知相關屬性等功能。 用來決定相依性屬性值的實際順序和邏輯相當複雜。 不過,知道此順序將有助於避免不必要的屬性設定,也可釐清試圖影響或預測相依性屬性值,為何最後並未產生所預期的值。
相依性屬性可能在多處「設定」
以下是範例 XAML,其中同一個屬性 (Background) 有三個可能會影響值的不同「設定」作業。
<StackPanel>
<StackPanel.Resources>
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</StackPanel.Resources>
<Button Template="{StaticResource ButtonTemplate}" Background="Red">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
Which color do you expect?
</Button>
</StackPanel>
在此,您預期會套用哪個色彩 - 紅色、綠色,還是藍色?
除了動畫值和強制型轉之外,區域屬性集會設為最高優先順序。 如果您在本機設定值,可預期該值將會被接受,甚至優先於任何樣式或控制項範本。 在此範例中,Background 在本機設定為 [紅色]。 因此,在此範圍內定義的樣式即使是隱含樣式,而且原本應套用到該範圍內屬於該類型的所有項目,也不會是提供 Background 屬性值的最高優先順序。 如果從該 Button 執行個體移除區域數值 Red,則樣式會具有優先順序,因此按鈕會從樣式取得 Background 值。 在樣式內,觸發程序具有較高的優先順序,因此如果將滑鼠移至按鈕上方,按鈕會變成藍色,否則就會是綠色的。
相依性屬性設定優先順序清單
以下是屬性系統在指派相依性屬性的執行階段值時,所使用的決定順序。 最高優先順序會先列出。 此清單更進一步說明了相依性屬性概觀中所述的部分概要。
屬性系統強制型轉: 如需強制型轉的詳細資訊,請參閱本主題稍後的強制型轉、動畫和基底值。
作用中動畫或具有 Hold 行為的動畫: 屬性的動畫必須優先於基底 (非動畫) 值,才能有任何實際效果,即使該值是在本機設定也一樣。 如需詳細資訊,請參閱本主題稍後的強制型轉、動畫和基底值。
區域數值: 您可以透過方便的「包裝函式」屬性 (也等於是設定為 XAML 中的屬性 (Attribute 或 Property) 項目),或是使用特定執行個體的屬性呼叫 SetValue API,來設定區域數值。 如果使用繫結或資源來設定區域數值,其在優先順序中的作用就如同設定直接值。
TemplatedParent 範本屬性: 如果元素是作為範本一部分 (TemplatedParent 或 ControlTemplate) 建立的,則該元素具有 DataTemplate。 如需何時套用此屬性的詳細資訊,請參閱本主題稍後的 TemplatedParent。 在範本內,優先順序如下:
TemplatedParent 範本的觸發程序。
TemplatedParent 範本的屬性集 (通常是透過 XAML 屬性)。
隱含樣式: 僅適用於
Style
屬性。Style
屬性是由具有符合項目類型之索引鍵的任何樣式資源所填入。 該樣式資源必須位於頁面或應用程式中;查閱隱含樣式資源不會進行到主題中。樣式觸發程序: 來自於頁面或應用程式之樣式內的觸發程序 (這些樣式可為明確或隱含樣式,但不能來自於預設樣式,預設樣式的優先順序較低)。
樣板觸發程序: 樣式內之範本或直接套用之範本中的任何觸發程序。
樣式 setter: 來自頁面或應用程式之樣式內 Setter 中的值。
預設 (主題) 樣式: 如需何時套用此樣式,以及主題樣式與主題樣式內範本之關聯的詳細資訊,請參閱本主題稍後的預設 (主題) 樣式。 在預設樣式內,優先順序如下:
主題樣式中的作用中觸發程序。
主題樣式中的 setter。
繼承: 有幾個相依性屬性會從父項目繼承值到子項目,因此無須在整個應用程式的每一個項目上特別設定。 如需詳細資訊,請參閱屬性值繼承。
來自相依性屬性中繼資料的預設值: 任何指定的相依性屬性都可能會有屬性系統註冊為該特定屬性建立的預設值。 此外,繼承相依性屬性的衍生類別可選擇根據類型覆寫該中繼資料 (包括預設值)。 如需詳細資訊,請參閱相依性屬性中繼資料。 因為會在預設值之前檢查繼承,所以對繼承的屬性而言,父項目的預設值會優先於子項目。 因此,如果未在任何位置設定可繼承的屬性,就會使用在根項目或父項目上指定的預設值,而不是子項目的預設值。
TemplatedParent
作為優先順序項目的 TemplatedParent 不會套用到您直接在標準應用程式標記中宣告之項目的任何屬性。 TemplatedParent 概念只對視覺化樹狀結構內因套用範本而產生的子項目有效。 當屬性系統搜尋 TemplatedParent 範本中的值時,它會搜尋建立該項目的範本。 來自 TemplatedParent 範本的屬性值通常會當做在子項目上設定的區域數值,但其優先順序會比存在的區域數值低,因為範本有可能共用。 如需詳細資訊,請參閱TemplatedParent。
Style 屬性
上述查閱順序適用於所有可能的相依性屬性,但有一個除外,那就是 Style 屬性。 Style 屬性之所以獨特,是因為它本身不能設定樣式,因此優先順序項目 5 至 8 並不適用。 此外,不建議將 Style 顯示為動畫或強制型轉 (將 Style 顯示為動畫需要自訂動畫類別)。 如此一來,就只有三種方式可以設定 Style 屬性:
明確樣式: Style 屬性會直接設定。 在大多數情況下,樣式不會內嵌定義,而是使用明確索引鍵將它當做資源參考。 在此情況下,Style 屬性本身就會當做區域數值,也就是優先順序項目 3。
隱含樣式: Style 屬性不會直接設定。 不過,Style 會存在於資源查閱順序的某個層級 (頁面、應用程式),而且會使用與套用樣式的類型相符的資源索引鍵加上索引鍵。 在此情況下,Style 屬性本身會依順序中識別為項目 5 的優先順序作用。 您可以針對 DependencyPropertyHelper 屬性使用 Style,並在結果中尋找 ImplicitStyleReference,即可偵測到此條件。
預設樣式也稱為主題樣式。Style 屬性不會直接設定,事實上,直到執行階段前都會讀取為
null
。 在此情況下,樣式會來自屬於 WPF 展示引擎一部分的執行階段主題評估。
對於不在主題中的隱含樣式,類型必須完全相符 - MyButton
Button
衍生的類別不會隱含使用 Button
的樣式。
預設 (主題) 樣式
WPF 隨附的每個控制項都有預設樣式。 該預設樣式可能會視主題而不同,因此預設樣式有時也稱為主題樣式。
在控制項的預設樣式中,最重要的資訊就是其範本。該範本是 Template 屬性的 setter,存在於主題樣式中。 如果預設樣式沒有範本,則沒有自訂範本作為自訂樣式一部分的控制項將完全沒有視覺外觀。 預設樣式中的範本會為每個控制項的視覺外觀提供基本結構,而且也會定義在範本視覺化樹狀結構中定義的屬性與相對應控制項類別之間的連接。 每個控制項會公開一組屬性,這些屬性可影響控制項的視覺外觀,但不會完全取代範本。 例如,以 Thumb 控制項的預設視覺外觀為例,這是 ScrollBar的元件。
Thumb 有某些屬性可自訂。 Thumb 的預設範本會建立一個基本結構/視覺化樹狀結構,其中包含數個巢狀 Border 元件,以建立斜面外觀。 如果屬於範本一部分的屬性不打算公開給 Thumb 類別自訂,則該屬性就必須由 TemplateBinding 在範本內公開。 在 Thumb的情況下,這些框線的各種屬性與 Background 或 BorderThickness等屬性會綁定至同一個範本。 但特定其他屬性或視覺化排列方式是用硬式編碼加入控制項範本,或繫結至直接來自於主題的值,除了取代整個範本之外,無法以其他方式變更。 一般而言,如果屬性來自於樣板化父項目且不由範本繫結公開,就無法使用樣式調整,因為沒有簡單的方式可將它設為目標。 但該屬性仍會受到所套用範本中的屬性值繼承所影響,或受到預設值所影響。
主題樣式在其定義中使用類型作為索引鍵。 不過,當主題套用到指定的項目執行個體時,此類型的主題查閱會藉由檢查控制項上的 DefaultStyleKey 屬性來執行。 這與隱含樣式使用常值 Type 的方式相反。
DefaultStyleKey 的值會繼承給衍生類別,即使實作器並未變更它也一樣 (變更屬性的預期方式不是在屬性層級中將它覆寫,而是變更其在屬性中繼資料內的預設值)。 這種間接方式可讓基底類別為沒有樣式 (或者更重要的是,在該樣式內沒有範本,因此完全沒有預設視覺外觀) 的衍生項目定義主題樣式。 因此,您可以從 MyButton
衍生 Button,但仍會得到 Button 的預設範本。 如果您是 MyButton
的控制項作者,而且想要不同的行為,可以覆寫 DefaultStyleKey 上 MyButton
的相依性屬性中繼資料,以傳回不同的索引鍵,然後定義相關的主題樣式,包括必須與 MyButton
控制項一起封裝的 MyButton
範本。 如需主題、樣式和控制項撰寫的詳細資訊,請參閱控制項撰寫概觀。
動態資源參考和繫結
動態資源參考和繫結作業會採用其設定位置的優先順序。 例如,套用到區域數值的動態資源會依優先順序項目 3 作用,主題樣式內屬性 setter 的繫結會依優先順序項目 9 套用,依此類推。 由於動態資源參考和繫結必須能夠從應用程式的執行階段狀態取得值,因此決定任何指定屬性之屬性值優先順序的實際程序也會延伸至執行階段。
嚴格來說,動態資源參考不是屬性系統的一部分,但它們有自己的查閱順序,會與上面所列的順序互動。 該優先順序在 XAML 資源中有更詳細的說明。 基本上來說,該優先順序就是:項目優先於頁面根項目、應用程式、主題和系統。
動態資源和繫結具有其設定位置的優先順序,但會受到其值影響。 因此,如果您將動態資源或繫結設定為區域數值,區域數值的任何變更都會取代整個動態資源或繫結。 即使您呼叫 ClearValue 方法清除在本機設定的值,也無法還原動態資源或繫結。 事實上,如果您在有動態資源或繫結 (沒有常值本機值) 的屬性上呼叫 ClearValue,ClearValue 呼叫也會把它們清除。
SetCurrentValue
SetCurrentValue 方法是設定屬性的另一種方式,但不在優先順序中。 相反地,SetCurrentValue 可讓您變更屬性的值,而不需要覆寫先前值的來源。 每當您想要設定值,但不想要讓該值具有區域數值的優先順序時,都可以使用 SetCurrentValue。 例如,如果某個屬性是由觸發程序設定,然後透過 SetCurrentValue 指派其他值,則屬性系統仍然會優先使用觸發程序,而且該屬性會在發生觸發程序動作時變更。 SetCurrentValue 可讓您變更屬性的值,而不需要提供更高優先順序的來源給它。 同樣地,您也可以使用 SetCurrentValue 來變更屬性的值,而不需覆寫繫結。
強制型轉、動畫和基底值
在這個 SDK 中,強制型轉和動畫都會在名為「基底值」的值上作用。 因此,基底值是透過在項目中往上評估,直到達到第 2 個項目來決定的值。
對動畫而言,如果動畫未同時指定特定行為的 "From" 和 "To",或是動畫刻意在完成後還原成基底值,則基底值可能會對動畫值造成影響。 若要了解實際的情形,請執行 From、To 和 By 動畫目標值範例。 請試著設定此範例中矩形高度的區域數值,讓初始區域數值與動畫中的任何 "From" 不同。 您會發現,動畫會立即使用 "From" 值啟動,並在啟動後取代基底值。 動畫可以藉由指定 Stop FillBehavior,在完成後返回動畫之前找到的值。 在此之後,就會使用正常優先順序來決定基底值。
您可將多個動畫套用到單一屬性,每個動畫可能已透過值優先順序的不同點定義。 不過,這些動畫可能會將值結合起來,而不是只從較高的優先順序套用動畫。 這完全取決於動畫的定義方式,以及要顯示為動畫之值的類型。 如需將屬性顯示為動畫的詳細資訊,請參閱動畫概觀。
強制型轉的套用層級最高。 即使是已在執行中的應用程式也受值強制型轉的限制。 WPF 中的某些現有相依性屬性有內建強制型轉。 對於自訂相依性屬性,您可以在建立屬性時撰寫 CoerceValueCallback,並將回呼當做中繼資料的一部分傳遞,以定義自訂相依性屬性的強制型轉行為。 您也可以覆寫現有屬性的強制型轉行為,方式是在衍生類別中覆寫該屬性上的中繼資料。 強制型轉與基底值互動的方式是,強制型轉上的條件約束會以當時存在的條件約束套用,但仍保留基底值。 因此,如果強制型轉中的條件約束稍後放寬,強制型轉就會傳回最接近該基底值的值,而且強制型轉對屬性的影響有可能會在所有條件約束都放寬之後停止。 如需強制型轉行為的詳細資訊,請參閱相依性屬性回呼和驗證。
觸發程序行為
控制項通常會將觸發程序行為定義為其在主題中之預設樣式的一部分。 在控制項上設定區域屬性,可能會使觸發程序無法從視覺或行為上回應使用者驅動的事件。 屬性觸發程序最常用於控制項或狀態屬性,例如 IsSelected。 例如,當預設停用 Button 時 (IsEnabled 的觸發程序為 false
),主題樣式中的 Foreground 值會使控制項變成「灰色」。 但如果已設定區域 Foreground 值,則區域屬性集在順序上就會優先於一般灰色色彩,即使在此屬性觸發的情節中也一樣。 當您為具有主題層級觸發程序行為的屬性設定值時,請特別小心,並確保不會不當干擾該控制項的預期使用者體驗。
ClearValue 和值優先順序
ClearValue 方法可快速從項目上設定的相依性屬性清除任何在本機套用的值。 不過,呼叫 ClearValue 並不保證在屬性註冊期間於中繼資料建立的預設值是新的有效值。 值優先順序中的所有其他參與者仍在作用中。 只有在本機設定的值會從優先順序移除。 例如,如果您在屬性上呼叫 ClearValue,而該屬性也由主題樣式設定,則會套用主題值作為新值,而不是基於中繼資料的預設值。 如果您要將所有屬性值參與者移出處理序,並將值設定為註冊的中繼資料預設值,您可以藉由查詢相依性屬性中繼資料來明確取得預設值,然後透過 SetValue 的呼叫,使用該預設值來設定屬性的區域數值。