1. 程式人生 > >【WPF學習】第二十八章 程式集資源

【WPF學習】第二十八章 程式集資源

  WPF應用程式中的程式集資源與其他.NET應用程式中的程式集資源在本質上是相同的。基本概念是為專案新增檔案,從而Visual studio可將其嵌入到編譯過的應用程式的EXE或DLL檔案中。WPF程式集資源與其他應用程式中的程式集資源之間的重要區別是引用他們的定址系統不同。

  在前面章節已討論過程式集資源的工作原理。因為每次編譯應用程式時,專案中的每個XAML檔案都轉換為解析效率更高的BAML檔案。這些BAML檔案作為獨立資源嵌入到程式集中。新增自己的資源同樣很容易。

一、新增資源

  可通過向專案新增檔案,並在Properties視窗中將其Build Action屬性設定為Resource來新增自己的資源。這是需要完成的全部工作——這確實是好訊息。

  為更加合理地組織資源,可在專案中建立子資料夾(在Solution Explorer中右擊專案名稱,然後選擇Add|New Folder選單項),然後使用這些子資料夾組織不同型別的資源。

  以這種方式新增的資源易於更新。只需要替換檔案並重新編譯應用程式即可。例如,可在Windows瀏覽器中將所有新檔案複製到指定資料夾中。只要替換在專案中包含的檔案的內容,就不必在Visual Studio中再採取任何其他特殊步驟(除了實際編譯應用程式外)。

  為成功地使用程式集資源,務必注意以下兩點:

  不能將Build Action屬性錯誤地設定為Embedded Resource。儘管所有程式集資源都被定義為嵌入的資源,但Embedded Resource生成操作會在另一個更難訪問的位置放置二進位制資料。在WPF應用程式中,假定總是使用Resource生成型別。

  不要將Project Properties視窗中使用Resource選項卡。WPF不支援這種型別的資源URI。

  好奇的程式設計人員自然希望瞭解嵌入到程式集中的資源到底發生了什麼變化。WPF將他們和其他BAML資源合併到單獨的流中。單獨的資源流使用以下格式命名AssemblyName.g.resources。

  如果想要實際檢視在編譯過的程式集中嵌入的資源,可使用反編譯工具。例如,使用Reflector(http://reflector.net)的更出色工具來深入挖掘資源。

  除所有影象和音訊檔案外,還可看到用於應用程式中視窗的BAML資源。在WPF中,檔案中的空格不會引起問題,因為Visual Studio足夠智慧,它能夠正確地略過他們。當應用程式被編譯過之後,你可能還會注意到檔名變成了小寫形式。

二、檢索資源

  顯然,新增資源非常容易,但到底如何使用他們呢?可以採用多種方法來使用資源。

  低階方法是檢索封裝資料的StreamResourceInfo物件,然後決定如何使用該物件。可通過程式碼,使用靜態方法Application.GetResourceStream()完成該工作。例如,下面的程式碼為winter.jpg影象獲取StreamResourceInfo物件:

StreamResourceInfo sri=Application.GetResourceStream(new Uri("image/winter.jpg",UriKind.Relative));

  一旦得到StreamResourceInfo物件,就可以得到兩部分資訊。ContentType屬性返回一個描述資料型別的字串——在該例中是image/jpg。Stream屬性返回一個UnmanagedMemoryStream物件,可使用該物件讀取資料,一次讀取一個位元組。

  GetResourceStream()的確是一個很有用的輔助方法,它封裝了ResourceManager類和ResourceSet類。這些類是.NET Framework資源體系的核心,自從.NET 1.0開始就提供了這些類。如果不使用GetResourceStream()方法,就需要具體訪問AssemblyName.g.resources資源流(這是儲存所有WPF資源的地方),並查詢所需的物件。下面是完成這一操作的非常簡單的程式碼:

Assembly assembly=Assembly.GetAssembly(this.GetType());
string resourceName=assembly.GetName().Name+".g";
ResourceManager rm=new ResourceManager(resourceName,assembly);

using(ResourceSet set=rm.GetResourceSet(CultureInfo.CurrentCulture,tur,true))
{
    UnmanagedMemoryStream s;
    s=(UnmanagedMemoryStream)set.GetOjbect("images/winter.jpg",true);
}

  通過ResourceManager類和ResourceSet類還可完成其他一些Application類自身不能完成的工作。例如,下面的程式碼片段會向你現實在AssemblyName.g.resources資源流中所有嵌入資源的名稱:

Assembly assembly=Assembly.GetAssembly(this.GetType());
string resourceName=assembly.GetName().Name+".g";
ResourceManager rm=new ResourceManager(resourceName,assembly);
using(ResourceSet set=rm.GetResourceSet(CultureInfo.CurrentCulture,true,ture))
{
    foreach(DictionaryEntry res in set)
    {
        MessageBox.Show(res.Key.ToSting());
    }
}

 雖然GetResourceStream()方法可提供幫助,但直接檢索資源還可能會遇到麻煩,問題是使用該方法得到的相對低階的UnmanagedMemoryStream物件,該物件本身沒有什麼用處,需要將它轉換成一些更有意義的資料,例如具有屬性和方法的高階物件。

  WPF提供了幾個專門使用資源的類。這些類不要求提取資源(這非常混亂且不是型別安全的),他們使用資源的名稱訪問資源。例如,如果希望在WPF的Image元素中顯示Blue.jpg影象,可使用下面的標記:

<Image Source="Images/Blue.jpg"></Image>

  注意反斜槓變成了正斜槓,因為這是WPF作用URI的約定(實際上這兩種方式都可行,但為了連貫起見,建議使用正斜槓)。

  可使用程式碼完成相同的工作。對於Image元素,只需要將Source屬性設定為BitmapImage物件,該物件使用URI確定希望顯示的影象的位置,可以像下面這樣指定完全限定的檔案路徑:

img.Source = new BitmapImage(new Uri("d:\images\winter.jpg",));

  但如果使用相對URI,就可從程式集中提取不同資源,並將他們傳遞給影象,而且不需要使用UnmanagedMemoryStream物件:

img.Source = new BitmapImage(new Uri("images/winter.jpg", UriKind.Relative));

  該技術通過在基本應用程式URI的末尾處加上images/winter.jpg構造了URI。大多數情況下不需要考慮URI語法——只要遵循相對URI,剩下的工作就由程式集負責了。然而有些情況下,更詳細理解URI系統的非常重要的,當希望訪問嵌入到另一個程式集中額資源時更是如此。

三、pack URI

  WPF使用pack URI語法定址編譯過的資源(比如用於頁面的BAML)。上一節的Image物件和標籤使用相對URI來引用資源,如下所示:

  images/winter.jpg

  這與下面更繁瑣的絕對URI是等效的:

  pack://application:,,,/images/winter.jpg

  當為一幅影象設定源時可使用這種絕對URI,儘管這種方法沒有任何優點:

img.Source = new BitmapImage(new Uri("pack://application:,,,/images/winter.jpg"));

  pack URI語法來自XPS(XML Paper Specification,XML頁面規範)標準。它看起來非常奇怪,因為它在一個URI中嵌入了另一個URI。三個逗號實際上時三個轉義的斜槓。換句話說,上面顯示的包含應用成功需URI的pack URI是以application:///開頭的。

  位於其他程式集中的資源

  使用pack URI還可檢索嵌入到另一個庫中的資源(換句話說,在應用程式中使用的DLL程式集中的資源)。這種情況下需要使用如下語言:

pack://application:,,,/AssemblyName;component/ResourceName

  例如,如果影象唄嵌入到引用的名為ImageLibrary的程式集中,將需要使用如下URI:

img.Source=new BitmapImage(new Uri("pack://application:,,,/ImageLibrary;component/images/winter.jpg"));

  或從更實用的角度看,可使用等價的相對URI:

img.Source=new BitmapImage(new Uri("ImageLibrary;component/images/winter.jpg",UriKind.Relative));

  如果使用強命名的程式集,可使用包含版本和/或公鑰標記的限定程式集引用代替程式集的名稱。使用分號隔離每段資訊,並在版本號數字之前新增字元v.下面是一個使用版本號的示例:

image.Source=new BitmapImage(new Uri("ImageLibrary;v1.25;component/images/winter.jpg",UriKind.Relative));

  下面的示例同時使用了版本號和公鑰標記:

image.Source=new BitmapImage(new Uri("ImageLibrary;v1.25;dc642a7f5bd64912;component/images/winter.jpg",UriKind.Relative));

四、內容檔案

  當嵌入檔案作為資源時,會將檔案放到編譯過的程式集中,並且可以確保檔案總是可用的。對於部署而言這是理想選擇,並且可避免可能存在的問題。然而在有些情況下,使用這種方法並不方便:

  •   希望改變資原始檔,又不想重新編譯應用程式。
  •   資原始檔非常大。
  •   資原始檔是可選的,並且可以不隨程式集一起部署。
  •   資源是聲音檔案。

  顯然,可事業能夠應用程式部署檔案,併為應用程式新增程式碼,進而從硬碟驅動器中讀取這些檔案來解決該問題。然而,WPF還有更方便的選擇,使這一過程更加容易管理。可將這些未編譯的檔案專門標記為內容檔案。

  不能將內容檔案嵌入到程式集中。然而,WPF為程式集添加了AssemblyAssociatedContentFile特性,公告每個內容檔案的存在。該特性還記錄了每個內容檔案相對可執行檔案的位置(指示內容檔案是否和可執行檔案位於同一資料夾中,或者位於某個子資料夾中)。最方便的是,當為能夠理解資源的元素(如Image類)使用內容檔案時,可使用相同的URI系統。

  為測試該技術,為專案新增聲音檔案,在Solution Exporer中選擇該檔案,並在Properties視窗中將Build Action屬性改為Content,確保將Copy to Output Directory屬性設定為Copy Always,以確保當生產專案時將聲音檔案複製到輸出目錄中。

  現在可使用相對URI,將MediaElement元素指向內容檔案:

<MediaElement Name="Sound" Source="Sounds/start.wav" LoadedBehavior="Manual"></MediaElement>