読者です 読者をやめる 読者になる 読者になる

SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

UWPでのファイル・ストレージ操作

今まで、ちゃんとしたUWPアプリ作ってなかったので、ファイル操作などはほとんど扱ってませんでした。。。
ですが、本格的なアプリを作ろうと思うと、この手のファイル操作はが必要になる場面は多いですよね。

ということで、ファイル・ストレージ操作でよく使いそうなものをメモしておこうと思います。

UWPではセキュリティ確保などの観点から、ストレージなどへのアクセスは色々と制限されています。
そのため、UWPではユーザーのファイルにアクセスする場合は、以下のどちらかの手段を用いる必要があります。

  • 各種Pickerを通してユーザーが選択したファイル/フォルダにアクセスする
  • KnownFoldersとして定義されている場所へのアクセス(ピクチャライブラリなど)

また、通常のファイル/フォルダとは別に、アプリ専用のストレージなども用意されています。

  • ローカルのアプリ用ストレージ
  • ローミングのアプリ用ストレージ(Microsoftアカウントなどに紐づけて、複数端末でデータの同期ができるもの)

ファイル/フォルダ選択ダイアログ

ファイルやフォルダを選択するダイアログを開くには、FilePicker/FolderPickerなどといったピッカーというものを使用します。

ファイルを開くダイアログ

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var filePicker = new Windows.Storage.Pickers.FileOpenPicker();

            filePicker.FileTypeFilter.Add(".jpg");
            filePicker.FileTypeFilter.Add(".bmp");
            filePicker.FileTypeFilter.Add("*");

            // 単一ファイルの選択
            var file = await filePicker.PickSingleFileAsync();
            if (file != null)
            {
                var dlg = new MessageDialog(file.Name);
                await dlg.ShowAsync();
            }

            // 複数選択
            var files = await filePicker.PickMultipleFilesAsync();
            var result = string.Empty;
            foreach (var f in files)
            {
                result += f.Name + System.Environment.NewLine;
            }

            if (!string.IsNullOrEmpty(result))
            {
                var dlg = new MessageDialog(result);
                await dlg.ShowAsync();
            }
        }

f:id:minami_SC:20170506132910p:plain

ちなみに、Mobileの方で実行するとこんな感じ。
f:id:minami_SC:20170506133541p:plain
(エミュレータで実行したので、英語表示ですが・・・)

フォルダを開くダイアログ

ファイルと同じような感じです。
FolderPickerというクラスを使うと、フォルダを開くダイアログが表示できます。

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var folderPicker = new Windows.Storage.Pickers.FolderPicker();
            folderPicker.FileTypeFilter.Add("*");

            // フォルダの選択
            var folder = await folderPicker.PickSingleFolderAsync();
            if (folder != null)
            {
                var dlg = new MessageDialog(folder.Name);
                await dlg.ShowAsync();
            }
        }

f:id:minami_SC:20170506132925p:plain

ファイルの保存ダイアログ

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var filePicker = new Windows.Storage.Pickers.FileSavePicker();
            filePicker.FileTypeChoices.Add("テキストファイル", new string[] { ".txt" });
            filePicker.SuggestedFileName = "新規テキスト";

            // 単一ファイルの選択
            var file = await filePicker.PickSaveFileAsync();
            if (file != null)
            {
                var dlg = new MessageDialog(file.Name);
                await dlg.ShowAsync();
            }
        }

f:id:minami_SC:20170506133008p:plain

ファイルの読み書き

今度は、実際にファイルの内容を読み込んだり、ファイルにデータを書き込んだりしてみます。

FilePickerなどを用いると、StorageFile形式のオブジェクトが取得できます。
ファイルの読み書きは、このStorageFileオブジェクトに対して行います。

ファイルへの書き込み
            // 単一ファイルの選択
            var file = await filePicker.PickSaveFileAsync();
            if (file != null)
            {
                await Windows.Storage.FileIO.WriteTextAsync(file, "Hello World!!");
            }
ファイルからの読み込み
            var file = await filePicker.PickSingleFileAsync();
            if (file != null)
            {
                var text = await Windows.Storage.FileIO.ReadTextAsync(file);
                var dlg = new MessageDialog(text);
                await dlg.ShowAsync();
            }
ファイル書き込みの結果チェック

CachedFileManager.CompleteUpdatesAsyncメソッドで、ファイル操作結果の状態を確認できます。
このメソッドの戻り値をチェックすることで、ファイル書き込みが正常にできたかを確認できます。

            var file = await filePicker.PickSaveFileAsync();
            if (file != null)
            {
                await Windows.Storage.FileIO.WriteTextAsync(file, "Hello World!!");

                var result = await Windows.Storage.CachedFileManager.CompleteUpdatesAsync(file);
                if (result == Windows.Storage.Provider.FileUpdateStatus.Complete)
                {
                    var dlg = new MessageDialog("書き込み成功");
                    await dlg.ShowAsync();
                }
                else
                {
                    var dlg = new MessageDialog("書き込み失敗");
                    await dlg.ShowAsync();
                }
            }

ストレージ操作

UWPには、通常のファイル/フォルダとは別に、アプリ専用のデータ保存領域があります。

保存するデータの形式と保存場所が異なる、以下のようなストレージが用意されています。

  • 保存する形式
    • ○○Folder・・・・通常のフォルダのように、任意のファイルを保存できるストレージ
    • ○○Storage・・・Key/Valueペアの形式でデータを保存するストレージ
  • データの保存場所
    • Local〇〇・・・アプリをインストールしている端末上だけで利用できるストレージ
    • Roaming〇〇・・・ユーザーアカウントに紐づけられ、複数の端末で保存内容が同期されるストレージ

