mikeo_410


Silverlightのビルドアクションとリソース

  Silverlightでは、ビルドの結果として .xapファイルを作ります。これは、リンカの出力の.EXEやDLLではなく、フォルダを圧縮したパッケージです。
  .EXEを実行する場合のカレントディレクトリを圧縮してパッケージとしてブラウザからダウンロードして使います。
  リンカの出力自体は、パッケージの中にDLLとして含まれます。

  Visual Studio のプロジェクトに含まれるファイルのプロパティには、「ビルドアクション」、「カスタムツール」があります。
  .csなどのプログラムはコンパイルされDLLに含まれますが、イメージやデータファイルはビルドアクションで扱いが変わります。ビルドアクションのうち、「コンテンツ」と「埋め込まれたリソース」、「Resource」の違いを理解したいと思います。 

  プロジェクトに、xaml ファイルを追加して、「ビルドアクション」、「カスタムツール」を変えて、実行時にどう見えるか確認します。
  ビルドアクションは、「コンテンツ」と「埋め込まれたリソース」、「Resource」のいずれかにします。カスタムツールは「MSBuild:Compile」か空白にします。「カスタムツール」は良くわかりませんが、XAMLはコンパイルの対象に成り得るものなので、コンパイル結果が格納されるものかどうかが分かればと考えました。

プロジェクトは、左図の構成にしました。
各xamlファイルは、ほぼ同じ内容です。TextBlockコントロールのTextにファイル名が設定してあり、それだけが異なっています。

  1. <Grid
  2.     xmlns="http://..."
  3.     xmlns:x="http://...">
  4.     <TextBlock Text="コンテンツ_コンパイル"/>
  5. </Grid>

パッケージの内容

  これをビルドすると、SilverlightApplication6.xap ができます。
  これはフォルダを圧縮したものなので、解凍すると以下のフォルダが含まれていることが分かります。

  1. AppManifest.xaml
  2. SilverlightApplication6.dll
  3. コンテンツ.xaml
  4. コンテンツ_コンパイル.xaml

   ビルドアクションが「コンテンツ」の場合、そのままパッケージに含まれることが分かりました。
  「埋め込まれたリソース」、「Resource」の場合は、DLLに含まれることになります。(AppManifest.xaml は、リソースの追加に関係なく同じ内容のテキストが格納されています。)

リソースの一覧

  リソースがプログラムからどのように見えるのか調べます。まず、一覧する方法を見ます。

  1. // マニフェストリソースの一覧
  2. Assembly asm = Assembly.GetExecutingAssembly();
  3. string[] sa = asm.GetManifestResourceNames();
  4. foreach (string s in sa)
  5.     Debug.WriteLine("ManifestResource : " + s);
  6. // リソースマネージャによる一覧
  7. ResourceManager rm = new ResourceManager(
  8.     "SilverlightApplication6.g", asm);
  9. Stream stream = rm.GetStream("app.xaml");
  10. ResourceSet rs 
  11.     = rm.GetResourceSet(
  12.         System.Threading.Thread.CurrentThread.CurrentCulture,
  13.         false, true);
  14. foreach (object r in rs)
  15.     Debug.WriteLine("ResourceManager : " 
  16.                 + ((DictionaryEntry)r).Key);

  実行すると以下の出力になります。

  1. ManifestResource : SilverlightApplication6.g.resources
  2. ManifestResource : SilverlightApplication6.埋め込まれたリソース_コンパイル.xaml
  3. ManifestResource : SilverlightApplication6.埋め込まれたリソース.xaml
  4. ResourceManager : resource_%e3%82%b3%e3%83%b3%e3%83%91%e3%82%a4%e3%83%ab.xaml
  5. ResourceManager : resource.xaml
  6. ResourceManager : mainpage.xaml
  7. ResourceManager : app.xaml

  GetManifestResourceNames()で得られるのは、「埋め込まれたリソース」でした。
  また、ResourceManager で使用している、"SilverlightApplication6.g"は、GetManifestResourceNames()で得られる SilverlightApplication6.g.resources から来ていて、マニフェストリソースは、ResourceManager が管理するリソースを一括して扱っていることが分かります。

  ResourceManager が扱うリソース名は、URLエンコードが必要で、全て小文字にする必要がありました。

カスタムツール

  カスタムツールを「MSBuild:Compile」にしても、空白にしても何の差異もないようです。
  少なくとも、リソースはStreamでアクセスしますが、Streamのサイズと、インスタンスする方法には差がありません。
  また、ResourceManager で表示する一覧には、app.xaml、mainpage.xaml が含まれ、読み出すこともできました。
  ビルドアクションがPageのプログラムの一部のXAMLもそのままテキストで保持されているもののようです。

リソースのアクセス

  想定したカスタムツールの設定は結果に影響しないことが分かったので、_コンパイルを付加した名前のリソースは除外して記します。
  これらのリソースも、同じ方法でインスタンスできることは確かめました。

