WPF容許在代碼中以及在標記中的各個位置定義資源(和特定的控件、窗口一塊兒定義,或在整個應用程序中定義)。編程
資源具備許多重要的優勢,以下所述:app
1、資源集合字體
每一個元素都有Resources屬性,該屬性存儲了一個資源字典集合(他是ResourceDictionary類的實例)。資源集合可包含任意類型的對象,並根據字符串編寫索引。this
儘管每一個元素都提供了Resources屬性(該屬性做爲FrameworkElement類的一部分定義),但一般在窗口級別定義資源。這是由於每一個元素均可以訪問各自資源集合中的資源,也能夠訪問全部父元素的資源集合中的資源。spa
例如,分析下圖中顯示的包含三個按鈕的窗口。其中的兩個按鈕使用了相同的畫刷——繪製笑臉圖像的平鋪式的圖像畫刷。操作系統
在該例中,顯然但願頂部和底部的兩個按鈕具備相同的樣式。不過,之後可能但願改變圖像畫刷的特徵。所以,在窗口的資源中定義圖像畫刷並在須要時重用該畫刷是合理的。3d
下面的標記顯示瞭如何定義畫刷:code
<Window.Resources> <ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush> </Window.Resources>
爲使用XAML標記中的資源,須要一種引用資源的方法。這是經過標記擴展完成的。實際上有兩個標記擴展可提供使用:一個用於動態資源,另外一個用於靜態資源。靜態資源在首次建立窗口時一次性地設置完畢。而對於動態資源,若是發生了改變,就會從新應用資源。在該例中,圖像畫刷永遠不會改變,因此使用靜態資源是合適的。orm
下面是一個使用該資源的按鈕:xml
<Button Background="{StaticResource TileBrush}" Padding="5" FontWeight="Bold" FontSize="14" Margin="5" >A Tiled Button</Button>
上面的代碼檢索資源並將資源指定給Button.Background屬性。可以使用動態資源執行相同的操做(但開銷稍大些):
<Button background="{DynamicResource TileBrush}"></Button>
2、資源的層次
每一個元素都有本身的資源集合,爲了找到指望的資源,WPF在元素樹中進行遞歸搜索。在當前示例中,可將圖像畫刷從窗口的資源集合移到包含這三個按鈕的StackPanel面板的資源集合中,而沒必要改變應用程序的工做方式。也可將圖像畫刷放到Button.Resources集合中,不過,須要定義畫刷兩次——爲每一個按鈕分別定義一次。
須要考慮的另外一個問題是,當使用靜態資源時,必須老是在引用資源以前的標記中定義資源。這意味着儘管從標記角度看,將Window.Resources部分放在窗口的主要內容(包含全部按鈕的StackPanel面板)以後是徹底合法的,但這會破壞當前示例。當XAML解析器遇到它不知道的資源的靜態引用時,會拋出異常(但是使用動態資源避免這一問題,但不必增長額外的開銷)。
所以,若是但願在按鈕元素中放置資源,須要稍微從新排列標記,從而在設置背景以前定義資源。下面的實現該操做的一種方法:
<Button Margin="5" Padding="5" FontWeight="Bold" FontSize="14"> <Button.Resources> <ImageBrush x:Key="TileBrush1" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush> </Button.Resources> <Button.Background> <StaticResource ResourceKey="TileBrush1"/> </Button.Background> <Button.Content>Another Tiled Button</Button.Content> </Button>
這個示例中的靜態資源標記擴展語法稍有不一樣,由於資源被放在嵌套元素中(而不是特性中),爲指向正確的資源,使用ResourceKey屬性指定資源鍵。
有趣的是,只要不在同一集合中屢次使用相同的資源名,就能夠重用資源名稱。這意味着可以使用以下所示的標記建立窗口,該標記在兩個地方建立圖像畫刷。
<Window x:Class="Resources.TwoResources" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="TwoResources" Height="300" Width="300"> <Window.Resources> <ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush> </Window.Resources> <StackPanel Margin="5"> <Button Background="{StaticResource TileBrush}" Padding="5" FontWeight="Bold" FontSize="14" Margin="5" >A Tiled Button</Button> <Button Padding="5" Margin="5" FontWeight="Bold" FontSize="14">A Normal Button</Button> <Button Padding="5" Margin="5" FontWeight="Bold" FontSize="14" > <Button.Resources> <ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 10 10" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush> </Button.Resources> <Button.Background> <StaticResource ResourceKey="TileBrush" /> </Button.Background> <Button.Content>Another Tiled Button</Button.Content> </Button> </StackPanel> </Window>
效果圖以下所示:
在上面的代碼中,按鈕使用找到的第一個資源。由於是從本身的資源集合開始查找,因此第二個按鈕使用按鈕內部定義的資源,而第一個按鈕從包含窗口獲取畫刷。
3、靜態資源和動態資源
由於上面的示例使用了靜態資源(在該例中是圖形畫刷),因此你可能認爲對於資源的任何改變都不會有什麼反應。然而,事實並不是如此。
例如,設想在應用了資源而且顯示了窗口以後,執行下面的代碼:
ImageBrush brush = (ImageBrush)this.Resources["TileBrush"]; brush.Viewport = new Rect(0, 0, 5, 5);
上面的代碼從Window.Resources集合中檢索畫刷,並對它進行操做(從技術角度看,代碼改變了每一個平鋪圖像的尺寸,縮小了笑臉圖像並壓縮圖像模式使其更加緊湊)。當運行此代碼時,可能不但願用戶界面有任何反應——畢竟,它是靜態資源。但這一變化會傳播給兩個按鈕。實際上,會使用新設置的Viewport屬性進行更新,而無論是經過靜態資源仍是動態資源使用畫刷。
這是由於Brush類繼承自Freezable類。Freezable類有一個基本的變化跟蹤特性(若是不須要改變,能被「凍結」爲只讀狀態)。這意味着,不管什麼時候在WPF中改變畫刷,全部使用該畫刷的控件都會自動更新。控件是不是經過資源獲取其畫刷可有可無。
如今,你可能想弄清楚靜態資源和動態資源之間到底有什麼區別。區別在於靜態資源只從資源集合中獲取對象一次。根據對象的類型(以及使用對象的方式),對象的任何便哈均可能被當即注意到。然而,動態資源在每次須要對象時都會從新從資源集合中查找對象。這意味着可在同一鍵下放置一個全新對象,並且動態資源會應用該變化。
下面經過一個示例演示它們之間的區別,分析下面的代碼,這段代碼用全新的(而且有些乏味的)存藍色畫刷替換了當前的圖像畫刷:
this.Resources["TileBrush"] = new SolidColorBrush(Colors.LightBlue);
動態資源會應用該變化,而靜態資源不知道它的畫刷已在Resources集合中被其餘內容替換了,它仍然繼續使用原來的ImageBrush。
下圖顯示了該示例,該窗口包含動態資源(頂部按鈕)和靜態資源(底部資源)。
一般不須要使用動態資源,使用靜態資源應用程序也可以很完美地工做。建立依賴於Windows設置(例如系統顏色)的資源明顯屬於例外狀況,對於這種狀況,若是但願可以響應當前顏色方案的任何改變,就須要使用動態資源(不然,若是使用靜態資源,將仍使用原來的顏色方案,直到用戶從新啓動應用程序爲止)。在稍後介紹系統資源時,將討論動態資源工做原理的更多相關內容。
做爲通常規則,只有在下列狀況下才須要使用動態屬性。
然而,不該該過分使用動態資源。主要問題是對資源的修改未必會觸發對用戶界面的更新(在畫刷示例中,由於構造畫刷對象的方式——畫刷具備內置的通知支持,確實更新了用戶界面)。許多狀況下,須要在控件中顯示動態內容,並且控件須要隨着內容的改變調整自身。對於這種狀況,使用數據綁定更合理。
4、非共享資源
一般,在多個地方使用某種資源時,使用的是同一個對象實例。這種行爲——稱爲共享——一般這也正是所但願的。然而,也可能但願告訴解析器在每次使用時建立單獨的對象實例。
爲關閉共享行爲,須要使用Shared特性,以下所示:
<ImageBrush x:Key="TileBrush" x:Shared="False" ...></ImageBrush>
不多有理由須要使用非共享的資源。若是但願之後分別修改資源實例,可考慮使用非共享資源。例如,可建立包含幾個使用同一畫刷按鈕的窗口,並關閉共享行爲,從而能夠分別改變每一個畫刷。因爲效率底下,這種方式不常見。在這個示例中,開始時告訴全部按鈕使用同一個畫刷,當須要時在建立並應用新的畫刷,這樣可能更好。這樣,只有當肯定須要時才承擔額外的畫刷對象開銷。
使用非共享資源的另外一個緣由是,可能但願以一種本來不容許的方式重用某個對象。例如,使用該技術,可將某個元素(如一幅圖像或一個按鈕)定義爲資源,而後再窗口的多個不一樣位置顯示該元素。
一樣,一般這不是最佳方法。例如,若是但願重用Image元素,再合理的作法是存儲相關信息(例如,用於指定圖像源的BitmapImage對象)並在多個Image元素之間共享。若是隻是但願標準化控件,讓他們共享相同的屬性,最好使用樣式。經過樣式可爲任意元素建立相同或幾乎相同的副本,當屬性值尚未被應用時,能夠重用他們並且能夠關聯不一樣的事件處理程序。若是簡單地使用非共享資源克隆元素,就會丟失這兩個特性。
5、經過代碼訪問資源
一般在標記中定義和使用資源。若有必要,也可在代碼中使用資源集合。
正如已經看到的,可經過名稱從資源集合中提取資源。爲此,須要使用正確元素的資源集合。如前所述,對於標記沒有這一限制。控件(如按鈕)可以檢索資源,而不須要知道定義資源的確位置。當嘗試爲Background屬性指定畫刷時,WPF會在按鈕的資源集合中檢索名爲TileBrush的資源,而後檢查包含StackPanel的資源集合,接下來檢查包含窗口(這個過程實際上海會繼續檢查應用程序資源和系統資源)。
可以使用FrameworkElement.FindResource()方法以相同的方式查找資源。下面是一個示例,當引起Click事件時,會查找按鈕資源(或它的一個更高級的容器):
private void cmdChange_Click(object sender,RoutedEventArgs e) { Button cmd=(Button)sender; ImageBrush brush=(ImageBrush)sender.FindResource("TileBrush"); ... }
可以使用TryFindResource()方法代替FindResource()方法,若是找不到資源,該方法會返回null引用,而不是拋出異常。
此外,還可經過編寫代碼添加資源。選擇但願放置資源的元素,並使用資源集合的Add()方法。然而,一般在標記中定義資源。
6、應用程序資源
窗口不是查找資源的最後一站。若是在控件或其容器中(直到包含窗口或頁面)找不到指定的資源,WPF會繼續檢查爲應用程序定義的資源集合。在Visual Studio中,這些資源是在App.xaml文件的標記中定義的資源,以下所示:
<Application x:Class="Resources.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="Menu.xaml"> <Application.Resources> <ImageBrush x:Key="TileBrush" x:Name="DynamicBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush> </Application.Resources> </Application>
應用程序資源爲在整個應用程序中重用對象提供了一種極佳的方法。在這個示例中,若是計劃在多個窗口中使用圖像畫刷,這是一種很好的選擇。
當某個元素查找資源時,應用程序資源仍然不是最後一站。若是沒有在應用程序資源中找到所需的資源,元素還會繼續查找系統資源。
7、系統資源
動態資源主要用於輔助應用程序對系統環境設置的變化作出響應。但這會致使一個問題——開始時如何檢索系統環境設置並在代碼中使用它們。
爲此須要使用三個類,分別是SystemColors、SystemFonts和SystemParameters,這些類都位於System.Windows名稱空間中。SystemColors類用於訪問顏色設置;SystemFonts類用於訪問字體設置;而SystemParameters類封裝了大量的設置列表,這些設置描述了各類屏幕元素的標準尺寸、鍵盤和鼠標設置、屏幕尺寸以及各類圖形效果(如熱跟蹤、陰影以及當拖動窗口時顯示窗口內容)是否已經打開。
SystemColors、SystemFonts和SystemParameters類經過靜態屬性公開了它們的全部細節。例如,SystemColors.WindowTextColor屬性提供了Color結構,您可方便地使用該結構。下面的示例使用該屬性建立一個畫刷,並填充元素的前景色:
label.Foreground=new SolidBrush(SystemColors.WindowTextColor);
或者爲了提升效率,可以使用現成的畫刷屬性:
label.Foreground=SystemColors.WindowTextBrush;
在WPF中,可以使用靜態標記擴展訪問靜態屬性。例如,下面的標記演示瞭如何使用XAML設置同一標籤的前景色:
<Label Foreground="{x:Static SystemColors.WindowTextBrush}"> Ordinary Text </Label>
上面的示例沒有使用資源,這可能會引起一個小問題——當解析窗口並建立標籤時,會根據當前窗口文本顏色的「快照」建立畫刷。若是在應用程序運行時(在顯示了包含標籤的窗口後)改變了Windows顏色,Label控件不會更新自身。具備這種行爲的應用程序被認爲是不太合理的。
爲解決這個問題,不能將Foreground屬性直接設置爲畫刷對象,而是須要將它設置爲封裝了該系統資源的DynamicResource對象。幸運的是,全部SystemXxx類都提供可返回ResourceKey對象引用的補充屬性集,使用這些引用可從系統資源集合中提取資源。這些屬性與直接返回對象的普通屬性同名,後面加上單詞Key。例如,SystemColors.WindowTextBrush的資源鍵時SystemColors.WindowTextBrushKey。
下面的標記顯示瞭如何使用來自SystemXxx類的資源:
<Label Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"> Ordinary Text </Label>
上面的標記比前面的示例複雜一些。首先定義了一個動態資源,但該動態資源不是從應用程序的資源集合中提取資源,而是使用了一個由SystemColors.WindowTextBrushKey屬性定義的鍵。該屬性是靜態屬性,所以還須要使用靜態標記擴展,從而讓解析器理解正在嘗試執行什麼操做。
現已完成修改,當系統設置變化時,Label控件可以無縫地更新自身。