用實例講DynamicResource與StaticResource的區別

               

之前我的博客文章"WPF中的資源(Resource)"中概略性地提到過DynamicResource與StaticResource的區別。其中有這麼一句,確切地說是兩句:靜態資源在第一次編譯後即確定其對象或值,之後不能對其進行修改。動態資源則是在運行時決定,當運行過程中真正需要時,纔到資源目標中查找其值。

下面用例子更詳細地說明DynamicResource與StaticResource的區別。

先看看這段XAML代碼:
// LinearGradientBrush.xaml
<Window x:Class="BrawDraw.Com.LinearGradientBrush.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="LinearGradientBrush" Height="300" Width="300">
    <Canvas Background="{DynamicResource innerLgbResource}">
        <Canvas.Resources>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" x:Key="innerLgbResource">
                    <GradientStop Color="Yellow" Offset="0.0" />
                    <GradientStop Color="Orange" Offset="0.5" />
                    <GradientStop Color="Red" Offset="1" />
                </LinearGradientBrush>
        </Canvas.Resources>
    </Canvas>
</Window>
注意:innerLgbResource是基於Yellow, Orange, Red三種顏色的漸變。

相應的cs文件:
// LinearGradientBrush.xaml.cs
using System;
using System.Windows;

namespace BrawDraw.Com.LinearGradientBrush
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}
運行的效果:
使用元素內部的動態資源
圖1

注意XAML代碼中的這句:<Canvas Background="{DynamicResource innerLgbResource}">,Canvas的背景使用了動態資源。
如果你將它改爲<Canvas Background="{StaticResource innerLgbResource}">,將會收到錯誤提示:「StaticResource reference 'innerLgbResource' was not found.」
出現此問題的原因是:StaticResource 查詢行爲不支持向前引用,即不能引用在引用點之後才定義的資源
DynamicResource可以向前引用,即DynamicResource運行時才查找並加載所定義的資源。

接下來我們來「變變花樣」。
先在App.xaml中加入應用程序級資源:
<Application x:Class="BrawDraw.Com.LinearGradientBrush.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="LinearGradientBrush.xaml">
    <Application.Resources>
        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" x:Key="appLgbResource">
            <GradientStop Color="Beige" Offset="0.0" />
            <GradientStop Color="Red" Offset="0.3" />
            <GradientStop Color="Yellow" Offset="0.5" />
            <GradientStop Color="Green" Offset="0.75" />
            <GradientStop Color="Orange" Offset="1" />
        </LinearGradientBrush>
        <Style TargetType="Canvas">
            <Setter Property="Background" Value="{StaticResource appLgbResource}">
            </Setter>
        </Style>
    </Application.Resources>

</Application>
注意<Application.Resources>...</Application.Resources>之間的部分。這裏使用了從Beige, Red, Yellow, Green到Orange五種顏色的漸變。同時,將Canvas的背景屬性使用Style/Setter的方式設置爲這五種給定的漸變色。由於此五種顏色是一次性設置,之後不再改變,所以使用了StaticResource。

[討論]
可以使用DynamicResource嗎?
比如:<Setter Property="Background" Value="{DynamicResource appLgbResource}">
答案是:可以!

然後將LinearGradientBrush.xaml中<Canvas Background="{DynamicResource innerLgbResource}">這句改成:
<Canvas Background="{StaticResource appLgbResource}">,運行結果:
應用程序級資源
圖2

接着試驗:
(1)將<Canvas Background="{StaticResource appLgbResource}">改成:<Canvas Background="{DynamicResource appLgbResource}">試試,效果與圖2一樣!
(2)將<Canvas Background="{...}">中Background屬性去掉,改成:<Canvas>,運行效果也與圖2一致。
(3)如果改成:<Canvas Background="{DynamicResource innerLgbResource}">時,則顯示圖1所示基於基於Yellow, Orange, Red三種顏色的漸變效果。
(4)你甚至可以這樣:
App.xml中使用<Setter Property="Background" Value="{DynamicResource appLgbResource}">,而在LinearGradientBrush.xaml中使用<Canvas Background="{StaticResource appLgbResource}">(運行效果如圖2)。

