【WPF】プロジェクト内のファイルリソースを読み込む

WPF

はじめに

WPF開発でファイルリソース(画像など)をWPFアプリ内で扱う方法を書き留めておきます。

このエントリーはVisual Studio 2019.netcore 3.1 WPFで動作確認しています。

プロジェクトにファイルを追加する

ファイルリソースはプロジェクトの直下でも問題ないですが、今回はディレクトリを作り管理します。
プロジェクトを右クリックして「追加」->「新しいフォルダ」でディレクトリを追加します。

今回はResourcesディレクトリを作り、画像ファイルを扱うのでImagesディレクトリも作成しましょう。

このImagesディレクトリへ画像リソースを追加していきます。
サンプルではresource.pngファイルを追加しています。

ビルドアクションを設定する

追加したファイルを右クリック、プロパティを選択してプロパティウィンドウを開きます。
ショートカットキーは、追加したファイルを選択した状態でF4キー、またはAlt + Enterです。

プロパティウィンドウ内の「詳細」グループにある「ビルドアクション」と「出力ディレクトリにコピー」項目を設定していきます。

このエントリーでは

ビルドアクション出力ディレクトリにコピー
リソース未設定 / コピーしない
コンテンツ常にコピーする / 新しい場合はコピーする
埋め込みリソース未設定 / コピーしない
なし常にコピーする / 新しい場合はコピーする

を、説明します。

アプリ内から読み込む

<Window>
  <Grid>
    <Image x:Name="ResourceImg"/>
  </Grid>
</Window>

今回は上記のImageコントロールResourceImgへ画像リソースを読み込んでいきます。

Windowのプレフィックス等は省略しているので気をつけてください。

ビルドアクション「リソース」

ビルドアクションを「リソース」に設定した場合、ファイルリソースはアセンブリ(.exeや.dll)へ埋め込まれます。
よって、出力ディレクトリへのコピーは不要なので「コピーしない」か未設定です。

また、アセンブリに埋め込まれるため、ファイルリソースの変更が行われた場合は再ビルドが必要です。

この設定はWPFプロジェクトのみですので、他のプロジェクトでは使えません。

XAML

XAMLから読み込む場合はImageコントローラーのSourceプロパティへプロジェクトをRootとした画像ファイルへのパスを設定すれば読み込まれます。

<Image x:Name="ResourceImg"
       Source="/Resources/Images/resource.png"/>

もし、プロジェクト直下にファイルリソースを置いている場合のパスは、

<Image x:Name="ResourceImg"
       Source="/resource.png"/>

と、なります。

C#

コードビハインドで読み込む場合はApplicationクラスにリソースのストリームを取得するメソッドGetResourceStreamがあるのでそれを使います。

public partial class MainWindow : Window {
  public MainWindow() {
    InitializeComponent();

    // Load
    ResourceImg.Source = LoadResourceTypeImage("resource.png");
  }

