WP7 - TitleContainer.OpenStream與App.GetResourceStream - 讀取XAP內資源的方法

Windows Phone 7 – TitleContainer.OpenStream與App.GetResourceStream - 讀取XAP內資源的方法

在撰寫這篇文章之前,也許你可以想一下,在學習撰寫WP7程式的時候,你有聽過Isolated Storage,

知道Isolated Storage是Silverlight特殊的一個儲存媒介,裡面可放置任何的資料類型,只要不超過指定的大小限制。

到此,這些是在學習Silverlight寫WP7程式會有的觀念,但也許你心裡會有一個跟我一樣的疑問:

「如果專案檔中增加一個文字檔或XML檔,但不是要在程式產生時才加入到Isolated Storage中;

要直接使用,那我該怎麼取得檔案的內容?」

 

這樣的問題一直是我想搞清楚的,因此,下列將介紹幾個我閱讀過可以直接處理上述需求的方法:

*往下說明之前,要先了解以下所舉的範例與說明,均是以要讀取的資源已經被放置於XAP檔中,

並且該資源的Build Action屬性也設定成Content

至於資源檔該選擇「Resource」或「Content」,這二者的差異,可以先參考該文章的說明:

<WP7 Development Tip of the Day: Understanding the difference between content and resources>

 

App.GetResourceStream(Uri)

“Returns a resource file from a location in the application package.”該段所指的application package就是封裝

所有資源與Silverlight執行.dll檔的XAP檔案。然而,該方法指定的Uri參數,使用的是相對的路徑,它自動

由根目錄(Root)直接往下延伸,因此,假設XAP檔中有一資料夾Resources裡有一個圖片MyPhoto.jpg,那該

Uri的寫法為:

 

