連載
» 2013年07月11日 17時49分 UPDATE

WinRT/Metro TIPS:ユーザーが指定したテキスト・ファイルを読み書きするには?[Win 8]

Windowsストア・アプリでは、ユーザーがファイル・ピッカーを使って選択したテキスト・ファイルは読み書きできる。その実装方法を説明する。

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

powered by Insider.NET

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

連載目次

 アプリケーション・データ記憶域にあるファイルは自由に読み書きできるけれども、そのほかにあるファイルはどうだろうか? Windowsストア・アプリでは、ユーザーが選択したファイルならば、読み書きできる。そのためには、「ファイル・ピッカー」を使わねばならない。本稿では、ファイル・ピッカーを使ってテキスト・ファイルを読み書きする方法を説明する。本稿のサンプルは「Windows Store app samples:MetroTips #44(Windows 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を使用している。

ファイル・ピッカーを使って読み書きできる場所

 ファイルの保存場所と読み書き方法をまとめた一覧を示す。これは、「WinRT/Metro TIPS アプリに同梱したテキスト・ファイルを読むには?[Win 8/WP 8]」にも掲載したものだ。

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

 上の表のうち、「アプリケーションのインストール・ディレクトリ」から読み取る方法は「WinRT/Metro TIPS アプリに同梱したテキスト・ファイルを読むには?[Win 8/WP 8]」で、また、「アプリケーション・データ記憶域」の読み書きについては「WinRT/Metro TIPS:アプリケーション・データ記憶域のテキスト・ファイルを読み書きするには?[Win 8/WP 8]」で解説した。

 今回紹介する方法は、表の中で「ファイル・ピッカー」が「利用可」または「必須」となっている場所においてテキスト・ファイルを読み書きできる。

サンプル画面

 ファイル・ピッカーを使ってテキスト・ファイルを読み書きするアプリの画面を、まず用意しておこう。今回のサンプルはLayoutAwarePageクラスを継承したページにする必要がある。その手順は、「WinRT/Metro TIPS:複数のバインディング・ソースを画面にバインドするには?[Win 8]」の*5を参照してほしい。

 LayoutAwarePageクラスを継承したページの用意ができたら、XAMLコードで次のような画面を作成する。

完成したアプリの外観 完成したアプリの外観
大きな白い部分はテキストボックスで、自由に文字を編集できる。
テキストボックスの上には、[テキスト・ファイルを開く]ボタンと[名前を付けて保存する]ボタンがある。

……省略……
<Grid x:Name="mainGrid" Grid.Row="1" Margin="120,0,40,40">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>

  <StackPanel x:Name="commandStack" Orientation="Horizontal" Margin="0,10,0,0">
    <Button x:Name="openButton" Tapped="openButton_Tapped"
            Content="テキスト・ファイルを開く" />
    <Button Tapped="saveButton_Tapped"
            Content="名前を付けて保存する" Margin="10,0,0,0" />
  </StackPanel>

  <TextBox x:Name="textbox1" Grid.Row="1" Margin="0,10,0,0"
            AcceptsReturn="True" TextWrapping="Wrap"
            ScrollViewer.VerticalScrollBarVisibility="Auto" />
</Grid>
……省略……

完成したアプリのコンテンツ部分のXAMLコード(XAML)
コードビハインドにイベント・ハンドラを記述していないので、まだビルドできない。

 画面のコンテンツ部分にはGridコントロール(Windows.UI.Xaml.Controls名前空間)を置き、ボタンを配置する行とテキストボックスを置く行の上下2行に分割した。ボタン2つは、StackPanelコントロール(Windows.UI.Xaml.Controls名前空間)の中に入れて、横に並べている。TextBoxコントロール(Windows.UI.Xaml.Controls名前空間)のプロパティは、複数行にわたるテキストを表示できるように設定してある。

 本稿では、スナップ状態でのアプリの挙動も見たいので、次の画像のようにスナップ状態でもきちんと表示されるようにXAMLコードを定義しておく。