探討:
1、當引用資源時,選擇StaticResource還是DynamicResource的考慮因素:
(1)在哪裏創建資源?(資源的範圍或層級
a. 資源是在一個Page/Canvas/Window中?
b. 在應用程序範圍中?
c. 在鬆散的Xaml中?
d. 在某個特定的Object(比如某個特定的Button)中?
物件級:此時,資源只能套用在這個Object物件,或套用至該物件的子物件。
文件級:如果將資源定義在Window或Page層級的XAML檔中,那麼可以套用到這個文件中的所有物件。
應用程序級:如果我們將資源定義在App.xaml 中,那麼,就可以將資源套用到應用程序內的任何地方。
字典級:當我們把資源封裝成一個資源字典, 定義到一個ResourceDictionary的XAML文件時,就可以在另一個應用程序中重複使用。

(2) 應用程序的功能:是否在運行時改變資源?
如果需要改變,則使用DynamicResource。
(3) 每個資源引用類型不同的尋找行爲。(需要支持向前引用嗎?)

StaticResources的適用場合:
(1)在資源第一次引用之後無需再修改資源的值。
(2)資源引用不會基於運行時的行爲進行重新計算,比如在重新加載Page/Window的時候。
(3)當需要設置的屬性不是DependencyObject或Freezable類型的時候,用StaticResource。
(4)當需要將資源編譯到dll中,並打包爲程序的一部份,或者希望在各應用程序之間共享時,也使用StaticResource。
(5)當需要爲一個自定義控件創建一個Theme,並Theme中使用資源,就需要使用StaticResource。因爲StaticResource的資源查找行爲時可預測的,並且本身包含在Theme中。而對於DynamicResource,即使資源是定義在Theme中,也只能等到運行時確定,導致一些可能意料不到的情況發生。
(6)當需要使用資源設置大量的依賴屬性(Dependency Property)的時候。
由於依賴屬性具有屬性系統提供的值緩存機制,所以,如果能在程序裝載時設置依賴屬性的值,這樣,依賴屬性就不需要檢查自己的值並返回最後的有效值了。
 
Dynamic Resource一般使用在如下場合:
(1)資源的值依賴一些條件,而該條件直到運行時才能確定。
包括系統資源,或是用戶可設置的資源。比如:可以創建引用系統屬性諸如SystemColors,SystemFonts來設置值,而這些屬性是動態的,它們的值又來自於運行環境和操作系統。
(2)爲自定義控件引用或創建Theme Style。
(3)希望在程序運行期間調整資源字典的內容時。
(4)希望資源可以向前引用時(如上面在Canvas中引用innerLgbResource一樣)
(5)資源文件很大,希望在運行時才加載。
(6)要創建的Style的值可能來自於其它值,而這些值又依賴於Theme或用戶的設置。
(7)當引用資源的元素的父元素有可能在運行期改變,這個時候也需要使用動態資源。因爲父元素的改變將導致資源查詢的範圍。
Dynamic resource的限制條件:屬性必須是依賴屬性,或是Freezable的。

資源的查詢方式
Static Resource的查詢
(1)查找使用該資源的元素的Resource字典;
(2)順着邏輯樹向上查找父元素的資源字典,直到根節點;
(3)查找Application資源;
(4)不支持向前引用,即:不能引用在引用點之後才定義的資源。

Dynamic Resource的查詢
(1)查找使用該資源的元素的Resource字典;
如果元素定義了一個Style 屬性,將查找Style中的資源字典;如果元素定義了一個Template屬性,將查找FrameworkTemplate中的資源字典。
(2)順邏輯樹向上查找父元素的資源字典,直到根節點;
(3)查找Application資源;
(4)查找當前**狀態下的Theme資源字典;
(5)查找系統資源。