まとめると、こんな感じですね。
f:id:minami_SC:20170506133020p:plain

LocalFolder/RoamingFolder

LocalFolderと書いてる部分をRoamingFolderに変更すれば、ローミングを用いた保存内容の同期ができます。

書き込み
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalFolderへの書き込み
            var storage = Windows.Storage.ApplicationData.Current.LocalFolder;
            var file = await storage.CreateFileAsync("sample.txt", Windows.Storage.CreationCollisionOption.ReplaceExisting);
            await Windows.Storage.FileIO.WriteTextAsync(file, "Hello World!!");
        }
読み込み
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalFolderからの読み込み
            try
            {
                var storage = Windows.Storage.ApplicationData.Current.LocalFolder;
                var file = await storage.GetFileAsync("sample.txt");
                var text = await Windows.Storage.FileIO.ReadTextAsync(file);

                var dlg = new MessageDialog(text);
                await dlg.ShowAsync();

            }
            catch (Exception)
            {
                // ファイルが存在しない場合の処理
            }
        }

LocalSettings/RoamingSettings

こちらも同様に、LocalSettingsの部分をRoamingSettingsに変えると、端末間での保存内容の同期ができます。

書き込み
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalSettingsへの書き込み
            var container = Windows.Storage.ApplicationData.Current.LocalSettings;
            container.Values["Sample"] = "Hello World!!";
        }
読み込み
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // LocalSettingsからの読み込み
            var container = Windows.Storage.ApplicationData.Current.LocalSettings;
            var data = container.Values["Sample"];
            if (data != null)
            {
                var text = (string)data;
                var dlg = new MessageDialog(text);
                await dlg.ShowAsync();
            }
        }

その他

KnownFolderへのアクセス

ピクチャライブラリや、ドキュメントなどなど、

            var storage = Windows.Storage.KnownFolders.PicturesLibrary;
            var files = await storage.GetFilesAsync();
            var folderPicker = new Windows.Storage.Pickers.FolderPicker();

            // ピクチャライブラリの一番目のファイル要素を取得
            var firstItem = files.First();
            var dlg = new MessageDialog(firstItem.Name);
            await dlg.ShowAsync();

この例のPictureLibraryにアクセスするためには、プロジェクトのプロパティのパッケージマニフェスト画面を開き、以下のように「ピクチャライブラリ」の機能にチェックを付けておく必要があります。
f:id:minami_SC:20170506133036p:plain

MostRecentlyUsedList/FutureAccessList

FilePickerやFolderPickerなどを使って、ユーザにファイル/フォルダ選択を行わせれば、UWPアプリは選択された任意のファイル/フォルダにアクセスすることができます。

しかし、一度選択した項目でも、アプリ起動のたびにユーザーが選択しなおさなければならないのでは、あまりに不便になってしまいます。

そこで、UWPでは、一度アクセスしたフォルダを記憶しておき、後でアクセスする際に各種ピッカー操作を伴わなくても利用できるようにする仕組みが用意されています。

  • MostRecentlyUsedList
    • 最近使用したファイル一覧を格納するリスト
    • 最大25項目まで記録
    • 記録数の上限に達した後は、古いものから自動的に削除される
  • FutureAccessList
    • あとでアクセスする一覧
    • 最大1000項目を記録
    • 最大の記録数を超えないように、アプリ側で管理する必要がある

どちらもだいたい同じようなアクセス方法になるので、 とりあえず、このサンプルではMostResentlyUsedListを使ってみます。

MostRecentlyUsedListへの記録
            var filePicker = new Windows.Storage.Pickers.FileOpenPicker();
            filePicker.FileTypeFilter.Add(".txt");

            // 単一ファイルの選択
            var file = await filePicker.PickSingleFileAsync();
            if (file != null)
            {
                Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Add(file);
            }
MostRecentlyUsedListに記録された項目へのアクセス
            var entries = Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Entries;
            var firstItem = entries.First();
            var file = await Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(firstItem.Token);

            var dlg = new MessageDialog(file.Name);
            await dlg.ShowAsync();

Credential Locker

今まで扱ってきたデータの保存方法は、内容を平文で保存するので、パスワードのような機密性の高い情報を保存する用途には使用できません。

PasswordVaultというクラスを使うと、パスワードのような平文で保存すべきでないような情報を、暗号化した状態で保管できるようになります。

パスワード情報の保存
            // パスワード情報の保存
            var passwordVault = new Windows.Security.Credentials.PasswordVault();
            var credential = new Windows.Security.Credentials.PasswordCredential("MyApplicationName", "username", "ここにパスワード情報などを入れる");
            passwordVault.Add(credential);
パスワード情報の取得
            // パスワード情報の取得
            var passwordVault = new Windows.Security.Credentials.PasswordVault();
            try
            {
                var credential = passwordVault.Retrieve("MyApplicationName", "username");
                var dlg = new MessageDialog(credential.Password);
                await dlg.ShowAsync();
            }
            catch (Exception)
            {
                // パスワード情報が取得できなかった場合
            }
保存済みのパスワード情報の削除
            // 保存済みのパスワード情報の削除
            var passwordVault = new Windows.Security.Credentials.PasswordVault();
            var credential = passwordVault.Retrieve("MyApplicationName", "username");
            passwordVault.Remove(credential);