完成したアプリの外観(スナップ状態)
スナップ時には、2つのボタンは上下に並べ替えて、テキストボックスの回りの余白は削る。 完成したアプリの外観(スナップ状態) スナップ時には、2つのボタンは上下に並べ替えて、テキストボックスの回りの余白は削る。

……省略……
<VisualStateManager.VisualStateGroups>

  <!-- Visual states reflect the application's view state -->
  <VisualStateGroup x:Name="ApplicationViewStates">
    <VisualState x:Name="FullScreenLandscape"/>
    <VisualState x:Name="Filled"/>

    <!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
    <VisualState x:Name="FullScreenPortrait">
    ……省略……
    </VisualState>

    <!-- The back button and title have different styles when snapped -->
    <VisualState x:Name="Snapped">
      <Storyboard>
        ……省略……

        <!-- ここから -->
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="mainGrid" Storyboard.TargetProperty="Margin">
          <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="commandStack" Storyboard.TargetProperty="Orientation">
          <DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="openButton" Storyboard.TargetProperty="Margin">
          <DiscreteObjectKeyFrame KeyTime="0" Value="10,0,0,0"/>
        </ObjectAnimationUsingKeyFrames>
        <!-- ここまで -->

      </Storyboard>
    </VisualState>
  </VisualStateGroup>
</VisualStateManager.VisualStateGroups>
……省略……

完成したアプリのスナップ状態を定義するXAMLコード(XAML)
VisualStateGroupの定義の中に、コメントの「ここから」と「ここまで」の間を追加する。

ファイル・オープン・ピッカーを使ってテキスト・ファイルを読み取るには?

 ファイル・オープン・ピッカーのオブジェクトを作り、ファイルの拡張子を必ず指定してから、PickSingleFileAsyncメソッド(または、複数のファイルを選択できるPickMultipleFilesAsyncメソッド)を呼び出せばよい。

 画面の[テキスト・ファイルを開く]ボタンのTappedイベント・ハンドラを、コードビハインドに次のように記述する。

private async void openButton_Tapped(object sender, TappedRoutedEventArgs e)
{
  // ファイル・オープン・ピッカーを用意する
  var openPicker
    = new Windows.Storage.Pickers.FileOpenPicker()
          {
            CommitButtonText = "ファイルを開く",
            SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop,
            ViewMode = Windows.Storage.Pickers.PickerViewMode.List,
            // 以上のプロパティ指定はオプション
          };
  openPicker.FileTypeFilter.Add(".txt");  // 最低1つの指定が必須

  // ファイル・オープン・ピッカーをユーザーに提示する
  Windows.Storage.StorageFile selectedFile = null;
  selectedFile = await openPicker.PickSingleFileAsync();
  if (selectedFile == null) // [CANCEL]されたときはnull
    return;

  // ファイルを読み取ってテキストボックスに表示する
  this.textbox1.Text = await Windows.Storage.FileIO.ReadTextAsync(selectedFile);
}