  //
  // LoadByResourceTypeImage
  //
  private BitmapImage LoadResourceTypeImage(string fileName) {
    BitmapImage result = null;
    Uri resource_uri = new Uri("/Resources/Images/" + fileName, UriKind.Relative);
    StreamResourceInfo info = Application.GetResourceStream(resource_uri);

    if(info != null){
      using (Stream stream = info.Stream) {
        if (stream != null) {
          result = new BitmapImage();

          result.BeginInit();
          result.StreamSource = stream;
          result.CacheOption = BitmapCacheOption.OnLoad;
          result.EndInit();
        }
      }
    }

    return result;
  }

GetResourceStreamへ指定するUriは相対指定(UriKind.Relative)にしてあります。

Uriのパスは上記のXAMLと同じくプロジェクトをRootとしたパスを渡します。

絶対指定(UriKind.Absolute)にもできますが、

Uri resource_uri = new Uri("pack://application:,,,/Resources/Images/" + fileName, UriKind.Absolute);

と、ややこしいパスになるので特に理由がなければ相対指定でいいです。

ビルドアクション「コンテンツ」

ビルドアクションを「コンテンツ」に設定した場合、ビルドアクション「リソース」と同じようにアクセスが可能です。

大きく違うところはファイルリソースがアセンブリ(.exeや.dll)へ埋め込まれるのではなく、アセンブリは外部にあるファイルリソースへの関連を持つというところです。

アセンブリは外部ファイルへの関連を持っているだけなので、実行時に参照先にファイルがないとエラーになります。

そこで、ファイルリソースのプロパティ内の「出力先ディレクトリにコピー」項目を「常にコピーする」か「新しい場合はコピーする」に設定してやることで、ビルド時に必要なファイルリソースを自動でコピーされるようにします。

また、アセンブリからの相対位置とファイル名が一致していればファイルを入れ替えても再ビルドの必要なく動作させられます。

XAML

XAMLから読み込む場合はビルドアクション「リソース」とまったく同じで、ImageコントローラーのSourceプロパティへプロジェクトをRootとした画像ファイルへのパスを設定すれば読み込まれます。

<Image x:Name="ResourceImg"
       Source="/Resources/Images/resource.png"/>

C#

コードビハインドの場合もほぼ同じですが、ストリームを取得するメソッドがGetContentStreamであることに注意です。

public partial class MainWindow : Window {
  public MainWindow() {
    InitializeComponent();

    // Load
    ResourceImg.Source = LoadContentTypeImage("resource.png");
  }

  //
  // LoadContentTypeImage
  //
  private BitmapImage LoadContentTypeImage(string fileName) {
    BitmapImage result = null;
    Uri resource_uri = new Uri("/Resources/Images/" + fileName, UriKind.Relative);
    StreamResourceInfo info = Application.GetContentStream(resource_uri);

    if (info != null) {
      using (Stream stream = info.Stream) {
        if (stream != null) {
          result = new BitmapImage();

          result.BeginInit();
          result.StreamSource = stream;
          result.CacheOption = BitmapCacheOption.OnLoad;
          result.EndInit();
        }
      }
    }

    return result;
  }
}

Uriの絶対指定も違いはありません。

Uri resource_uri = new Uri("pack://application:,,,/Resources/Images/" + fileName, UriKind.Absolute);

ビルドアクション「なし」

ビルドアクション「なし」は例えばREADMEテキストファイルやライセンスが表記されたファイルなど、アプリケーションに読み込むことはないけど、ビルド後に成果物として配置したいファイルリソースに指定します。

よって、ファイルリソースのプロパティ内の「出力先ディレクトリにコピー」項目を「常にコピーする」か「新しい場合はコピーする」に設定します。

READMEテキストファイルなどだとプロジェクト直下に配置しますが、今回はこのままの構成で説明します。

XAML

読み込む予定にないファイルリソースなのでXAMLからの読み込みは提供されていぽいです。

どうしても読み込みたい場合はコードビハインドなどでゴニョゴニョします。

C#

読み込む場合は、普通のファイルとして読み込みます。

public partial class MainWindow : Window {
  public MainWindow() {
    InitializeComponent();

    // Load
    ResourceImg.Source = LoadNoneTypeImage("resource.png");
  }

