アプリケーション・データ記憶域でファイルを作成/削除/コピーしたり列挙したりする方法、そしてテキスト・ファイルの内容を読み書きする方法を説明する。
powered by Insider.NET
アプリ・パッケージに同梱したテキスト・ファイルは、読み取れるけれども(「WinRT/Metro TIPS アプリに同梱したテキスト・ファイルを読むには?[Win 8/WP 8]」を参照)、書き込めない。インストール後に変更を必要としないファイルならよいが、アプリでユーザーが設定するオプションや、アプリの状態などを保存したいときに、ファイルを自由に扱えないのでは困ってしまう。Windowsストア・アプリでは、アプリごとに自由にファイルを扱える場所が用意されている。それが「アプリケーション・データ記憶域」だ。
そこで本稿では、アプリケーション・データ記憶域でファイルを作成/削除/コピーしたり列挙したりする方法、そしてテキスト・ファイルの内容を読み書きする方法を説明する。本稿のサンプルは「Windows Store app samples:MetroTips #42(Windows 8版)」と「Windows Store app samples:MetroTips #42(WP 8版)」からダウンロードできる。
なお、掲載しているコードは特記なき場合はWindowsストア・アプリとWindows Phone 8(以降、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(無償)が必要となる。
●アプリケーション・データ記憶域
アプリケーション・データ記憶域は、アプリごとに割り当てられたローカル・ストレージ(=HDDなど)のフォルダであり、この領域にあるファイルにはアプリから自由にアクセスできる。ファイルを作成したりコピーしたり、あるいはフォルダ内のファイルを列挙したり、それらのファイルを読み書きしたりもできる。アプリケーション・データ記憶域は、アプリごとのユーザー設定や、アプリの動作に必要なデータなどを保存するといった用途が想定されている*1。
アプリケーション・データ記憶域には、次の表に示す3種類がある。いずれも、ApplicationDataクラス(Windows.Storage名前空間)を利用してアクセスする。これら3つのプロパティはいずれもStorageFolderクラス(Windows.Storage名前空間)のオブジェクトだ。
名称 | ApplicationDataオブジェクトのプロパティ | URI | Win 8 | WP 8 | ローカルとの相違点 |
---|---|---|---|---|---|
ローカル | LocalFolder | ms-appdata:///local/ | ○ | ○ | --- |
ローミング | RoamingFolder | ms-appdata:///roaming/ | ○ | ×*2 | ユーザーの保有する他のデバイスと自動的に同期される*3 |
一時 | TemporaryFolder | ms-appdata:///temporary/ | ○ | ×*2 | システムによって削除されることがある |
アプリケーション・データ記憶域の種類 |
本稿ではローカル・アプリケーション・データ記憶域(以降、ローカル・ストア)のファイルへのアクセス方法を説明するが、3つの記憶域(=上の表のプロパティ)でStorageFolderオブジェクトの取得方法が異なる以外は、その使い方は同じである。
*1 アプリケーション・データ記憶域は、アプリがアンインストールされるときには消されてしまう。従って、アンインストール後もユーザーが必要とするデータを保管してはいけない。
*2 ローミングと一時データ記憶域は、WP 8ではAPIは存在するが実装されていない。
*3 同期されるとApplicationData.Current.DataChangedイベントが発生するので、ファイルを読み込み直す必要がある。また、同期される容量には制限がある。
●本稿で作成するアプリの外観
完成したアプリの外観と、XAMLコードを先に掲載しておこう。
……省略……
<!-- ローカル・ストアの実際の場所 -->
<TextBlock x:Name="textLocalStorage" ……省略…… />
……省略……
<Button Tapped="createButton_Tapped" Content="test.txtファイルを作成" />
<Button Tapped="copyButton_Tapped" Content="アプリ同梱のファイルをコピー" />
<Button Tapped="listButton_Tapped" Content="ローカル・ストアのファイルを列挙" />
<Button Tapped="deleteButton_Tapped" Content="ローカル・ストアのファイルを削除" />
……省略……
<!-- ローカル・ストアのファイル一覧 -->
<ListBox x:Name="fileNameList"
SelectionChanged="fileNameList_SelectionChanged" ……省略…… />
<!-- 読み取ったファイルの内容(編集可) -->
<TextBox x:Name="editTextBox" TextWrapping="Wrap" AcceptsReturn="True" ……省略…… />
<Button Tapped="overwriteButton_Tapped" Content="上書き保存" ……省略…… />
……省略……
●ローカル・ストアの実際の場所
ローカル・ストアの実際の場所が分からないと、テストやデバッグのときに困ることもあるかもしれない。確かめておこう。
まず、ローカル・ストアの場所を表示するためのTextBlockコントロールを画面の適当な場所に配置し、「textLocalStorage」と名前を付けておく。そうしたら、コードビハインドで次のコードのようにして、ローカル・ストアの実際の場所をApplicationDataオブジェクトから取得し、TextBlockコントロールに表示する。
this.textLocalStorage.Text
= Windows.Storage.ApplicationData.Current.LocalFolder.Path;
Me.textLocalStorage.Text _
= Windows.Storage.ApplicationData.Current.LocalFolder.Path
これで実行して表示されるのが、ローカル・ストアの実際の場所だ。例えば次のように表示されるだろう。
[Win 8の例]*4
D:\Users\biac\AppData\Local\Packages\140b61e0-1810-4335-83de-b77ce4350c76_88y8q2a7hew66\LocalState
[WP 8の例]
C:\Data\Users\DefApps\AppData\{1779174E-1ED5-473E-B5B7-3F951C6B37A8}\Local
なお、パスの途中に長い識別子が入っているが、これはWindowsストア・アプリではパッケージ名*5、WP 8アプリではプロダクトID*6である。
*4 筆者の開発用PCは、DドライブにWindows 8をインストールしてあるため、このような場所になっている。通常はCドライブである。
*5 Windowsストア・アプリのパッケージ名は、Visual StudioでPackage.appxmanifestファイルをダブルクリックして開くと、その編集画面の[パッケージ化]タブに記載されている。なお、Windowsストアとアプリの関連付けを行うと、自動的に書き換えられる。
*6 WP 8アプリのプロダクトIDは、Visual StudioでPropertiesフォルダのWMAppManifest.xmlファイルをダブルクリックして開くと、その編集画面の[パッケージ化]タブに記載されている。
●ローカル・ストアにファイルを作成するには?
StorageFolderオブジェクトのCreateFileAsyncメソッドを使う。
画面の適当な場所にButtonコントロールを配置し、そのTappedイベント・ハンドラ(WP 8ではClickイベント・ハンドラ)で、次のコードのように実装する。ApplicationDataオブジェクトからローカル・ストアを表すStorageFolderオブジェクトを取得したら、そのCreateFileAsyncメソッドを使ってファイルを作成する。
private async void createButton_Tapped(object sender, TappedRoutedEventArgs e)
{
Windows.Storage.StorageFolder folder
= Windows.Storage.ApplicationData.Current.LocalFolder;
Windows.Storage.StorageFile newFile
= await folder.CreateFileAsync("test.txt");
}
Private Async Sub createButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
Dim folder As Windows.Storage.StorageFolder _
= Windows.Storage.ApplicationData.Current.LocalFolder
Dim newFile As Windows.Storage.StorageFile _
= Await folder.CreateFileAsync("test.txt")
End Sub
上のコードでは、すでに同名のファイルが存在していると例外が発生する。そのようなときにどう処理するかを、第2引数にCreationCollisionOption列挙体(Windows.Storage名前空間)として指定できる。例えば、すでにファイルが存在していたときには、そのファイルを開くことにするなら、次のようになる。
private async void createButton_Tapped(object sender, TappedRoutedEventArgs e)
{
Windows.Storage.StorageFolder folder
= Windows.Storage.ApplicationData.Current.LocalFolder;
Windows.Storage.StorageFile newFile
= await folder.CreateFileAsync("test.txt",
Windows.Storage.CreationCollisionOption.OpenIfExists);
}
Private Async Sub createButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
Dim folder As Windows.Storage.StorageFolder _
= Windows.Storage.ApplicationData.Current.LocalFolder
Dim newFile As Windows.Storage.StorageFile _
= Await folder.CreateFileAsync("test.txt", _
Windows.Storage.CreationCollisionOption.OpenIfExists)
End Sub
なお、このコードで実際にファイルが作成されたことを確認するには、後述する「ローカル・ストアのファイルを列挙するには?」のコードを使ってほしい。
●ローカル・ストアにファイルをコピーするには?
StorageFileオブジェクトのCopyAsyncメソッドを使う。
よくあるアプリのパターンとして、インストールされてから最初の起動時に、アプリ同梱の設定ファイル(初期値が記述されているもの)をローカル・ストアにコピーすることがある。1回コピーした後は、アプリからそのローカル・ストアにある設定ファイルを読み書きするのだ。
アプリ同梱のテキスト・ファイルをローカル・ストアにコピーしてみよう。
まず、「WinRT/Metro TIPS アプリに同梱したテキスト・ファイルを読むには?[Win 8/WP 8]」と同様にして、プロジェクト内に「text」というフォルダを作り、そこに「sample01.txt」〜「sample03.txt」の3つのテキスト・ファイルを用意してほしい。
次に、画面の適当な場所にButtonコントロールを配置し、そのTappedイベント・ハンドラ(WP 8ではClickイベント・ハンドラ)で、次のように、アプリ同梱のファイルをコピーするコードを実装する。コピー元とコピー先のStorageFolderオブジェクトを取得したら、GetFilesAsyncメソッドを使ってコピー元のフォルダにあるファイルを列挙し、得られたStorageFileオブジェクトのCopyAsyncメソッドを呼び出す。2つのStorageFolderオブジェクトを得る方法が、それぞれの場所によって異なることに注意してほしい。
private async void copyButton_Tapped(object sender, TappedRoutedEventArgs e)
{
// アプリ・パッケージ内のtextフォルダを表すStorageFolderオブジェクト
Windows.Storage.StorageFolder textFolder
= await Windows.ApplicationModel.Package.Current
.InstalledLocation.GetFolderAsync("text");
// ローカル・ストア(=コピー先)を表すStorageFolderオブジェクト
Windows.Storage.StorageFolder destFolder
= Windows.Storage.ApplicationData.Current.LocalFolder;
// textフォルダ内の全ファイルを列挙し、それぞれをローカル・ストアへコピーする
foreach (Windows.Storage.StorageFile srcFile in await textFolder.GetFilesAsync())
{
Windows.Storage.StorageFile copiedFile = null;
try
{
copiedFile = await srcFile.CopyAsync(destFolder);
}
catch { }
}
}
Private Async Sub copyButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
' アプリ・パッケージ内のtextフォルダを表すStorageFolderオブジェクト
Dim textFolder As Windows.Storage.StorageFolder _
= Await Windows.ApplicationModel.Package.Current _
.InstalledLocation.GetFolderAsync("text")
' ローカル・ストア(=コピー先)を表すStorageFolderオブジェクト
Dim destFolder As Windows.Storage.StorageFolder _
= Windows.Storage.ApplicationData.Current.LocalFolder
' textフォルダ内の全ファイルを列挙し、それぞれをローカル・ストアへコピーする
For Each srcFile As Windows.Storage.StorageFile In Await textFolder.GetFilesAsync()
Dim copiedFile As Windows.Storage.StorageFile = Nothing
Try
copiedFile = Await srcFile.CopyAsync(destFolder)
Catch
End Try
Next
End Sub
CopyAsyncメソッドで少々困ることは、実際のコピー中に何らかの原因で失敗したときと、すでに同名のファイルが存在していてコピーを実行できなかったときで、どちらの場合も同じSystem.Exception例外が発生するためにその区別がつかないことだ。例外が発生してもローカル・ストアでファイルを開いてみて、もしも開くことができたら、すでに同名のファイルが存在していたと判断できる(上のコードには実装していない)。あるいは、丁寧にやるならば、ローカル・ストアにファイルを作成して開き、アプリ・パッケージ内のファイルから読み込んだ内容を書き込むようにする。
●ローカル・ストアのファイルを列挙するには?
StorageFolderオブジェクトのGetFilesAsyncメソッドを使う。
すでに先ほどのコードで、アプリ・パッケージ内のファイルを列挙しているが、今度はローカル・ストアにあるファイルを列挙して、そのファイル名を表示してみよう。
画面の適当な場所にもう1つButtonコントロールを配置する。さらに、ListBoxコントロールも配置して「fileNameList」と名前を付けておく。そうしたら、そのButtonコントロールのTappedイベント・ハンドラ(WP 8ではClickイベント・ハンドラ)に次のコードを記述する。
private async void listButton_Tapped(object sender, TappedRoutedEventArgs e)
{
await ListAllFilesAsync();
}
private async System.Threading.Tasks.Task ListAllFilesAsync()
{
// ローカル・ストアを表すStorageFolderオブジェクト
Windows.Storage.StorageFolder folder
= Windows.Storage.ApplicationData.Current.LocalFolder;
// フォルダ内の全ファイルを列挙し、ListBoxにバインドする
this.fileNameList.ItemsSource = await folder.GetFilesAsync();
// ListBoxには、与えたStorageFolderオブジェクトのNameプロパティを表示させる
this.fileNameList.DisplayMemberPath = "Name";
}
Private Async Sub listButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
Await ListAllFilesAsync()
End Sub
Private Async Function ListAllFilesAsync() As System.Threading.Tasks.Task
' ローカル・ストアを表すStorageFolderオブジェクト
Dim folder As Windows.Storage.StorageFolder _
= Windows.Storage.ApplicationData.Current.LocalFolder
' フォルダ内の全ファイルを列挙し、ListBoxにバインドする
Me.fileNameList.ItemsSource = Await folder.GetFilesAsync()
' ListBoxには、与えたStorageFolderオブジェクトのNameプロパティを表示させる
Me.fileNameList.DisplayMemberPath = "Name"
End Function
上のコードで、ファイル名をListBoxコントロールに表示する処理をListAllFilesAsyncメソッドとして独立させた。このメソッドの呼び出しを前述のcreateButton_TappedメソッドとcopyButton_Tappedメソッドの末尾にも追加しておいてほしい。すると、ファイルを作成/コピーしたときにもリストが更新されるようになる。
ちなみにWin 8では、StorageFileQueryResultクラスのContentsChangedイベントを利用することで、フォルダ内のファイルに追加/削除/変更があったときにファイルの一覧表示を更新することも可能だ。
●ローカル・ストアのファイルを削除するには?
StorageFileオブジェクトのDeleteAsyncメソッドを使う。
先ほどローカル・ストアにコピーしたファイルを、今度は全部削除してみよう。
画面の適当な場所にもう1つButtonコントロールを配置する。そのTappedイベント・ハンドラ(WP 8ではClickイベント・ハンドラ)で、ローカル・ストアを表すStorageFolderオブジェクトを取得したら、そこにあるファイルをStorageFileオブジェクトとして列挙し、それぞれでDeleteAsyncメソッドを呼び出せばよい。
private async void deleteButton_Tapped(object sender, TappedRoutedEventArgs e)
{
// ローカル・ストアを表すStorageFolderオブジェクト
Windows.Storage.StorageFolder folder
= Windows.Storage.ApplicationData.Current.LocalFolder;
// フォルダ内の全ファイルを列挙し、それぞれ削除する
foreach (Windows.Storage.StorageFile file in await folder.GetFilesAsync())
{
try
{
await file.DeleteAsync();
}
catch { }
}
// ListBoxコントロールのファイル一覧を更新
await ListAllFilesAsync();
}
Private Async Sub deleteButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
' ローカル・ストアを表すStorageFolderオブジェクト
Dim folder As Windows.Storage.StorageFolder _
= Windows.Storage.ApplicationData.Current.LocalFolder
' フォルダ内の全ファイルを列挙し、それぞれ削除する
For Each file As Windows.Storage.StorageFile In Await folder.GetFilesAsync()
Try
Await file.DeleteAsync()
Catch
End Try
Next
' ListBoxコントロールのファイル一覧を更新
Await ListAllFilesAsync()
End Sub
また、ファイル名が分かっている特定の1ファイルを削除するだけならば、StorageFolderオブジェクトのGetFileAsyncメソッド*7を使ってそのファイルのStorageFileオブジェクトを直接取得し*8、DeleteAsyncメソッドで削除すればよい。
*7 上のコードで列挙に使ったのはGetFilesAsyncメソッド。ファイルを1つだけ取得するのはGetFileAsyncメソッド。紛らわしいが、メソッド名の中のFileが複数か単数かという違いだ。
*8 ファイル名をURIで指定してStorageFileクラスのGetFileFromApplicationUriAsyncメソッドを使うことでも、そのファイルのStorageFileオブジェクトを取得できる。
●ローカル・ストアのテキスト・ファイルを読み取るには?
StorageFileオブジェクトを取得できたら、後はアプリ同梱のテキスト・ファイルを読み取る方法(「WinRT/Metro TIPS アプリに同梱したテキスト・ファイルを読むには?[Win 8/WP 8]」で説明した)と同様である。ただし、URIで直接ファイルを指定する場合のスキーマは「ms-appx:///」ではなく、「ms-appdata:///」を用いる(冒頭に掲載した表を参照)。
ここでは、列挙したStorageFileオブジェクトの中からユーザーに選択してもらい、それを読み取ってTextBoxコントロールに表示してみよう。
まず、前述の「ローカル・ストアのファイルを列挙するには?」で作成したListBoxコントロール「fileNameList」に、SelectionChangedイベント・ハンドラを追加し「fileNameList_SelectionChanged」という名前にしておく。fileNameListには、StorageFileオブジェクトのコレクションがバインドしてあるから、ユーザーがこのListBoxコントロールで選択したファイルのStorageFileオブジェクトを、SelectionChangedイベント・ハンドラ内で取得できるのだ。次に、読み込んだテキスト・ファイルの内容を表示するためのTextBoxコントロールを、画面の適当な場所に配置し、「editTextBox」と名前を付けておく。
SelectionChangedイベント・ハンドラで、次のコードのようにしてユーザーに選択されたテキスト・ファイルを読み取ってTextBoxコントロールに表示する。
private async void fileNameList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.editTextBox.Text = string.Empty;
// ユーザーに選択されたファイル
Windows.Storage.StorageFile selectedFile
= this.fileNameList.SelectedItem as Windows.Storage.StorageFile;
if (selectedFile == null)
return;
// StorageFileオブジェクトからファイルを開いて読み込み、TextBoxに表示する
using (Stream st = (await selectedFile.OpenReadAsync()).AsStream())
using (TextReader reader = new StreamReader(st))
{
this.editTextBox.Text = await reader.ReadToEndAsync();
}
}
Private Async Sub fileNameList_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
Me.editTextBox.Text = String.Empty
' ユーザーに選択されたファイル
Dim selectedFile As Windows.Storage.StorageFile _
= DirectCast(Me.fileNameList.SelectedItem, Windows.Storage.StorageFile)
If (selectedFile Is Nothing) Then
Return
End If
' StorageFileオブジェクトからファイルを開いて読み込み、TextBoxに表示する
Using st As Stream = (Await selectedFile.OpenReadAsync()).AsStream(),
reader As TextReader = New StreamReader(st)
Me.editTextBox.Text = Await reader.ReadToEndAsync()
End Using
End Sub
なお、Win 8では、次のように簡単に書ける。
// StorageFileオブジェクトからファイルを開いて読み込み、TextBoxに表示する
// using (Stream st = (await selectedFile.OpenReadAsync()).AsStream())
// using (TextReader reader = new StreamReader(st))
// {
// this.editTextBox.Text = await reader.ReadToEndAsync();
// }
// ↓
// WP 8との互換を取らなくてもよいなら、1行で書ける
this.editTextBox.Text = await Windows.Storage.FileIO.ReadTextAsync(selectedFile);
' StorageFileオブジェクトからファイルを開いて読み込み、TextBoxに表示する
' Using st As Stream = (Await selectedFile.OpenReadAsync()).AsStream(),
' reader As TextReader = New StreamReader(st)
'
' Me.editTextBox.Text = Await reader.ReadToEndAsync()
' End Using
' ↓
' WP 8との互換を取らなくてもよいなら、1行で書ける
Me.editTextBox.Text = Await Windows.Storage.FileIO.ReadTextAsync(selectedFile)
このWin 8専用のFileIOクラス(Windows.Storage名前空間)には、1行ずつ読み込むReadLinesAsyncメソッドなどの便利なメソッドがあるので、WP 8との互換性を考えなくてよいときは活用してほしい。
●ローカル・ストアのテキスト・ファイルに書き込むには?
読み込みとよく似た手順になる。ここでは、前述のTextBoxコントロールに表示されている文字列を、ユーザーがListBoxコントロールで選択しているファイルに保存してみよう。
画面の適当な場所にもう1つButtonコントロールを配置する。そのTappedイベント・ハンドラ(WP 8ではClickイベント・ハンドラ)に次のように記述する。
private async void overwriteButton_Tapped(object sender, TappedRoutedEventArgs e)
{
// 書き込む内容
string text = this.editTextBox.Text;
// ユーザーに選択されたファイル
Windows.Storage.StorageFile selectedFile
= this.fileNameList.SelectedItem as Windows.Storage.StorageFile;
if (selectedFile == null)
return;
// StorageFileオブジェクトからファイルを開いて書き込む
using (Stream st = (await
selectedFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite)).AsStream())
using (TextWriter writer = new StreamWriter(st))
{
await writer.WriteAsync(text);
}
}
Private Async Sub overwriteButton_Tapped(sender As Object, e As TappedRoutedEventArgs)
' 書き込む内容
Dim text As String = Me.editTextBox.Text
' ユーザーに選択されたファイル
Dim selectedFile As Windows.Storage.StorageFile _
= DirectCast(Me.fileNameList.SelectedItem, Windows.Storage.StorageFile)
If (selectedFile Is Nothing) Then
Return
End If
' StorageFileオブジェクトからファイルを開いて書き込む
Using st As Stream = (Await _
selectedFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite)).AsStream(),
writer As TextWriter = New StreamWriter(st)
Await writer.WriteAsync(text)
End Using
End Sub
なお、存在しないファイルに書き込むことはできない。その場合は、まず空のファイルを作成してから書き込むようにする。
また、読み込みと同様に書き込みでも、Win 8では次のように簡単に書ける。
// StorageFileオブジェクトからファイルを開いて書き込む
// using (Stream st = (await selectedFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite)).AsStream())
// using (TextWriter writer = new StreamWriter(st))
// {
// await writer.WriteAsync(text);
// }
// ↓
// WP 8との互換を取らなくてもよいなら、1行で書ける
await Windows.Storage.FileIO.WriteTextAsync(selectedFile, text);
' StorageFileオブジェクトからファイルを開いて書き込む
' Using st As Stream = (Await selectedFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite)).AsStream(),
' writer As TextWriter = New StreamWriter(st)
'
' Await writer.WriteAsync(text)
' End Using
' ↓
' WP 8との互換を取らなくてもよいなら、1行で書ける
Await Windows.Storage.FileIO.WriteTextAsync(selectedFile, text)
●まとめ
アプリケーション・データ記憶域では、ApplicationDataクラスを使ってStorageFolderオブジェクトを取得する。アプリケーション・データ記憶域では、自由にファイルのアクセスができるので、StorageFolderオブジェクトを使ってファイルを列挙し、得られたStorageFileオブジェクトを使ってファイルの作成/コピー/削除を行える。また、StorageFileオブジェクトとStreamクラスやTextReader/TextWriterクラス(Win 8ではFileIOクラスでも可)を使って、テキスト・ファイルの読み書きができる。
Copyright© Digital Advantage Corp. All Rights Reserved.