Private Async Sub openButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
  ' ファイル・オープン・ピッカーを用意する
  Dim openPicker _
      = New Windows.Storage.Pickers.FileOpenPicker() _
            With { _
              .CommitButtonText = "ファイルを開く",
              .SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop,
              .ViewMode = Windows.Storage.Pickers.PickerViewMode.List
            }
  ' 以上のプロパティ指定はオプション
  openPicker.FileTypeFilter.Add(".txt"' 最低1つの指定が必須

  ' ファイル・オープン・ピッカーをユーザーに提示する
  Dim selectedFile As Windows.Storage.StorageFile = Nothing
  selectedFile = Await openPicker.PickSingleFileAsync()
  If (selectedFile Is Nothing) Then ' [CANCEL]されたときはNothing
    Return
  End If

  ' ファイルを読み取ってテキストボックスに表示する
  Me.textbox1.Text = Await Windows.Storage.FileIO.ReadTextAsync(selectedFile)
End Sub

ファイル・オープン・ピッカーを使ってテキスト・ファイルを読み取る(上:C#、下:VB)
メソッド内部でawait/Awaitを使っているので、メソッド・シグネチャにasync/Asyncキーワードが必要だ。
なお、もう1つのボタンのイベント・ハンドラをまだ記述していないので、このままではビルドできない。ここで試したい場合は、[名前を付けて保存する]ボタンのTappedイベント・ハンドラも作成してほしい(メソッドの中身は空のままで構わない)。

 ファイル・ピッカーを使うときの注意点は、上のコード中にもコメントしたが、ファイル拡張子を最低でも1つは指定する必要があることだ。

 それ以外のプロパティは指定しなくても構わない。上のコードのように、OKボタンに表示する文字列(=CommitButtonTextプロパティ)と最初に提示するフォルダ(=SuggestedStartLocationプロパティ)は、指定するのが望ましい。また、ViewModeプロパティに「Thumbnail」を指定すると画像のサムネイルが表示されるので、画像ファイルを開く場合には適している。

 ファイル・オープン・ピッカーをユーザーに提示するには、PickSingleFileAsyncメソッドを呼び出す。ユーザーがファイルを選んで[OK]ボタン(上のコードでは[ファイルを開く]ボタンになっている)をクリックすると、PickSingleFileAsyncメソッドは選ばれたファイルのStorageFolderオブジェクト(Windows.Storage名前空間)を返してくるので、あとはそこから読み出すだけだ。

 実行して[テキスト・ファイルを開く]ボタンをタップすると、次の画像のようにファイル・オープン・ピッカーが画面全体に表示される。テキスト・ファイルを選んでから[ファイルを開く]ボタンをタップすれば、そのファイルが読み込まれてテキストボックスに表示される(ただし、読み込めるのはUnicodeなテキスト・ファイルだ。シフトJISでエンコーディングされているテキスト・ファイルを読み込もうとすると例外が発生するので注意しよう)。

ファイル・オープン・ピッカーが表示された ファイル・オープン・ピッカーが表示された

 ここで、ファイル・オープン・ピッカーの左上にある[ファイル]というタイトルのすぐ右にある[v]マークをタップしてみると、どんな場所のファイルにアクセスできるのか分かる。ローカル・ディスク内だけでなく、ネットワークやSkyDriveなどのファイルも読み書きできる。

ファイル・セーブ・ピッカーを使ってテキスト・ファイルを書き出すには?

 ファイル・セーブ・ピッカーのオブジェクトを作り、ファイルの拡張子を必ず指定してから、PickSaveFileAsyncメソッドを呼び出せばよい。

 画面の[名前を付けて保存する]ボタンのTappedイベント・ハンドラを、コードビハインドに次のように記述する。

private async void saveButton_Tapped(object sender, TappedRoutedEventArgs e)
{
  // ファイル・セーブ・ピッカーを用意する
  var savePicker
    = new Windows.Storage.Pickers.FileSavePicker()
          {
            CommitButtonText = "ファイルを保存する",
            DefaultFileExtension = ".txt",
            SuggestedFileName = "WinRTMetroSample",
            SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop,
            // 以上のプロパティ指定はオプション
          };
  // FileTypeは、最低1つの指定が必須
  savePicker.FileTypeChoices.Add("テキスト・ファイル", new List<string> { ".txt", });

  // ファイル・セーブ・ピッカーをユーザーに提示する
  Windows.Storage.StorageFile saveFile = null;
  saveFile = await savePicker.PickSaveFileAsync();
  if (saveFile == null) // [CANCEL]されたときはnull
    return;

  // テキストボックスの内容をファイルに書き込む
  await Windows.Storage.FileIO.WriteTextAsync(saveFile, this.textbox1.Text,
                                  Windows.Storage.Streams.UnicodeEncoding.Utf8);
}

Private Async Sub saveButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
  ' ファイル・セーブ・ピッカーを用意する
  Dim savePicker _
      = New Windows.Storage.Pickers.FileSavePicker() _
            With { _
              .CommitButtonText = "ファイルを保存する",
              .DefaultFileExtension = ".txt",
              .SuggestedFileName = "WinRTMetroSample",
              .SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop
            }
  ' 以上のプロパティ指定はオプション

  ' FileTypeは、最低1つの指定が必須
  savePicker.FileTypeChoices.Add("テキスト・ファイル", New List(Of String) From {".txt"})
  ' ※上記は、次の3行の省略記法
  'Dim fileTypeList = New List(Of String)()
  'fileTypeList.Add(".txt")
  'savePicker.FileTypeChoices.Add("テキスト・ファイル", fileTypeList)

  ' ファイル・セーブ・ピッカーをユーザーに提示する
  Dim saveFile As Windows.Storage.StorageFile = Nothing
  saveFile = Await savePicker.PickSaveFileAsync()
  If (saveFile Is Nothing) Then ' [CANCEL]されたときはNothing
    Return
  End If

  ' テキストボックスの内容をファイルに書き込む
  Await Windows.Storage.FileIO.WriteTextAsync(saveFile, Me.textbox1.Text, _
                                  Windows.Storage.Streams.UnicodeEncoding.Utf8)
  End Sub

ファイル・セーブ・ピッカーを使ってテキスト・ファイルを書き出す(上:C#、下:VB)
メソッド内部でawait/Awaitを使っているので、メソッド・シグネチャにasync/Asyncキーワードが必要だ。

 ファイル・セーブ・ピッカーでも、ファイル拡張子を最低でも1つは指定する必要がある。[OK]ボタンに表示する文字列(=CommitButtonTextプロパティ)と最初に提示するフォルダ(=SuggestedStartLocationプロパティ)、そして、提案するファイル名(=SuggestedFileName)は、指定するのが望ましい。

 ファイルを保存するときには、上書きの確認が気になるところだが、ユーザーが既存のファイルを指定した場合には上書きするかをファイル・セーブ・ピッカーがユーザーに尋ねてくれるので、コードでは気にする必要がない。PickSaveFileAsyncメソッドからファイルが返ってきたならば、ユーザーが「上書きOK」と答えたことを意味する。

 実際に実行して既存のファイルに保存しようとすると、次の画像のようなメッセージ・ダイアログが表示される。ここで[はい]を選べば、ファイル・セーブ・ピッカーは閉じてアプリにStorageFileオブジェクトが返されるので、上書き保存される。[いいえ]を選んだ場合は、ファイル・セーブ・ピッカーのファイル一覧画面に戻るだけだ。

ファイル・セーブ・ピッカーで既存のファイルに上書きしようとした ファイル・セーブ・ピッカーで既存のファイルに上書きしようとした

スナップ状態ではどうなるか?

 ここまでに作ったアプリを、スナップ状態にして使ってみてほしい。ファイル・ピッカーを出そうとすると、Windows 8では例外が出てアプリは終了してしまう。Windows 8のスナップ状態では、ファイル・ピッカーを出せないのだ。

 ちなみに、同じアプリをWindows 8.1*1にインストールして実行してみると、スナップ状態でもファイル・ピッカーが出る(次の画像)。Windows 8.1では、アプリの幅がどんな状態だろうとファイル・ピッカーが表示されるように変更されたのだ。

Windows 8.1 Previewの上で動かした場合は、スナップ状態でもファイル・ピッカーが出る
Windows 8では、スナップ状態でファイル・ピッカーを使うと例外が発生する。Windows 8.1では、このようにスナップ状態でもファイル・ピッカーが表示されるようになった。 Windows 8.1 Previewの上で動かした場合は、スナップ状態でもファイル・ピッカーが出る
Windows 8では、スナップ状態でファイル・ピッカーを使うと例外が発生する。Windows 8.1では、このようにスナップ状態でもファイル・ピッカーが表示されるようになった。

*1 執筆時点でのWindows 8.1はPreview版であり、製品版では変更される可能性もある。


 では、例外が出てしまうWindows 8ではどうしたらよいだろうか? ApplicationViewクラス(Windows.UI.ViewManagement名前空間)を使うとスナップ状態の解除を試みることができる。

 例えばファイル・オープン・ピッカーをユーザーに提示する部分は、次のように変更する。

// ファイル・オープン・ピッカーをユーザーに提示する
Windows.Storage.StorageFile selectedFile = null;

// selectedFile = await openPicker.PickSingleFileAsync();
// if (selectedFile == null) // [CANCEL]されたときはnull
//   return;
//
// ↓

try
{
  selectedFile = await openPicker.PickSingleFileAsync();
  if (selectedFile == null) // [CANCEL]されたときはnull
    return;
}
catch
{
  if (ApplicationView.Value == ApplicationViewState.Snapped)
  {
    // スナップ状態ではファイル・ピッカーを開けないので、スナップ解除(Win8.0の場合)
    if (!ApplicationView.TryUnsnap())
      return; // スナップ解除できなかったので、あきらめる
  }
}
if (selectedFile == null) // ファイル・ピッカーを開けなかったので、もう一度試す
  selectedFile = await openPicker.PickSingleFileAsync();

if (selectedFile == null) // [CANCEL]されたときはnull
  return;

' ファイル・オープン・ピッカーをユーザーに提示する
Dim selectedFile As Windows.Storage.StorageFile = Nothing

' selectedFile = Await openPicker.PickSingleFileAsync()
' If (selectedFile Is Nothing) Then ' [CANCEL]されたときはNothing
'   Return
' End If
'
' ↓

Try
  selectedFile = Await openPicker.PickSingleFileAsync()
  If (selectedFile Is Nothing) Then ' [CANCEL]されたときはNothing
    Return
  End If
Catch
  If (ApplicationView.Value = ApplicationViewState.Snapped) Then
    ' スナップ状態では、ファイル・ピッカーを開けないので、スナップ解除(Win8.0の場合)
    If (Not ApplicationView.TryUnsnap()) Then
      Return ' スナップ解除できなかったので、あきらめる
    End If
  End If
End Try
If (selectedFile Is Nothing) Then ' ファイル・ピッカーを開けなかったので、もう一度試す
  selectedFile = Await openPicker.PickSingleFileAsync()
End If
If (selectedFile Is Nothing) Then ' [CANCEL]されたときはNothing
  Return
End If

ファイル・オープン・ピッカーで例外が出たときに、スナップ解除を試みる(上:C#、下:VB)
このほかにC#では、ファイルの先頭でWindows.UI.ViewManagement名前空間の取り込みが必要だ。

 上のコードでは、ファイル・オープン・ピッカーを提示しようとして例外が出たとき、現在スナップ状態であるならばApplicationViewクラスのTryUnsnapメソッドを呼び出してスナップ状態の解除を試みる。そしてもう一度ファイル・オープン・ピッカーを提示している。ファイル・セーブ・ピッカーの方も、同様に変更する。

 なお、このTryUnsnapメソッドは、ユーザーのアクション(ボタンのタップなど)がトリガーになっていないと働かないので注意してほしい。アプリ側の好きなタイミングで勝手にスナップを解除することはできないのだ。

まとめ

 ファイル・ピッカーを使えば、ユーザーが指定したファイルを読み書きできる。ただし、Windows 8のスナップ状態ではファイル・ピッカーが出ないので、対処が必要だ。

 ファイル・ピッカーについては、次のドキュメントも参照してほしい。

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

WinRT/Metro TIPS

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

@IT Special

- PR -

TechTargetジャパン

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

Focus

- PR -

RSSについて

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

メールマガジン登録

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