  //
  // LoadNoneTypeImage
  //
  private BitmapImage LoadNoneTypeImage(string fileName) {

    BitmapImage result = null;
    Assembly ass = Assembly.GetExecutingAssembly();
    string resource_path = ass.Location;
    int index = resource_path.LastIndexOf(@"\");

    if(index < 0) { return null; }

    resource_path = resource_path.Substring(0, index);
    resource_path += @"\Resources\images\" + fileName;

    if (File.Exists(resource_path)) {
      using(Stream stream = File.OpenRead(resource_path)) {

        if (stream != null) {
          result = new BitmapImage();
          result.BeginInit();

          result.StreamSource = stream;
          result.CacheOption = BitmapCacheOption.OnLoad;

          result.EndInit();
        }
      }
    }

    return result;
  }
}

アセンブリからファイルリソースへの相対位置はわかっているので実行中のアセンブリのパスをまず取得します。

Assembly ass = Assembly.GetExecutingAssembly();
string resource_path = ass.Location;

次に取得したパスからRootとなるディレクトリパスを取得します。

int index = resource_path.LastIndexOf(@"\");

if(index < 0) { return null; }

resource_path = resource_path.Substring(0, index);

最後にファイルリソースへの相対パスをつなげます。

resource_path += @"\Resources\images\" + fileName;

あとはストリームを開いて読み込みます。

ただ、アプリケーションから読み込む必要が出た段階でビルドアクションを変更した方がリソースとしては正しいです。

ビルドアクション「埋め込みリソース」

ビルドアクション「埋め込みリソース」はファイルリソースをアセンブリへ埋め込みます。

ビルドアクション「リソース」との違いは、埋め込み先が違い、WPFプロジェクト以外の.NETプロジェクトでファイルリソースを埋め込むためのビルドアクションらしいです。

WPFアプリを開発しているならビルドアクション「埋め込みリソース」ではなくビルドアクション「リソース」を使ったほうが素直です。

また、アセンブリに埋め込むため「出力ディレクトリにコピー」は「未設定」か「コピーしない」です。

XAML

XAMLから呼ばれることが想定されていないのか提供されていないぽいです。

C#

コードビハインドで読み込む場合はAssemblyクラスにリソースのストリームを取得するメソッドGetManifestResourceStreamがあるのでそれを使います。

namespace LoadImageResourceSample{
  public partial class MainWindow : Window {
    public MainWindow() {
      InitializeComponent();

      // Load
      ResourceImg.Source = LoadEmbeddedTypeImage("resource.png");
    }

    //
    // LoadEmbeddedTypeImage
    //
    private BitmapImage LoadEmbeddedTypeImage(string fileName) {

      BitmapImage result = null;
      string path = "LoadImageResourceSample.Resources.Images.";
      Assembly ass = Assembly.GetExecutingAssembly();

      using (Stream stream = ass.GetManifestResourceStream(path + fileName)) {
        if (stream != null) {
          result = new BitmapImage();

          result.BeginInit();
          result.StreamSource = stream;
          result.CacheOption = BitmapCacheOption.OnLoad;
          result.EndInit();
        }
      }

      return result;

    }
  }
}

基本的にはビルドアクション「リソース」と同じながれですが、ファイルリソースへのパスが

  • 階層の区切りが.(ピリオド)である
  • ネームスペース.サブディレクトリ.ファイル名である

である点に注意が必要です。

まとめ

ここまで4つのビルドアクションによるファイルリソースの読み込み方を見てきました。

WPFアプリ開発においてファイルリソースを扱う場合、ビルドアクション「リソース」「コンテンツ」「なし」が基本になると思います。

先にも書いた通りアセンブリへ埋め込むという意味ではビルドアクション「埋め込みリソース」は「リソース」と同じなのでWPFアプリ開発ではこのビルドアクションを使う理由はないように思います。
試してはないですがWPFアプリと他の.NETアプリで共用のアセンブリ(.dll)を作ろうとした時に使うかも?ぐらいです。

どのビルドアクションでもコードビハインドからアクセスでき、最終的にはストリームでアクセスしています。
これはストリームからデータを読めれば画像やテキストなどに限らず、オリジナルフォーマットのファイルでもファイルリソースにできるということです。オリジナルフォーマット…ワクワクしますね!

参考リンク

https://docs.microsoft.com/ja-jp/visualstudio/ide/build-actions?view=vs-2019

https://docs.microsoft.com/ja-jp/dotnet/desktop/wpf/app-development/wpf-application-resource-content-and-data-files?view=netframeworkdesktop-4.8

https://docs.microsoft.com/ja-jp/dotnet/desktop/wpf/app-development/pack-uris-in-wpf?view=netframeworkdesktop-4.8

コメント

タイトルとURLをコピーしました