mainpage.xaml を読み込む

  少し外れますが、加えたリソースではなく、MainPage.xaml を読み込んでみます。

  1. ResourceManager rm = new ResourceManager(
  2.     "SilverlightApplication6.g", asm);
  3. Stream mainpage = rm.GetStream("mainpage.xaml");
  4. Debug.WriteLine((new StreamReader(mainpage)).ReadToEnd());

  前述のように ResourceManager で表示する一覧にあり、ResourceManager でStream として取得できます。
  この場合、指定するリソースの名前は、一覧で表示された通り、すべて小文字である必要があります。
  Visual Studioの「出力」ウインドウには以下のように表示されました。
  プログラムの一部の.xaml もテキストのまま保持されていて、ビルドアクションをResourceとして組み込んだファイルと同じ扱いになります。

  これが以降で説明するテストプログラムのXAMLで、stackpanel1 と名前を付けた StackPanel があります。
  以降のテストは、リソースを読み込んで、インスタンスを stackpanel1 の子要素に追加するものです。

  1. <UserControl x:Class="SilverlightApplication6.MainPage"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5.     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6.     mc:Ignorable="d"
  7.     d:DesignHeight="300" d:DesignWidth="400">
  8.     <Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
  9.         <StackPanel Name="stackpanel1"/>
  10.     </Grid>
  11. </UserControl>

埋め込まれたリソース

  GetManifestResourceNames()で取得できるリソース名からすると、「埋め込まれたリソース」は、「マニフェストリソース」のようです。
  また、マニフェストリソースには、「アセンブリ名.g.resources」が含まれ、 ResourceManager の引数となっていることから、マニフェストリソースには、「Resource」の上位の概念のようです。
  マニフェストリソースの名前は、「アセンブリ名.プロジェクトのファイル名」の形式になっています。アセンブリ名を省略することはできませんでした。
  また、日本語でも、何のエンコードも必要がないようです。
  以下のようにして、文字列としてXAMLを読み込んで、これをインスタンスして表示します。

  1. Assembly asm = Assembly.GetExecutingAssembly();
  2. string[] sa = asm.GetManifestResourceNames();
  3. Stream s0 = asm.GetManifestResourceStream(
  4.     "SilverlightApplication6.埋め込まれたリソース.xaml");
  5. stackpanel1.Children.Add(
  6.     XamlReader.Load(
  7.         (new StreamReader(s0)).ReadToEnd())
  8.             as UIElement);
  9. Stream s2 = asm.GetManifestResourceStream(sa[2]);
  10. stackpanel1.Children.Add(
  11.     XamlReader.Load(
  12.         (new StreamReader(s2)).ReadToEnd())
  13.             as UIElement);

コンテンツ

  おそらく、リソースとコンテンツは並列の概念ですが、SilverlightのApplicationには、GetContentStream()がありません。
  GetResourceStream()でアクセスします。
  また、XmlXapResolver を使っても、同じことができます。Xap の Resolver は、理解できますが、なぜ Xml なのかは、XML記述中の参照の解決が本来の目的だからと言うことのようです。コードだと XmlReader.Create("abc.xml"); のようにしたときにXmlXapResolver 使われ、abc.xmlはビルドアクションがコンテンツで組み込まれていることが想定されていることになります。

GetResourceStream()

  1. StreamResourceInfo sri1 = App.GetResourceStream(
  2.     new Uri("コンテンツ.xaml", UriKind.Relative));
  3. stackpanel1.Children.Add(
  4.     XamlReader.Load(
  5.         (new StreamReader(sri1.Stream)).ReadToEnd())
  6.             as UIElement);

XmlXapResolver

  1. XmlXapResolver xxr = new XmlXapResolver();
  2. stackpanel1.Children.Add(
  3.     XamlReader.Load(
  4.         (new StreamReader(
  5.             (Stream)xxr.GetEntity(
  6.                 new Uri("コンテンツ.xaml", UriKind.Relative),
  7.                 null, typeof(Stream))
  8.             )).ReadToEnd())
  9.             as UIElement);

Resource

  ビルドアクションを「Resource」とした場合、ResourceManager で取得できますが、リソース名が問題です。
  前述の「リソースの一覧」のように、小文字化とURLエンコードが必要です。適用順で結果が異なると思いますが良くわかりません。 
  リソース名は英数にして、プログラムでの参照はすべて小文字と覚えれば問題は起きません。

  1. Assembly asm = Assembly.GetExecutingAssembly();
  2. ResourceManager rm = new ResourceManager(
  3.     "SilverlightApplication6.g", asm);
  4. string rc_name 
  5.     = System.Windows.Browser.HttpUtility.UrlEncode(
  6.             "Resource.xaml").ToLower();
  7. Stream resource_c = rm.GetStream(rc_name);
  8. stackpanel1.Children.Add(
  9.     XamlReader.Load(
  10.         (new StreamReader(resource_c)).ReadToEnd())
  11.             as UIElement);

mikeo_410@hotmail.com