連載
» 2013年06月13日 17時12分 UPDATE

WinRT/Metro TIPS:アプリに同梱したテキスト・ファイルを読むには?[Win 8/WP 8]

Windowsストア・アプリやWindows Phone 8アプリでファイルを読み書きする方法とは? アプリ・パッケージに同梱したテキスト・ファイルを読み取る方法を説明する。

[山本康彦,BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 これまで.NET Frameworkでデスクトップ・アプリなどを作ってきた人がWindowsストア・アプリやWindows Phone 8(以降、WP 8)アプリを始めたときに戸惑うことの筆頭が、ファイルの読み書きであろう。ファイルを読み書きするためのAPIが大幅に変わっているうえに、ファイルの存在する場所によって読み書き方法が異なるからだ。それをいっぺんに全部理解しようとするのは混乱の元になる。「こういう場所にファイルがあるときはこうする」と場合分けして、1つずつ身に付けていくのがよいだろう。

 そこで本稿では、ファイルの存在場所によるアクセス方法の違いを概観した後、まずアプリ・パッケージに同梱したテキスト・ファイルを読み取る方法を説明する。本稿のサンプルは「Windows Store app samples:MetroTips #40(Windows 8版)」と「Windows Store app samples:MetroTips #40(WP 8版)」からダウンロードできる。

 なお、掲載しているコードは特記なき場合はWindowsストア・アプリとWindows Phone 8(以降、WP 8)アプリで共通である。

 なお、掲載しているコードはWindowsストア・アプリとWP 8アプリで共通である。

事前準備

 Windows 8(以降、Win 8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。

 WP 8向けのアプリを開発するには、SLAT対応CPUを搭載したPC上の64bit版Win 8 Pro以上Windows Phone SDK 8.0(無償)が必要となる。

ファイルの場所と読み書き方法(概要)

 Windowsストア・アプリもWP 8アプリも、セキュリティ面からファイル・アクセスが制限されている。従来のデスクトップ・アプリのようにファイルを自由に読み書きすることはできない。ファイルの保存場所と読み書き方法をまとめると、次の表のようになる。

場所 読み書き ファイル・ピッカー 読み書きに利用するもの
アプリケーションのインストール・ディレクトリ 読み取り専用 利用不可 Package.Current.InstalledLocationプロパティ
「ms-appx:///」スキーマ
アプリケーション・データの場所(ローカル、ローミング、一時) 読み書き可 利用不可 ApplicationData.Currentプロパティ
「ms-appdata:///」スキーマ
ユーザーのDownloadsフォルダ 読み書き可(自アプリで作成したファイル以外はファイル・ピッカーの利用が必須) 利用可 DownloadsFolderクラス(Windows.Storage名前空間)
マニフェストでcapability(機能)を与えた場所 読み書き可 利用可 KnownFoldersクラス(Windows.Storage名前空間)
上記以外でユーザーが指定したファイルやフォルダ 読み書き可 必須 Windows.Storage.Pickers名前空間
Windowsストア・アプリのファイルの保存場所と読み書き方法

場所 読み書き 読み書きに利用するもの
インストール・フォルダ 読み取り専用 Package.Current.InstalledLocationプロパティ
「ms-appx:///」スキーマまたは「appdata:/」スキーマ(旧API用)
ローカル・フォルダ(WP 7.xの分離ストレージに該当) 読み書き可 ApplicationData.Current.LocalFolderプロパティ
「ms-appdata:///local/」スキーマまたは「isostore:/」スキーマ(旧API用)
マニフェストで許可したメディア・ライブラリ 読み取り専用(一部書き込み可) メディア・ライブラリ拡張API(Microsoft.Xna.Framework.Media.PhoneExtensions名前空間)
マニフェストで許可したSDカード 読み取り専用 ExternalStorageFolderクラス(Microsoft.Phone.Storage名前空間)
WP 8アプリのファイルの保存場所と読み書き方法

ファイルを読み書きするAPIの特徴

 従来と大きく異なるのは、次の2点。

  • 任意のパス名/ファイル名を使ってファイルを開くことはできない
    − 許可された場所では可能。ただし場所によってその方法は異なる。
  • 非同期APIのみ
    − async/awaitキーワードを使って非同期呼び出しをしなければならない。

 以上のことがらを一度に理解するのは大変だ。そこで今回は、アプリ・パッケージに同梱したテキスト・ファイルを読み取るという、ごくシンプルな例から説明する。

アプリ・パッケージにテキスト・ファイルを同梱するには?

 アプリのパッケージにテキスト・ファイルを含めたいことがある。例えば、アプリの初期値を記述したファイルや、画面に表示するライセンス条件を記載したファイルなどである。

 そのようなテキスト・ファイルは、Visual Studio上でアプリのプロジェクトに含めればよい。アプリのプロジェクトにフォルダを作って、そこに含めてもOKだ。例えば、「text」というフォルダを作り、そこに「sample01.txt」というファイルを作ってみよう。どちらの作業もVisual Studio上で行えば問題はない。

 ファイルを作ったら、改行を含めて適当な文章を数行ほど入力し、保存しておいてほしい。すると、Visual Studioは次の画像のような状態になるはずだ。

Visual Studioでテキスト・ファイルを作成したところ(Win 8)
Visual Studioでテキスト・ファイルを作成したところ(WP 8) Visual Studioでテキスト・ファイルを作成したところ(上:Win 8、下:WP 8)

 Visual Studioのソリューション・エクスプローラで「sample01.txt」を選び、そのプロパティを表示してみてほしい(上の画像では、画面右下に出ている)。そのプロパティの中に次の項目があるはずだ(画像の赤枠内)。

ビルドアクション コンテンツ
出力ディレクトリにコピー コピーしない
アプリ・パッケージに同梱されるファイルのプロパティ

 このようになっていれば、そのテキスト・ファイルはアプリ・パッケージに同梱される。Visual Studioでテキスト・ファイルを作成した場合は問題ないが、ほかのアプリで作ったファイルをコピーしてきたときはプロパティが上記と異なる場合があるので、ここを確認して違っていたら上のように修正すること。

 それでは、以降のサンプル・コードを試すために、同様にして「sample02.txt」ファイルと「sample03.txt」ファイルも作っておいてほしい。

アプリ・パッケージに同梱したテキスト・ファイルを読み取るには?

 基本は、Packageクラス(Windows.ApplicationModel名前空間)のInstalledLocationプロパティを使って、アプリ・パッケージに同梱したファイルにアクセスする。

 アプリ・パッケージに同梱したファイルは、列挙と読み取りができる。ファイル名やURIを指定して読み取ることも可能である。以下、順に説明していく。

 その前に、これからの解説で使う画面を用意しておこう。次のように、ボタンを4つとテキストボックスを配置しておく。またコードビハインドには、ボタンのClickイベントのハンドラも用意しておいてほしい(メソッドの中身は空のままで)。

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="……省略……">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <StackPanel Orientation="Horizontal">
    <Button x:Name="button1" Content="列挙" Click="button1_Click" />
    <Button x:Name="button2" Content="01" Click="button2_Click" />
    <Button x:Name="button3" Content="02" Click="button3_Click" />
    <Button x:Name="button4" Content="03" Click="button4_Click" />
  </StackPanel>
  <TextBox x:Name="textBox1" Grid.Row="1" TextWrapping="Wrap" />
</Grid>

以下の解説で使う画面のコントロールの定義部分(XAML)

アプリ・パッケージに同梱したファイルを列挙するには?

 PackageクラスのInstalledLocationプロパティ(=StorageFolderクラス(Windows.Storage名前空間))を利用する。

 StorageFolderオブジェクトのGetFolderAsyncメソッドを使えば、そのStorageFolderオブジェクトの配下のフォルダを表すStorageFolderオブジェクトを得られる。StorageFolderオブジェクトのGetFilesAsyncメソッドを使うと、そのフォルダ内のファイルを全て列挙できる。

 インストール・ディレクトリ(=フォルダ)の直下にある「text」フォルダの中にあるファイルを列挙するには、次のコードのようにする。ここでは、button1ボタンをクリックしたときに、列挙してその結果をTextBoxコントロールに表示している。

private async void button1_Click(object sender, RoutedEventArgs e)
{
  // インストール・フォルダを表すStorageFolderオブジェクト
  Windows.Storage.StorageFolder installedFolder
    = Windows.ApplicationModel.Package.Current.InstalledLocation;

  // 「text」フォルダを表すStorageFolderオブジェクトを取得する
  Windows.Storage.StorageFolder textFolder
    = await installedFolder.GetFolderAsync("text");

  // フォルダに含まれる全ファイルを列挙する
  IReadOnlyList<Windows.Storage.StorageFile> sampleFiles
    = await textFolder.GetFilesAsync();

  // 列挙したファイルのフルパスをTextBoxに表示する
  foreach (var file in sampleFiles)
    this.textBox1.Text += (file.Path + System.Environment.NewLine);
}

Private Async Sub button1_Click(sender As Object, e As RoutedEventArgs)
  ' インストール・フォルダを表すStorageFolderオブジェクト
  Dim installedFolder As Windows.Storage.StorageFolder _
      = Windows.ApplicationModel.Package.Current.InstalledLocation

  ' 「text」フォルダを表すStorageFolderオブジェクトを取得する
  Dim textFolder As Windows.Storage.StorageFolder _
          = Await installedFolder.GetFolderAsync("text")

  ' フォルダに含まれる全ファイルを列挙する
  Dim sampleFiles As IReadOnlyList(Of Windows.Storage.StorageFile) _
          = Await textFolder.GetFilesAsync()

  ' 列挙したファイルのフルパスをTextBoxに表示する
  Me.textBox1.Text = String.Empty
  For Each file In sampleFiles
    Me.textBox1.Text += (file.Path + System.Environment.NewLine)
  Next
End Sub

アプリ・パッケージに同梱したファイルを列挙するコード(上:C#、下:VB)
非同期メソッドを実行するのでイベントハンドラにはasync(C#)/Async(VB)キーワードが必要になることにも注意しよう(以下同様)。

 これを実行すると次の画像のようになる。フォルダ内のファイルが3つとも表示されている。

ファイルを列挙した画面(Win 8)
ファイルを列挙した画面(WP 8) ファイルを列挙した画面(上:Win 8、下:WP 8)
VS 2012からのデバッグ実行中のため、Win 8の方はインストールしたときとは異なるフォルダで実行されている。

列挙したテキスト・ファイルの1つを読み取るには?

 開きたいファイルを表すStorageFileオブジェクト(Windows.Storage名前空間)を取得し、そのOpenReadAsyncメソッドを使ってIRandomAccessStreamオブジェクト(Windows.Storage.Streams名前空間)を得る。

 次に、IRandamAccessStreamオブジェクトをSystem.IO名前空間のAsStream拡張メソッドを使ってStreamオブジェクト(System.IO名前空間)に変換し、それを与えてStreamReaderオブジェクト(System.IO名前空間)を生成する。StreamReaderオブジェクトが得られれば、そのReadToEndAsyncメソッドでテキスト・ファイルの全体を読み込める。

 文章にするととてもややこしそうだが、実際は次のコードのようになる。ここでは、button2ボタンをクリックしたときに、button1ボタンと同様に列挙した中から「sample01.txt」ファイルを選び出し、それを読み込んでTextBoxコントロールに表示している。

private async void button2_Click(object sender, RoutedEventArgs e)
{
  // インストール・フォルダを表すStorageFolderオブジェクト
  Windows.Storage.StorageFolder installedFolder
    = Windows.ApplicationModel.Package.Current.InstalledLocation;

  // 「text」フォルダを表すStorageFolderオブジェクトを取得する
  Windows.Storage.StorageFolder textFolder
    = await installedFolder.GetFolderAsync("text");

  // フォルダに含まれる全ファイルを列挙する
  IReadOnlyList<Windows.Storage.StorageFile> sampleFiles
    = await textFolder.GetFilesAsync();

  //※ ここまではbutton1_Clickイベントハンドラと同じ

  // 「sample01.txt」ファイルを選択する
  Windows.Storage.StorageFile sample01File
    = sampleFiles.FirstOrDefault((file) => file.Name == "sample01.txt");

  // 「sample01.txt」ファイルを開いて読み込む
  using(Stream st = (await sample01File.OpenReadAsync()).AsStream())
  using(TextReader reader = new StreamReader(st))
  {
    // いっぺんに読み込む
    this.textBox1.Text = await reader.ReadToEndAsync();
  }
}

Private Async Sub button2_Click(sender As Object, e As RoutedEventArgs)
  ' インストール・フォルダを表すStorageFolderオブジェクト
  Dim installedFolder As Windows.Storage.StorageFolder _
      = Windows.ApplicationModel.Package.Current.InstalledLocation

  ' 「text」フォルダを表すStorageFolderオブジェクトを取得する
  Dim textFolder As Windows.Storage.StorageFolder _
          = Await installedFolder.GetFolderAsync("text")

  ' フォルダに含まれる全ファイルを列挙する
  Dim sampleFiles As IReadOnlyList(Of Windows.Storage.StorageFile) _
          = Await textFolder.GetFilesAsync()

  '※ ここまではbutton1_Clickイベントハンドラと同じ

  ' 「sample01.txt」ファイルを選択する
  Dim sample01File As Windows.Storage.StorageFile _
        = sampleFiles.FirstOrDefault(Function(File) File.Name = "sample01.txt")

  ' 「sample01.txt」ファイルを開いて読み込む
  Using st As Stream = (Await sample01File.OpenReadAsync()).AsStream(), _
        reader As TextReader = New StreamReader(st)

    ' いっぺんに読み込む
    Me.textBox1.Text = Await reader.ReadToEndAsync()
  End Using
End Sub

列挙したテキスト・ファイルの1つを読み取って表示するコード(上:C#、下:VB)
このほかに、AsStreamメソッドを使うためにファイルの先頭でSystem.IO名前空間のusing/Importが必要だ(Windowsストア・アプリでは最初から記述されているが、WP8では追加する必要がある)。

 なお、テキスト・ファイルから1行ずつ読み込んで処理するには、ReadLineAsyncメソッドを使う。上のサンプル・コードで「いっぺんに読み込む」とコメントしてある部分を、ReadLineAsyncメソッドを使って書き直すと次のコードのようになる。

//// いっぺんに読み込む
//this.textBox1.Text = await reader.ReadToEndAsync();
//    ↓
// 1行ずつ読み込んで処理する
this.textBox1.Text = string.Empty;
string line = null;
while ((line = await reader.ReadLineAsync()) != null)
  this.textBox1.Text += (line + System.Environment.NewLine);

'' いっぺんに読み込む
'Me.textBox1.Text = Await reader.ReadToEndAsync()
'    ↓
' 1行ずつ読み込んで処理する
Me.textBox1.Text = String.Empty
Do
  Dim line As String = Await reader.ReadLineAsync()
  If (line Is Nothing) Then
    Exit Do
  End If
  Me.textBox1.Text += (line + System.Environment.NewLine)
Loop

テキスト・ファイルを1行ずつ読み取っては処理をするコード(上:C#、下:VB)

テキスト・ファイルをファイル名で指定して読み取るには?

 StorageFolderオブジェクトが得られたなら、そのフォルダ内にあるファイルは名前で指定できる。それには、OpenStreamForReadAsync拡張メソッド(System.IO名前空間)を使う。

 読み取るテキスト・ファイルの名前が確定しているなら、この方法を使うと楽だ。button3ボタンをクリックしたときに「sample02.txt」ファイルを読み込んでTextBoxコントロールに表示するには次のようにする。

private async void button3_Click(object sender, RoutedEventArgs e)
{
  // インストール・フォルダを表すStorageFolderオブジェクト
  Windows.Storage.StorageFolder installedFolder
    = Windows.ApplicationModel.Package.Current.InstalledLocation;

  // ファイル名を指定してテキスト・ファイルを読み取る
  using (Stream st
          = await installedFolder.OpenStreamForReadAsync("text¥¥sample02.txt"))
  using (TextReader reader = new StreamReader(st))
  {
    // いっぺんに読み込む
    this.textBox1.Text = await reader.ReadToEndAsync();
  }
}

Private Async Sub button3_Click(sender As Object, e As RoutedEventArgs)
  ' インストール・フォルダを表すStorageFolderオブジェクト
  Dim installedFolder As Windows.Storage.StorageFolder _
      = Windows.ApplicationModel.Package.Current.InstalledLocation

  ' ファイル名を指定してテキスト・ファイルを読み取る
  Using st As Stream _
          = Await installedFolder.OpenStreamForReadAsync("text¥sample02.txt"), _
        reader As TextReader = New StreamReader(st)

    ' いっぺんに読み込む
    Me.textBox1.Text = Await reader.ReadToEndAsync()
  End Using
End Sub

テキスト・ファイルをファイル名で指定して読み取るコード(上:C#、下:VB)
OpenStreamForReadAsyncメソッドを使うには、ファイルの先頭でSystem.IO名前空間のusing/Importが必要だ(ここでは、上で追記しているので問題ない)。

 ここで、ファイル名の指定には、StorageFolderオブジェクトが表すフォルダからの相対パスを渡さねばならないことに注意してほしい。絶対パスは使えない。また、StorageFolderオブジェクトが表すフォルダから上位階層へ移動する相対パスの指定もできない。

テキスト・ファイルをURIで指定して読み取るには?

 ファイルを指定するために特別に用意されているURIスキーマを使ってURIを構築すると、直接StorageFileオブジェクトを得ることもできる。

 インストール・フォルダ(ディレクトリ)配下のファイルを表すURIは、「ms-appx:///」で始まる。そのURIをStorageFileクラスのGetFileFromApplicationUriAsyncメソッドに渡すと、StorageFileオブジェクトが得られるので、あとはbutton2_Clickイベントハンドラと同様にしてテキスト・ファイルを読み出せばよい。

 button4ボタンをクリックしたときに「sample03.txt」ファイルを読み込んでTextBoxコントロールに表示するには、次のようにする。

private async void button4_Click(object sender, RoutedEventArgs e)
{
  // URIを指定してStorageFileオブジェクトを得る
  Windows.Storage.StorageFile sample03File
    = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
                                  new Uri("ms-appx:///text/sample03.txt"));

  //※ 以下はbutton2_Clickイベントハンドラと同様

  // StorageFileオブジェクトからファイルを開いて読み込む
  using (Stream st = (await sample03File.OpenReadAsync()).AsStream())
  using (TextReader reader = new StreamReader(st))
  {
    // いっぺんに読み込む
    this.textBox1.Text = await reader.ReadToEndAsync();
  }
}

Private Async Sub button4_Click(sender As Object, e As RoutedEventArgs)
  ' URIを指定してStorageFileオブジェクトを得る
  Dim sample03File As Windows.Storage.StorageFile _
        = Await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
                                      New Uri("ms-appx:///text/sample03.txt"))

  '※ 以下はbutton2_Clickイベントハンドラと同様

  ' StorageFileオブジェクトからファイルを開いて読み込む
  Using st As Stream = (Await sample03File.OpenReadAsync()).AsStream(), _
        reader As TextReader = New StreamReader(st)

    ' いっぺんに読み込む
    Me.textBox1.Text = Await reader.ReadToEndAsync()
  End Using
End Sub

テキスト・ファイルをURIで指定して読み取るコード(上:C#、下:VB)

【補足】Unicode以外のテキスト・ファイルを読み取るには?[Win8]

 アプリに同梱するテキスト・ファイルの文字コードは自由に設定できるが、通常はUTF-8にしておけば問題ない。何らかの事情でUnicode以外のテキスト・ファイルにしなければならないときも、Windowsストア・アプリならば簡単に対応できる。

 TextReaderオブジェクトを作るときに、次のようにしてエンコーディングを指定すればよい。

//using (TextReader reader = new StreamReader(st))
//  ↓ シフトJISのファイルなら、次のようにする
using (TextReader reader = new StreamReader(st,
                    System.Text.Encoding.GetEncoding("Shift_JIS")))

' reader As TextReader = New StreamReader(st)
'   ↓ シフトJISのファイルなら、次のようにする
reader As TextReader = New StreamReader(st, _
                     System.Text.Encoding.GetEncoding("Shift_JIS"))

シフトJISのファイルを読み取るコード(上:C#、下:VB)
VBのコードでは便宜的に「reader As 〜」の行と次の行をコメントにしているが、実際にはここにはコメントを記述できないので、自分で試す際にはこの2行は記述しないようにしよう。

 なお、WP 8では、Unicode以外のエンコーディングはサポートされていない。バイナリ・データとして読み込んだ後、「WinRT/Metro TIPS:シフトJISのデータを読み取るには?[WP 8]」で解説したように、C++/CXでWinPRTコンポーネントを作ってWin32 APIを利用して変換することになる。

まとめ

 アプリ・パッケージに同梱したファイルを列挙するにはPackageクラスのInstalledLocationプロパティ(=StorageFolderオブジェクト)を使う。列挙した中から必要なStorageFileオブジェクトを選択するか、あるいはファイル名を指定することで、StorageFolderオブジェクトからStorageFileオブジェクトを得る。また、「ms-appx:///」スキーマを使ってURIを構築して、直接StorageFileオブジェクトを得ることもできる。StorageFileオブジェクトが得られたら、そこからStreamを開き、TextReaderを構築してテキスト・ファイルを読み込む。

 これはかなり厄介な手順に思えるが、StorageFileオブジェクトを得る方法と、StorageFileオブジェクトから読み込む方法に分けると理解しやすい。ファイルの保存場所により、StorageFileオブジェクトを得る方法は異なるが、StorageFileオブジェクトから読み込む方法は同じである。

 なお、次のドキュメントも参照してほしい。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。