Last Updated 2012/11/06
このページでは、WPF アプリケーションにおける「リソース "resource"」について説明します。特に、ビルドアクションについて詳しく解説します。
伝統的な Windows アプリケーションにおける「リソース "resource"」は、アプリケーション内に組み込んで使用する画像・音声・動画などのバイナリデータと文字列とをさします。
WPF 以前のアプリケーション開発においてデータをリソース化するメリットは、アプリケーションがリソースを使用する段階においてはじめてメモリ中にロードされることにあります。つまり、必要以上にメモリを使わないようにできます。また、国際化対応するアプリケーションの場合はリソース部分だけを入れ替えるだけで異なる言語に対応させることが容易になります。しかし、現在ではメモリ使用量を節約することのメリットはそれほど大きくはありません
WPF アプリケーションにおけるリソースは旧来の Windows フォームアプリケーションを含めてその位置づけが異なっています。つまり、ユーザーインターフェース(以後、UI と称する)部分は XAML 構文で構築することになったため、リソースへの参照をプロパティのように扱わなければならなくなったからです。また、グラフィックスオブジェクトやコントロールのスタイルなどをリソースとして定義することができるようになりました。
さて、このページでは画像・音声・動画ファイルをリソース化し、アプリケーションの中でそれらをどのように使うかについて説明します。グラフィックスオブジェクトやコントロールのスタイルなどリソース化などについては別のテーマとして取り上げたいと思います。
ビルドアクションとはプロジェクトをビルドするときにプロジェクトアイテム(プロジェクトを構成する各種ファイル)をどのように扱うかを決めることです。まず、基本的な知識を仕入れることにしましょう。
WPF と Windows フォームアプリケーションとではプロジェクトアイテムに対するビルドアクションが異なる場合があります。ここではあくまで WPF におけるそれですので、混同しないようにしてください。
Visual Studio 2010 C# のデフォルトのプロジェクトを起こして、自動的に作成されるプロジェクトアイテムのデフォルトの [ビルドアクション] と [出力ディレクトリにコピー] は次のとおりです。
出力ディレクトリとはビルド後の EXE または DLL を格納するディレクトリです。通常は、bin/Debug または bin/Release ディレクトリになります。以後の解説では簡単のため、「ビルド出力フォルダー」と呼ぶことにします。
ファイル | ビルドアクション | 出力ディレクトリにコピー |
---|---|---|
App.xaml | ApplicationDefinition | コピーしない |
App.xaml.cs | コンパイル | コピーしない |
MainWindow.xaml | Page | コピーしない |
MainWindow.xaml.cs | コンパイル | コピーしない |
properties/AssemblyInfo.cs | コンパイル | コピーしない |
properties/Resources.resx | 埋め込まれたリソース | コピーしない |
properties/Resources.Designer.cs | コンパイル | コピーしない |
properties/Settings.settings | なし | コピーしない |
properties/Settings.Designer.cs | コンパイル | コピーしない |
次に、プロジェクトに images、sounds、videos の各フォルダを追加し、それぞれに画像、音声、ビデオファイルを追加します。それらの [ビルドアクション] と [出力ディレクトリにコピー] は次のとおりです。
ファイル | ビルドアクション | 出力ディレクトリにコピー |
---|---|---|
images/content.jpg | Resource | コピーしない |
images/embedded.jpg | Resource | コピーしない |
images/resource.jpg | Resource | コピーしない |
sounds/chimes.wav | コンテンツ | コピーしない |
videos/butterfly.wmv | コンテンツ | コピーしない |
プロジェクトに追加する可能性のあるプロジェクトアイテムについても調べてみました。
ファイル | ビルドアクション | 出力ディレクトリにコピー |
---|---|---|
Dictionary1 | Page | コピーしない |
UserControl1.xaml | Page | コピーしない |
UserControl1.xaml.cs | コンパイル | コピーしない |
Class1.cs | コンパイル | コピーしない |
documents/readme.txt | Resource | コピーしない |
documents/a.html | コンテンツ | コピーしない |
documents/FlowDocument.xaml | Page | コピーしない |
プロジェクトアイテム、つまり、プロジェクトを構成する各種ファイルのプロパティを開いて、[ビルドアクション] を指定するコンボボックスには以下にリストアップする項目がならんでいるはずです。
これらについて、概観してみましょう。
[なし]「デフォルトのビルドアクション」の中で紹介したとおり、ビルドアクションが [なし] の項目は properties/Settings.settings だけです。
documents/readme.txt が [Resource] になっている点に注意してください。
ビルドアクションが [なし] ということは文字通りの理解でかまいません。つまり、ビルド時には一切のかかわりを持たないということです。
[コンパイル]ビルド時にコンパイルの対象となり、コンパイル後、ビルド出力フォルダの obj ディレクトリに出力されます。当然のことですが、C# ファイルはすべて「コンパイル」になっています。
[コンテンツ]ビルド時にコンパイルの対象とはなりませんが、[出力ディレクトリにコピー] を適切に設定しておくとアプリケーションからそのファイルを参照することが容易になります。つまり、[出力ディレクトリにコピー] を [常にコピー] または [新しい場合はコピー] に設定するとビルド出力フォルダー内にファイルがコピーされます。
たとえば、documents/a.html の [出力ディレクトリにコピー] を [新しい場合はコピー] に設定してビルドすると、ビルド出力フォルダ内に documents フォルダを作成し、a.html をコピーします。こうすることでアプリケーションが存在するフォルダから相対パスで参照することが可能になります。つまり、以下のコードでアクセス可能になります。
StreamResourceInfo info = Application.GetContentStream(new Uri("documents/a.htm", UriKind.Relative));[埋め込まれたリソース]
[埋め込まれたリソース] は WPF 以前のもので、WPF では [Resource] が基本です。WPF では XAML を使ってアプリケーションの骨格を作成する都合上、相対パスによるリソースへの参照が不可欠だからです。たとえば、Image コントロールの Source プロパティにイメージを設定する XAML 構文は次のとおり、きわめて簡潔です。
<Image Source="images/Open.png" />
しかし、[埋め込まれたリソース] と [Resource] はともにアセンブリに組み込まれるという点では同じです。ところで、XAML 構文ではなく、C# コードによる [Resource] へのアクセス方法は次のとおりです。
var assembly = System.Reflection.Assembly.GetExecutingAssembly(); var stream = assembly.GetManifestResourceStream("ResourceTest.images.content.jpg"); if (stream != null) { var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.StreamSource = stream; bitmapImage.EndInit(); image.Source = bitmapImage; stream.Close(); }[Resource]
このアクションは WPF 専用です。
[埋め込まれたリソース] で書いたとおり、ほぼ同じ機能です。ビルド後、obj フォルダ内に ApplicationName.g.resources ファイルとして格納されます。画像ファイルについては特段の理由がない限り、[Resource] でよいと思います。
[ApplicationDefinition]このアクションは WPF 専用です。
「デフォルトのビルドアクション」を見ても分かるとおり、アプリケーションの中でこのアクションを持つプロジェクトアイテムは App.xaml の一つだけです。そこで、App.xaml の内容を見てみましょう。
<Application x:Class="ResourceTest.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>
この中で留意すべき点は、x:Class="ResourceTest.App" と StartupUri="MainWindow.xaml" です。確信はありませんが、この二つのコードを持つものが [ApplicationDefinition] になるのではないかと思います。WPF アプリケーションでは [ApplicationDefinition] を持つプロジェクトアイテムは一つだけだと思います。
[Page]このアクションは WPF 専用で、ビルド後、対応する .baml ファイル(XAML ファイルのバイナリ版)が obj フォルダ内に格納され、最終的にはアセンブリに組み込まれます。
以下に、ビルドアクションに [Page] を設定するもの(プロジェクトにファイルを追加したときに自動的に設定されますので、明示的に設定する必要はありません)をリストアップしておきます。
[Page] アクションのファイルについては Visual Studio がよきにはからうでしょうから開発者がその詳細を知る必要はないと思います。
[SplashScreen]このアクションは WPF 専用で、スプラッシュスクリーンとして表示する画像ファルに対して設定します。[出力ディレクトリにコピー] は「コピーなし」のままでかまいません。
ところで、スプラッシュスクリーンとはアプリケーションを起動するときにごあいさつとして表示するものです。バックグラウンドでアプリケーションの初期化(大きなデータを読み込むなど)を実行するときにユーザーを退屈させないための工夫として利用することが多いですね。しかし、このビルドアクションはありがたみのある機能ではありません。スプラッシュスクリーンが必要であれば自作しましょう。
[DesignData]私は Silverlight も Expression Blend も使っていませんので、詳しく説明する能力はありません。しかし、このアクションは WPF と Silverlight との間でデータを共有するときに設定するもののようです。
[DesignDataWithDesignTimeCreatableTypes][DesignData] で説明したとおりです。[DesignData] との違いはデザイナがデータ型のインスタンスを作成可能な点が異なるようです。
[EntiryDeploy]このアクションは Windows フォームアプリケーション専用で、Entity Framework に関係しているらしいのですが私は分かりません。
WPF におけるビルドアクションの実務について、リソースの種類ごとに解説します。
画像リソースはツールバーのボタンやメニュー項目をあらわすイメージとして画像を使う機会が多いですから必ずリソースのお世話にならなければなりません。
画像ファイルをプロジェクトに追加するとそのビルドアクションは自動的に [Resource] になりますが、ほかのアクションに変更する理由はありません。
画像ファイルを別ファイルとして配布する場合は、[コンテンツ] ではなく、[なし] でいいと思います。
一方、何らかの都合で画像リソースをライブラリ化するほうが望ましい場合の手順については後述する「画像・音声ファイルのリソースライブラリ化」を参照してください。
効果音はたいていの場合、.wav ファイルを使いますので、これを前提として説明します。
WPF の MediaPlayer クラスは音声データを URI からの読み込む手段しか提供しません。MediaPlayer クラスを使う限りではデータをストリームから読み込むことはできないですから、別ファイルとしてアプリケーションに添付する方法を選ぶほかありません。
といってしまっては実もフタもないですから、別の方法を考えて見ましょう。
System.Media.SoundPlayer クラスは .wav ファイルの再生に使えます。しかも、ファイルからでもストリームからでもデータを読み込むことができます。.wav ファイルの再生が目的であればこのクラスを使えばいいのです。
プロジェクトに sounds フォルダを追加し、そこに chimes.wav を格納するとします。このとき、chimes.wav ファイルのビルドアクションを [埋め込まれたリソース] に設定してください。このリソースを読み込んで再生する手順は以下のとおりです。
private void button1_Click(object sender, RoutedEventArgs e) { var assembly = System.Reflection.Assembly.GetExecutingAssembly(); // リソース名は 名前空間名 + リソース名 です var stream = assembly.GetManifestResourceStream("ResourceTest.sounds.chimes.wav"); var player = new System.Media.SoundPlayer(stream); player.Play(); }
「効果音リソース」でも触れましたが、WPF の MediaPlayer クラスはデータをファイルから読み込む手段しか提供しません。したがって、音楽ファイルは別ファイルにしてアプリケーションに添付する形式しか選択できないということになります。
しかし、データはどうしてもアプリケーションに組み込みたいということであれば、音楽ファイルのビルドアクションを [埋め込まれたリソース] にしてアプリケーションからデータをストリームに読み込み、それをファイル化する手があります。裏技的ですが、ファイルをユーザーが気づきにくいフォルダ内に作成すればが気付かれることはないと思います。その具体的な手順については次の「ビデオリソース」で説明します。
「音楽リソース」の続きです。プロジェクトに images フォルダを追加し、そこに Butterfly.wmv を格納します。このファイルのビルドアクションに [埋め込まれたリソース] を設定します。.wmv データをストリームに読み込んでこれを一時的にファイルに保存します。保存先はどこでもいいのですが、ファイルを作成したことをユーザーに知らせたくない場合はユーザーに分かりにくいフォルダを選べばいいでしょう。ここでは、Users\User\AppData\Roaming にしました。
private void button1_Click(object sender, RoutedEventArgs e) { var assembly = System.Reflection.Assembly.GetExecutingAssembly(); var stream = assembly.GetManifestResourceStream("ResourceTest.images.Butterfly.wmv"); if (stream != null) { string folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string outFileName = Path.Combine(folder, "dummy.wmv"); byte[] bytes = new byte[stream.Length]; stream.Read(bytes, 0, (int)stream.Length); var outStream = new FileStream(outFileName, FileMode.Create, FileAccess.ReadWrite); outStream.Write(bytes, 0, (int)stream.Length); outStream.Close(); stream.Close(); var player = new MediaPlayer(); player.Open(new Uri(outFileName)); var videoDrawing = new VideoDrawing(); videoDrawing.Rect = new Rect(0, 0, 60, 60); videoDrawing.Player = player; var drawingImage = new DrawingImage(videoDrawing); image1.Source = drawingImage; player.Play(); } }
作成したファイルはアプリケーション終了時にでも削除すればいいでしょう。
伝統的な Windows アプリケーションでは文字列をリソース化することに意味はありました。つまり、リソースはそれを使う場合になってはじめてロードされますので、メモリの使用量を節約できるメリットがあるからです。しかし、現在のシステム環境ではメモリの使用量を気にすることはありません。したがって、海外に配布するような場合でもなければ文字列をリソース化する必要はないといえます。
ビルドアクションを変更したときはリビルドしましょう。そうしないと変更内容を反映しないからです。
画像・音声・動画ファイルをリソースとして持つライブラリ(.dll ファイル)を作成する手順について説明します。あわせて、リソースライブラリからリソースを読み込む手順を説明します。
これで ResourceLibrary.dll が作成されたはずです。
次に、ライブラリからリソースを読み込む手順について説明します。いつものように、WPF アプリケーションプロジェクトを起こし、フォームに Image コントロールと三つの Button コントロールを配置してください。
private void button1_Click(object sender, RoutedEventArgs e) { string fileName = @"ResourceLibrary.dll"; // フルパスを指定してもよい var assembly = System.Reflection.Assembly.LoadFile(fileName); // リソース名は「名前空間名 + リソース名」とする // リソース名は大文字と小文字を区別するので注意のこと var stream = assembly.GetManifestResourceStream("ResourceLibrary.test.jpg"); if (stream != null) { var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.StreamSource = stream; bitmapImage.EndInit(); image.Source = bitmapImage; stream.Close(); } }
すでに説明しましたが、System.Windows.Media.MediaPlayer クラスはメディアデータをストリームから受け取る手段を持ちません。URL を指定する方法だけです。しかし、System.Media.SoundPlayer クラスのほうはストリームからの読み込みが可能です。そこで、SoundPlayer クラスを使うことにしました。
private void button2_Click(object sender, RoutedEventArgs e) { string fileName = @"ResourceLibrary.dll"; // フルパスを指定してもよい var assembly = System.Reflection.Assembly.LoadFile(fileName); // リソース名は「名前空間名 + リソース名」とする // リソース名は大文字と小文字を区別するので注意のこと var stream = assembly.GetManifestResourceStream("ResourceLibrary.chimes.wav"); if (stream != null) { var player = new System.Media.SoundPlayer(stream); player.Play(); stream.Close(); } }
音声ファイルの場合と同様の問題がありますが、.NET Framework の中に動画を再生する機能は System.Windows.Media.MediaPlayer クラスしかありません。そこで、メディアデータを一旦ファイルに落として、あらためてファイルから読み込むことにしました。
private void button3_Click(object sender, RoutedEventArgs e) { string fileName = @"ResourceLibrary.dll"; // フルパスを指定してもよい var assembly = System.Reflection.Assembly.LoadFile(fileName); // リソース名は「名前空間名 + リソース名」とする // リソース名は大文字と小文字を区別するので注意のこと var stream = assembly.GetManifestResourceStream("ResourceLibrary.Butterfly.wmv"); if (stream != null) { // ファイルを置くフォルダはどこでもいいのですが、このような目的のために用意された // フォルダを選ぶほうがいいでしょう。そこで、SpecialFolder.ApplicationData フォルダ // にしました string folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); // 出力するファイル名 string outFileName = Path.Combine(folder, "dummy.wmv"); byte[] bytes = new byte[stream.Length]; stream.Read(bytes, 0, (int)stream.Length); // 出力先ストリーム var outStream = new FileStream(outFileName, FileMode.Create, FileAccess.ReadWrite); outStream.Write(bytes, 0, (int)stream.Length); outStream.Close(); stream.Close(); var player = new MediaPlayer(); player.Open(new Uri(outFileName)); var videoDrawing = new VideoDrawing(); videoDrawing.Rect = new Rect(0, 0, 60, 60); videoDrawing.Player = player; var drawingImage = new DrawingImage(videoDrawing); image.Source = drawingImage; player.Play(); } }
.NET Framework/WPF の英文のヘルプを見ると、「埋め込みリソース」と「埋め込まれたリソース」ともに "Embedded Resource" です。原語を日本語に直訳すると「埋め込まれたリソース」ですが、日本語としては「埋め込みリソース」のほうが落ち着きますね。したがって、日本語版では用語を使い分けていると考えられます。
埋め込みリソースは文字通り、アセンブリ(EXE または DLL ファイル)内に埋め込んだリソースという理解でいいと思います。埋め込みリソースにするにはビルドアクションを [Resource] または [埋め込まれたリソース] に設定しなければなりません。[Resource] と [埋め込まれたリソース] との違いについてはすでに説明したとおりです。
そこで結論ですが、「埋め込まれたリソース」はビルドアクションの一つをさす言葉で、「埋め込みリソース」はリソースの種類と考えていい思います。
下図は、WPF アプリケーション(EXE ファイル)またはライブラリ(DLL ファイル)からリソースを抽出するツールを実行したところです。抽出可能なリソースは画像・音声・動画です。
画像リソースは実寸で表示する機能があります。音声および動画は Windows に付属の Windows Media Player を起動し、再生することができます。また、指定のリソースをファイルに保存したり、すべてのリソースを一気にファイル化することが可能です。
アプリケーションの名前は WpfResHack です。入手先は Software のページをご覧ください。
このページのテーマとはちょっと異なるかもしれませんが、Windows アプリケーション(ライブラリを含む)はアイコンをリソースとして内蔵しています。これを抽出するツールを自作しましたので、紹介します。ツールの名前は IconHack です。下図は、IconHack を起動し、Explorer.exe を対象に内蔵するアイコンを表示したところです。
画面右側は現在フォーカスのあるアイコンに含まれるすべてのアイコンを表示しています。上図の例では、リソース名が 103 のアイコンにはサイズとピクセル当たりのビット数の異なる 9 つのアイコンが含まれています。
IconHack の詳細および入手先は Software のページをご覧ください。
−以上−