1: App.GetResourceStream(new Uri("Resources/MyPhoto.jpg", UriKind.Relative); 

 

但根據MSDN上的說明,其實GetResourceStream支援三種不同的地點:

(1) Embedded in the application assembly in the application package.

(2) Embedded in a library assembly that is included in the application package.

(3) Included in the application package.

 

以上三個地點其實不難了解,只要能包裝在XAP裡,不管是被嵌入至Assembly檔(.dll)的資源一樣可以使用,

只需要更換相關的Uri路徑,例如:假設XAP檔中有一圖片MyPhoto.jpg,Build Action選擇Resource,那Uri要改成:

 

1: //[ApplicationName]代表該WP7程式的名稱(Assembly檔案名)
2: App.GetResourceStream(new Uri("[ApplicationName];component/Resources/MyPhoto.png", UriKind.Relative);

=>「;component」這個是根目錄,代表由Assemply往下的嵌入元素;

 

TitleContainer Class

該類別被定義於「Microsoft.XNA.Framework」中,專用於協助XNA撰寫讀取指定資源的方法。常見的範例,

就是透過TitleContainer讀取XNA Game中的音樂檔或圖片檔。例如:<Streaming Data from a WAV File>。

“Provides file stream access to the title's default storage location.”這一段是MSDN對於TitleContainer的定義,

然而「storage location」指的是否與上述App.GetResourceStream中讀取的地點是一樣的呢?讓我們往下看:

根據MSDN針對XNA 4.0說明:<What Is Storage?>一文中,可了解到:

 

「Storage location所指的是被用於讀取/寫入資料的記憶體區域。」,由二個部分組成

 

a. Title storage -

該區域是WP7預設遊戲存取空間(default game storage location),本身屬於唯讀(Read-Only)空間,內容包括:

儲存了主要可執行的程式和支援檔案(圖檔、字型、素材等)。

要存取Title storage中的內容,分成2個方法:

 

a-1. Content Pipeline

通常情況下,管理遊戲使用到的素材與資源都是透過Content Pipeline來負責,常見的例子就是XNA Game Studio

即是透過Content Pipeline來管理製作遊戲時所使用的資產,例如:字型、圖形元件、遊戲素材等。

為何會需要透過Content Pipeline呢?主要因為XNA不允許直接操作這些資產,因此需要透過Content Piepline在

Build-time時進行Loaded與Unloaded的任務。詳細Content Pipeline的操作可以參考:<Adding Content to a Game>。

至於要怎麼使用Content Pipeline前,我建議先了解一下Content在XNA Game Studio代表的是何種意義會比較好。

建議可以先閱讀<  XNA-Content Pipeline - help- 點部落  >了解Content importer與Content processor的不同。

 

a-2. File Stream Access

該方法是支援上述Content Piepline不能處理的例外情形,例如:當遊戲過程中需要管理特定的遊戲資料或資源時,

就可以簡單透過File Stream Access的方法來方便處理。不過,在開發XNA上是建議使用Content Pipeline來處理所有遊

戲資源。該方法也支援程式運行時,直接透過TitleContainer存取Title Storage裡的資料。

那該怎麼存取呢?可以參考下方的二個連結:

Adding Game Data Files to Title Storage

Reading Game Data from Title Storage (透過TitleContainer.OpenStream儲存Title storage中Ready-only的資料)

 

 

b. User storage -

該存取區域屬於一個可供讀寫(Read-Write)的範圍,支援遊戲在執行期間可存取遊戲的資料,然而這些資料的存取權限,

可随需求,限制於特定的遊戲玩家或全部的玩家。

Windows Phone中儲存這範圍的方法,由於XNA Game Studio沒有提供直接存取該空間的功能,因此,需透過Silverlight

中的(System.IO.IsolatedStorage)來處理Isolated Storage

[注意]

在使用IsolatedStorage時,常見會透過「IsolatedStorageFile.GetUserStoreForApplication」取得IsolatedStorage Object,

但該指令如果用於XNA遊戲是Run在Windows平台上時,將會出現「InvalidOperationException」,為了避免這樣的錯誤,

需改成「GetUserStoreForDomain」來取得Object。如需發開發跨平台遊戲,需必讀該篇文章<conditional compilation symbols>,

其中一個重要的指令如下:

5:  
1: #if WINDOWS
2:  
3: // Execute code that is specific to Windows
4:     IsolatedStorageFile savegameStorage = IsolatedStorageFile.GetUserStoreForDomain();
6: #elif XBOX
7:  
8: // Execute code that is specific to Xbox 360
9:     IsolatedStorageFile savegameStorage = IsolatedStorageFile.GetUserStoreForApplication();
10:  
11: #elif WINDOWS_PHONE
12:  
13: // Execute code that is specific to Windows Phone
14:    IsolatedStorageFile savegameStorage = IsolatedStorageFile.GetUserStoreForApplication();
15:  
16: #else
17:  
18: // Print a compile-time error message
19: #error The platform is not specified or is unsupported by this game.
20:  
21: #endif

 

在操作User Storage時, 如果開發的對象是非Windows Phone版本(如:Xbox 360, Windows 7, Windows Vista, Windows XP),

可以透過二個步驟來進行存取:

(1) 取得StorageDevice物件來連結指定的User storage device

StorageDevice代表User storage device:可能是Memory Unit(記憶卡)或是硬碟(Hard device)。

透過BeginShowSelector方法彈出個Dialog Box呈現目前該XNA遊戲支援的Storage Device清單,該方法屬於非同步(asynchronously)模式

當用戶選擇完畢後,會觸發一個IAsyncResult或Callback方法,進行接下來要完成取得Device的功能。由於該方法是屬於非同步運作,

可透過確認該IAsyncResult是否被完成與使用EndShowSelector二個方法的合作,取得StorageDevice物件。如下程式碼:

1: //在公用變數區中宣告一個IAsyncResult變數來擷取當BeginShowSelector完成後回傳的事件
2: private IAsyncResult result;
3:  
4: public Game1(){ ... 內容省略 }
5:  
6: protected override void Update(GameTime gameTime)
7: {
8:     if (btnChoiceDevice == ButtonState.Pressed) {
9:         //使用BeginShowSelector進行StorageDevice的選擇
10:         result = StorageDevice.BeginShowSelector(
11:                  PlayerIndex.One, null, null);
12:     }
13:  
14:     //如果用戶選擇完畢之後該屬性會變成true    
15:     if (result.IsCompleted)
16:     {
17:         //取得StorageDevice物件
18:         StorageDevice device = StorageDevice.EndShowSelector(result);        
19:     }
20:    
21:     base.Update(gameTime);
22: }

更詳細的操作說明,可以參考該篇文章<Getting a StorageDevice Asynchronously>,裡面也含有完整的Sample code。

 

(2) 連結User storage device進行資料的讀寫

取得StorageDevice物件之後,接下來透過StorageDevice物件的BeginOpenContainer方法開啟該Device的儲存集合,進行與StorageDevice物件連結。

在完成StorageDevice資料的存取之後,需再補上一個EndOpenContainer方法來結束連結。然後在操作Device中的資料,是透過一個重要的類別:

StorageContainer Class」。StorageContainer是用來連結Device與操作Device中的所有資源,因此,使用前需建立一個StorageContainer物件:

1: // Open a storage container.
2: IAsyncResult result =
3:     device.BeginOpenContainer("StorageDemo", null, null);
4:  
5: // Wait for the WaitHandle to become signaled.
6: result.AsyncWaitHandle.WaitOne();
7:  
8: StorageContainer container = device.EndOpenContainer(result);
9:  
10: // Close the wait handle.
11: result.AsyncWaitHandle.Close();

然而,StorageContainer提供了熟悉操作檔案系統的功能,包括:

http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.storage.storagecontainer_members.aspx 

最常使用的就是讀取檔案的功能:OpenFile()。它回傳值為Stream,可參考以下程式片段:

1: //取得檔案內容
2: Stream stream = container.CreateFile(filename);
3:  
4: //XML檔案一樣可以透過這樣的方式取得
5: XDocument tXDocument = XDocument.Load(Stream);

更詳細的操作範例可以參考<Saving Data to a Save Game File>(一樣有Sample Code)。

 

====

至於為何會談論到TitleContainer呢?

只要是因為我在WP7想要讀取自己加入的XML內容,使用的是XDocument類別,但在網路上看到這麼一篇文章:

<XDocument versus TitleContainer / StorageContainer>。該篇文章是在說明,該作者使用XDocument來儲存相關玩家

在WP7上遊戲的記錄,該作法可以運作於所有的平台上,然而看到一篇有談論相關在開始XBOX 360時建議使用

TitleContainer/StorageContainer來讀取相關XML的內容。

 

看完App.GetResourceStream與TitleContainer二個類別之後,也許你心中有個疑問:「到底我該使用那一種呢?」,

簡單的分類來說:如果今天開發的是屬於Silverlight for WP7的話,建議使用App.GetResourceStream

因為,當開發Silverlight for WP7的程式時,引入Microsoft.XNA.Framework參考時,自動會彈出提示訊息告訴你,

使用XNA的參考在Silverlight程式中,可能會出現一些不可預期的例外。

另外,由於我們操作的資源與XNA使用的資源比較不相似,也無需管理XNA針對畫面處理等相關的問題,因此,

建議使用App.GetResourceStream即可。

但是如果今天要存取的資料是XML,那可以直接透過XDocument.Load()(System.Xml.Ling)來取得資料即可。如下範例:

1: //假設Silverlight for Windows Phone專案
2: //有一個資料夾:Resourcess;其中有一個檔案MyProfile.xml;路徑.. Resources/MyProfiles.xml
3:  
4: //讀取XAP中的XML檔案
5: XDocument tXDoc = XDocument.Load("Resources/MyProfile.xml");

至於要存取Isolated Storage中的XML內容,可以參考<HOW TO:使用 LINQ to XML 對隔離儲存區進行儲存和載入>。

 

以上是介紹存取XAP的方法,希望對大家有些幫助,在於XNA相關的部分可能介紹的不是非常詳細,

因為我本身還沒有開發過相關XNA的程式也研究的不深,如有遺漏或寫錯的地方請指導我一下。非常感謝

 

References:

[Silverlight][Windows Phone 7] 模擬器中將測試圖片存入圖庫 & Silverlight从DLL中读取XAML

5+ Ways to Reduce your .Xap Size - 了解XAP檔案內容必讀

How to use TitleContainer in XNA

Windows Phone 7-下載檔案至Isolated Storage

binding a Linq datasource to a listbox

Are there any best practices for app configuration in wp7

Modern App Design

HOW TO:使用 LINQ to XML 對隔離儲存區進行儲存和載入

Benefits of the Content Pipeline

Adding Content to a Game

WP7 Isolated Storage详解(8)-读取、保存二进制文件 (重要)

 

Dotblogs 的標籤: ,