資源概觀
更新:2007 年 11 月
這個概觀說明如何使用 WPF 資源做為重複使用一般定義之物件和值的簡單方式。這個概觀著重於如何在 XAML 中使用資源。您也可以使用程式碼來建立和存取資源,也可以交互使用程式碼和可延伸標記語言 (XAML)。如需詳細資訊,請參閱資源和程式碼。
這個主題包含下列章節。
- 在 XAML 中使用資源
- 靜態和動態資源
- Style、DataTemplate 和隱含索引鍵
- 相關主題
在 XAML 中使用資源
下列範例定義 SolidColorBrush 做為頁面之根項目上的資源。這個範例接著會參考資源,並使用它來設定多個子項目的屬性 (包含 Ellipse、TextBlock 和 Button)。
<Page Name="root"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<Page.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
<Style TargetType="Border" x:Key="PageBackground">
<Setter Property="Background" Value="Blue"/>
</Style>
<Style TargetType="TextBlock" x:Key="TitleText">
<Setter Property="Background" Value="Blue"/>
<Setter Property="DockPanel.Dock" Value="Top"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="#4E87D4"/>
<Setter Property="FontFamily" Value="Trebuchet MS"/>
<Setter Property="Margin" Value="0,40,10,10"/>
</Style>
<Style TargetType="TextBlock" x:Key="Label">
<Setter Property="DockPanel.Dock" Value="Right"/>
<Setter Property="FontSize" Value="8"/>
<Setter Property="Foreground" Value="{StaticResource MyBrush}"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="0,3,10,0"/>
</Style>
</Page.Resources>
<StackPanel>
<Border Style="{StaticResource PageBackground}">
<DockPanel>
<TextBlock Style="{StaticResource TitleText}">Title</TextBlock>
<TextBlock Style="{StaticResource Label}">Label</TextBlock>
<TextBlock DockPanel.Dock="Top" HorizontalAlignment="Left" FontSize="36" Foreground="{StaticResource MyBrush}" Text="Text" Margin="20" />
<Button DockPanel.Dock="Top" HorizontalAlignment="Left" Height="30" Background="{StaticResource MyBrush}" Margin="40">Button</Button>
<Ellipse DockPanel.Dock="Top" HorizontalAlignment="Left" Width="100" Height="100" Fill="{StaticResource MyBrush}" Margin="40" />
</DockPanel>
</Border>
</StackPanel>
</Page>
每個架構層級的項目 (FrameworkElement 或 FrameworkContentElement) 都會具有 Resources 屬性,這個屬性包含某個資源定義的資源 (做為 ResourceDictionary)。您可以在任何項目上定義資源。然而,資源最常定義在根項目上,在此範例中,根項目為 Page。
資源字典中的每個資源都必須具有唯一索引鍵。當您以標記定義資源時,請透過 x:Key 屬性指派唯一索引鍵。一般而言,索引鍵是字串,但是您也可以使用適當的標記延伸,將它設定為其他物件型別。資源的非字串索引鍵是供 WPF 的特定功能區域使用,主要是用於樣式、元件資源和資料樣式設定。
定義資源之後,就可以使用會指定索引鍵名稱的資源標記延伸語法,參考要用於屬性值的資源,例如:
<Button Background="{StaticResource MyBrush}"/>
<Ellipse Fill="{StaticResource MyBrush}"/>
在先前範例中,當 XAML 載入器處理 Button 的 Background 屬性值 {StaticResource MyBrush} 時,資源查閱邏輯會先檢查 Button 項目的資源字典。如果 Button 沒有資源索引鍵 MyBrush 的定義 (其資源集合會是空的),查閱接著會檢查 Button 的父項目 (即 Page)。因此,當您在 Page 根項目上定義資源時,Page 之邏輯樹狀結構中的所有項目都可以存取該資源,而且可以重複使用相同的資源來設定屬性值,這類屬性會接受資源所代表的 Type。在先前範例中,相同的 MyBrush 資源設定了兩個不同的屬性:Button 的 Background 和 Rectangle 的 Fill。
靜態和動態資源
資源可以當成靜態資源或動態資源進行參考。使用 StaticResource 標記延伸或 DynamicResource 標記延伸就可以達成這項作業。標記延伸是 XAML 的功能,因此可以讓標記延伸處理屬性字串以指定物件參考,並將物件傳回給 XAML 載入器。如需標記延伸行為的詳細資訊,請參閱標記延伸和 XAML。
當您使用標記延伸時,通常會以字串形式提供一個或多個該特定標記延伸所處理的參數,而不是在要設定之屬性的內容中進行評估。StaticResource 標記延伸會在所有可用資源字典中查閱索引鍵的值,以處理該索引鍵。這發生在載入期間,也就是載入處理序需要指派採用靜態資源參考之屬性值的時間點。DynamicResource 標記延伸會改為建立運算式來處理索引鍵,而該運算式在應用程式實際執行之前都會保持未評估狀態,而直到執行應用程式時運算式才會進行評估並提供值。
當您參考資源時,下列考量可能會影響是要使用靜態資源參考還是動態資源參考:
如何建立應用程式之資源的整體設計 (每個頁面、在應用程式中、在鬆散 XAML 中、在僅含資源的組件中)。
應用程式功能:即時更新資源是屬於應用程式需求的一部分嗎?
該資源參考型別 (Reference Type) 的個別查閱行為。
特定屬性或資源類型,以及那些類型的原生行為。
靜態資源
靜態資源參考最適合用於下列狀況:
應用程式設計是將其大部分資源集中至頁面或應用程式層級資源字典。靜態資源參考不會根據執行階段行為 (如重新載入頁面) 進行重新評估,因此在資源和應用程式設計不需要大量動態資源參考時,避免使用這些動態資源參考就可以提高效能。
您設定其值的屬性不在 DependencyObject 或 Freezable 上。
您所建立的資源字典會編譯為 DLL,並且封裝為應用程式的一部分,或在應用程式之間共用。
您正在建立自訂控制項的佈景主題,而且定義用於佈景主題內的資源。在這種情況下,您通常不會想要發生動態資源參考查閱行為,反而想要發生靜態資源參考行為,因此,對佈景主題而言,查閱是可以預測的而且獨立的 (Self-Contained)。使用動態資源參考,即使佈景主題內的參考在執行階段之前維持未評估狀態,而在套用佈景主題時,某個本機項目可能會重新定義佈景主題嘗試參考的索引鍵,而且本機項目會落在查閱中佈景主題本身的前面。如果發生這種情況,佈景主題就不會以預期方式運作。
您正使用資源來設定大量相依性屬性。相依性屬性具有屬性系統啟用的有效值快取,因此如果提供的相依性屬性值可以在載入時進行評估,則相依性屬性就不必檢查重新評估後的運算式,而且可以傳回最後一個有效值。這個技術可以提高效能。
您想要變更所有消費者的基礎資源,或想要使用 x:Shared 屬性為每個消費者維護不同的可寫入執行個體。
靜態資源查閱行為
查閱處理序會在資源字典內檢查要求的索引鍵,而這個索引鍵是由設定屬性的項目所定義。
查閱處理序接著會在邏輯樹狀結構中往上周遊至父項目和其資源字典。在到達根項目之前,會持續進行這個作業。
接下來,會檢查應用程式資源。應用程式資源就是資源字典內的資源,這個資源字典是由 WPF 應用程式的 Application 物件所定義。
資源字典內的靜態資源參考必須參考已用詞彙形式定義在資源參考前面的資源。而靜態資源參考無法解析向前參考 (Forward Reference)。基於這個理由,如果您使用靜態資源參考,則必須設計資源字典結構,讓依據資源使用的資源定義於或接近每個對應資源字典的開頭。
靜態資源查閱可以延伸至佈景主題或系統資源中,但是這只有在 XAML 載入器延後要求時才支援。延後是必要的,這樣載入頁面時的執行階段佈景主題才會正確地套用至應用程式。但是,不建議使用已知只存在於佈景主題中或做為系統資源的索引鍵靜態資源參考。這是因為使用者即時變更佈景主題時,並不會重新評估這類參考。當您要求佈景主題或系統資源時,動態資源參考會較為可靠。例外狀況是佈景主題項目本身要求另一個資源時。因為稍早所提及的原因,這些參考應該是靜態資源參考。
找不到靜態資源時的例外狀況行為會不同。如果延後資源,則會在執行階段發生例外狀況。如果未延後資源,則會在載入時發生例外狀況。
動態資源
動態資源最適合用於下列狀況:
資源的值取決於在執行階段之前未知的條件。這包括系統資源,或使用者可設定的資源。例如,您可以建立會參考系統屬性的 setter 值,而這些系統屬性是由 SystemColors、SystemFonts 或 SystemParameters 所公開。因為這些值最後是來自使用者和作業系統的執行階段環境,所以這些值是真正的動態。您可能也有可以變更的應用程式層級佈景主題,而其中頁面層級資源存取也必須擷取此變更。
您正建立或參考自訂控制項的佈景主題樣式。
您想要在應用程式存留期 (Lifetime) 內調整 ResourceDictionary 的內容。
您擁有的複雜資源結構具有相互依存性,因而可能需要向前參考。靜態資源參考不支援向前參考,但是因為動態資源在執行階段之前不需要進行評估,所以動態資源參考會支援向前參考,因此向前參考並不是相關概念。
從編譯或工作集的觀點來看,您所參考的資源特別大,所以在載入頁面時可能不會立即使用此資源。在載入頁面時,一律會從 XAML 載入靜態資源參考,但是動態資源參考在實際使用之前都不會載入。
您所建立之樣式的 setter 值可能是來自受佈景主題或其他使用者設定所影響的其他值。
將資源套用至可能會在應用程式存留期內於邏輯樹狀結構中重設父代的項目。變更父代也可能會變更資源查閱範圍,因此如果想要根據新範圍來重新評估重設父代之項目的資源,請一律使用動態資源參考。
動態資源查閱行為
如果呼叫 FindResource 或 SetResourceReference,則動態資源參考的資源查閱行為會與程式碼中的查詢行為同時進行。
查閱處理序會在資源字典內檢查要求的索引鍵,而這個索引鍵是由設定屬性的項目所定義。
查閱處理序接著會在邏輯樹狀結構中往上周遊至父項目和其資源字典。在到達根項目之前,會持續進行這個作業。
接下來,會檢查應用程式資源。應用程式資源就是資源字典內的資源,這個資源字典是由 WPF 應用程式的 Application 物件所定義。
針對目前使用中的佈景主題,檢查佈景主題資源字典。如果佈景主題在執行階段變更,則會重新評估值。
檢查系統資源。
例外狀況行為 (如果有的話) 會不同:
如果是透過 FindResource 呼叫要求資源,而且找不到資源,則會引發例外狀況。
如果是透過 TryFindResource 呼叫要求資源,而且找不到資源,則不會引發例外狀況,但是傳回的值會是 null。如果所設定的屬性不接受 null,則仍然可能會引發更進一步的例外狀況 (這取決於所設定的個別屬性)。
如果是透過 XAML 格式的動態資源參考來要求資源,而且找不到資源,則行為會取決於一般屬性系統,但是一般行為就是像在資源所在的層級上並未發生任何屬性設定作業一樣。例如,如果嘗試使用無法評估的資源來設定個別按鈕項目的背景,則不會產生任何值集,但是有效值仍然可以來自屬性系統和屬性值優先順序中的其他參與者。例如,背景值可能仍然來自本機定義的按鈕樣式,或來自佈景主題樣式。至於不是由佈景主題樣式所定義的屬性,失敗資源評估之後的有效值可能來自屬性中繼資料 (Metadata) 中的預設值。
限制
動態資源參考具有一些顯著的限制。至少必須符合下列其中一項:
所設定的屬性必須是 FrameworkElement 或 FrameworkContentElement 的屬性。該屬性必須由 DependencyProperty 支援。
所設定的屬性必須是 Freezable 的屬性,其會提供做為 FrameworkElement 或 FrameworkContentElement 屬性的值,或 Setter 值。
因為所設定的屬性必須是 DependencyProperty 或 Freezable 屬性,而且屬性系統會確認屬性變更 (變更的動態資源值),所以大部分的屬性變更都會傳播至 UI。大部分控制項包含的邏輯會在 DependencyProperty 變更而且該屬性會影響配置時,強制使用控制項的另一個配置。然而,並非所有值為 DynamicResource 標記延伸的屬性都一定會以在 UI 中即時更新屬性的方式來提供值。該功能仍然可能會根據屬性、擁有屬性的型別,甚至應用程式的邏輯結構而有所不同。
Style、DataTemplate 和隱含索引鍵
稍早已說明過,ResourceDictionary 中的所有項目都必須要有索引鍵。然而,這不表示所有資源都必須要有明確的 x:Key。當隱含索引鍵定義為資源時,有多種物件型別會支援隱含索引鍵,而且索引鍵值會繫結至另一個屬性的值。這稱為隱含索引鍵,而 x:Key 屬性是明確索引鍵。只要指定明確索引鍵,就可以覆寫任何隱含索引鍵。
其中一個非常重要的資源案例就是定義 Style 時。事實上,因為樣式基本上就是要重複使用,所以 Style 幾乎一律會定義為資源字典中的項目。如需樣式的詳細資訊,請參閱設定樣式和範本。
控制項的樣式可以同時使用隱含索引鍵進行建立和參考。而定義控制項預設外觀的佈景主題樣式會依賴隱含索引鍵。從要求隱含索引鍵的角度來看,隱含索引鍵就是控制項本身的 Type。而從定義資源的角度來看,隱含索引鍵則是樣式的 TargetType。因此,如果要建立自訂控制項的佈景主題、建立與現有佈景主題樣式互動的樣式,則不需要指定該 Style 的 x:Key 屬性。而且如果想要使用佈景主題樣式,則根本不需要指定任何樣式。例如,即使 Style 資源沒有索引鍵,下列樣式定義仍有作用:
<Style TargetType="Button">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Offset="0.0" Color="AliceBlue"/>
<GradientStop Offset="1.0" Color="Salmon"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="18"/>
</Style>
該樣式實際上具有索引鍵:隱含索引鍵 typeof(Button)。使用標記,您可以直接將 TargetType 指定為型別名稱,也可以選擇性地使用 {x:Type...} 傳回 Type。
透過 WPF 使用的預設佈景主題樣式機制,會將該樣式套用為頁面上 Button 的執行階段樣式,即使 Button 本身未嘗試指定它的 Style 屬性或樣式的特定資源參考時也是一樣。如果頁面中定義的樣式與佈景主題字典樣式使用相同的索引鍵,則在查閱順序中,頁面中定義之樣式的位置會在佈景主題字典樣式的前面。您只要在頁面的任何位置指定 <Button>Hello</Button>,則使用 Button 的 TargetType 定義的樣式就會套用至該按鈕。如果您想要避免在標記中造成混淆,仍然可以使用與 TargetType 相同的型別值來明確鍵入樣式,但這是選用作業。
如果 OverridesDefaultStyle 是 true (也請注意,OverridesDefaultStyle 可能會設定為控制項類別之原生行為的一部分,而不是明確設定於控制項的執行個體上),則控制項不會套用樣式的隱含索引鍵。此外,為了支援衍生類別案例的隱含索引鍵,控制項必須覆寫 DefaultStyleKey (所有提供為 WPF 一部分的現有控制項都會這麼做)。如需樣式、佈景主題和控制項設計的詳細資訊,請參閱設計可設定樣式控制項的方針。
DataTemplate 也有隱含索引鍵。DataTemplate 的隱含索引鍵是 DataType 屬性值。DataType 也可以指定為型別的名稱,而不是使用 {x:Type...} 明確指定。如需詳細資訊,請參閱資料範本化概觀。