連載
» 2013年12月19日 13時44分 UPDATE

WinRT/Metro TIPS:Windows 8.1の新機能、ファイルアクセスの便利機能を使うには?[Windows 8.1ストアアプリ開発]

Windows 8.1ストアアプリ向けのファイルアクセス機能の改善点から、例外が出ないファイルやフォルダーの取得と、パスに依存しないファイルやフォルダーの等価比較を紹介。

[山本康彦(http://www.bluewatersoft.jp/),BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

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

連載目次

 Windowsストアアプリでファイルにアクセスするコードを書いているときに困ったことはないだろうか? 例えば、あるファイルが存在していることを確認するには、Windows 8(以降、Win 8)用のWindowsストアアプリ(以降、Win 8アプリ)ではファイルが正常に開けることを試してみるしかなかった。そういったファイルアクセス機能の問題点に対する改善が、Windows 8.1(以降、Win 8.1)用のWindowsストアアプリ(以降、Win 8.1アプリ)から利用できるWindowsランタイム(以降、WinRT)*1には施されている。本稿では、その改善点の中から、例外が出ないファイルやフォルダーの取得と、パスに依存しないファイルやフォルダーの等価比較を紹介する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #60(Windows 8.1版)」からダウンロードできる。

*1 まことに回りくどい表現で申し訳ない。MSDNのWinRTのドキュメントには、今のところバージョン番号などが記されていないため、利用できる側のバージョンで区別するしかないのだ。その利用する側であるWindowsストアアプリにもバージョン番号などによる区別がないため、その実行環境であるOSのバージョンにさかのぼって区別するしかないのである。


事前準備

 Win 8.1アプリを開発するには、Win 8.1とVisual Studio 2013(以降、VS 2013)が必要である。本稿ではOracle VM VirtualBox上で64bit版Windows 8.1 Pro Preview(日本語版)*2とVisual Studio Express 2013 Preview for Windows(日本語版)を使用してプログラミングしている。これらを準備する方法や注意事項は、「WinRT/Metro TIPS: Win8用のソース・コードをWin8.1用に変換するには?[Win 8.1]」の記事をご参照いただきたい。また、本稿のソースコードは、64bit版Windows 8.1 Pro(日本語版の製品版)とVisual Studio Express 2013 for Windows(日本語版の製品版)*3でも動作を確認している。

*2 Win 8.1Preview版の使用期限は来月(2014年1月)の半ばまでとなっている。

*3 マイクロソフト公式ダウンロードセンターの「Microsoft Visual Studio Express 2013 for Windows」から無償で入手できる。


Win 8.1で改善されたファイルアクセス機能

 MSDNによれば、次のような改善が施されている。

  • ファイルピッカー/フォルダーピッカー: 狭いビュー(Win 8アプリでのスナップ状態に相当)でもファイルピッカー/フォルダーピッカーが開くようになった(Win 8ではスナップ状態のときに出そうとすると例外になっていた)
  • ライブラリ: 音楽やビデオなどのライブラリで、エンドユーザーは任意のフォルダーを追加/削除できるようになった(Windows.Storage名前空間のStorageLibraryクラス)
  • 親フォルダー: アプリが親フォルダーにアクセスできる場合に親フォルダーを取得するメソッドが新設された(Windows.Storage名前空間IStorageItem2インターフェースのGetParentAsyncメソッド)
  • 等価比較: 2つのストレージアイテム(=フォルダーまたはファイル)が同じかどうか判定できるようになった(本稿で説明する)
  • KnownFolders: CameraRollとPlaylistsが追加された
  • ファイル/フォルダーの取得: ストレージアイテムが存在しないときに例外を出さないメソッドが新設された(本稿で説明する)
  • ファイルアクティベーション: ファイルの関連付けによって起動されたときに引数として受け取るFileActivatedEventArgsオブジェクト(Windows.ApplicationModel.Activation名前空間)にNeighboringFilesQueryプロパティが新設された。そこには、渡されたファイルの近隣のファイルのコレクションが入っている(ただし、アプリからアクセス可能なフォルダーにあるファイルのみ)
  • アプリ内コンテンツ: Windowsシステムのインデックスに、アプリからコンテンツを追加したり、列挙/検索したりできるようになった(Windows.Storage.Search名前空間)

Win 8でファイルやフォルダーの存在を確認する方法の問題点

 Win 8アプリでは、ファイルやフォルダーを取得してみて例外が出たらファイルが存在しなかったと判定するしかなかった。例えば、指定したフォルダーの中に指定したファイルが存在するかどうかを判定するメソッドは、次のコードのように記述していた。

private static async System.Threading.Tasks.Task<bool> IsFileExistAsync(
                        Windows.Storage.StorageFolder storageFolder,
                        string fileName
                      )
{
  // Win 8のときのコード
  try
  {
    await storageFolder.GetFileAsync(fileName); // ファイルを取得する
    return true; // ファイルが取得できた=ファイルが存在した
  }
  catch
  {
    return false; // ファイルの取得に失敗=ファイルが存在しなかった
  }
}

Private Shared Async Function IsFileExistAsync(
                        storageFolder As Windows.Storage.StorageFolder,
                        fileName As String
                      ) As Task(Of Boolean)
  ' Win 8のときのコード
  Try
    Await storageFolder.GetFileAsync(fileName) ' ファイルを取得する
    Return True ' ファイルが取得できた=ファイルが存在した
  Catch ex As Exception
    Return False ' ファイルの取得に失敗=ファイルが存在しなかった
  End Try
End Function

Win 8で、指定したフォルダーの中に指定したファイルが存在するかどうかを判定するメソッド(上:C#、下:VB)

 例外をキャッチするのはパフォーマンス的に不利である。また、何をやっているコードなのか分かりにくかった。

例外をキャッチせずにファイルやフォルダーの存在を確認するには?

 Win 8.1では、StorageFolderクラス(Windows.Storage名前空間)にTryGetItemAsyncメソッドが新設された。このメソッドは、ファイルが存在しないときには例外を出すのではなく、null/Nothingを返す。先ほどと同じメソッドが、簡潔に分かりやすく記述できるようになった(次のコード)。

private static async System.Threading.Tasks.Task<bool> IsFileExistAsync(
                        Windows.Storage.StorageFolder storageFolder,
                        string fileName
                      )
{
  var file = await storageFolder.TryGetItemAsync(fileName) as Windows.Storage.StorageFile;
  return (file != null);
}

Private Shared Async Function IsFileExistAsync(
                        storageFolder As Windows.Storage.StorageFolder,
                        fileName As String
                      ) As Task(Of Boolean)
  Dim file = TryCast(Await storageFolder.TryGetItemAsync(fileName), Windows.Storage.StorageFile)
  Return (file IsNot Nothing)
End Function

Win 8.1で、指定したフォルダーの中に指定したファイルが存在するかどうかを判定するメソッド(上:C#、下:VB)

 TryGetItemAsyncメソッドは、名前が一致したファイルまたはフォルダーを返してくる。そこで、例えばファイルだけが欲しいときは、上のコードのようにStorageFileクラス(Windows.Storage名前空間)へのキャストを試みる。これで、TryGetItemAsyncメソッドがフォルダーを返してきたときでも、file変数にはnull/Nothingが入るようになる。

 なお、TryGetItemAsyncメソッドは、ファイルやフォルダーの名前として不適切な文字列(例えば、「.」「*」「\」など)を渡した場合には、これまでどおりに例外を発生する。エンドユーザーからの入力をそのまま使う場合には、例外をキャッチするコードを書くべきである。

Win 8でストレージアイテムの等価比較をする方法の問題点

 ストレージアイテム(=ファイルやフォルダー)のオブジェクトが2つあったとき、それらが同じファイルやフォルダーを指しているかを調べたいときがある。例えば、ファイルオープンピッカーを出してエンドユーザーに選択してもらったファイルが、すでにアプリで開いているファイルだった場合はその画面に切り替えるなどだ。

 ファイルやフォルダーが同一かを判断するのに、Win 8アプリでは、次のコードのようにしてストレージアイテムのパス文字列を比較していた。

// Windows 8のときのコード
// 注)これでは、[ライブラリ]の[ピクチャ]/[ビデオ]/[ミュージック]などのフォルダーが正しく比較できない
private static bool AreSame(
                      Windows.Storage.IStorageItem item1,
                      Windows.Storage.IStorageItem item2)
{
  if (item1 == null)
    return (item2 == null);
  else if (item2 == null)
    return false;

  // パス文字列を取得して比較する
  string path1 = item1.Path;
  string path2 = item2.Path;
  return string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase);
}

' Windows 8のときのコード
' 注)これでは、[ライブラリ]の[ピクチャ]/[ビデオ]/[ミュージック]などのフォルダーが正しく比較できない
Private Shared Function AreSame(
                          item1 As Windows.Storage.IStorageItem,
                          item2 As Windows.Storage.IStorageItem
                        ) As Boolean
  If (item1 Is Nothing) Then
    Return (item2 Is Nothing)
  ElseIf (item2 Is Nothing) Then
    Return False
  End If

  ' パス文字列を取得して比較する
  Dim path1 As String = item1.Path
  Dim path2 As String = item2.Path
  Return String.Equals(path1, path2, StringComparison.OrdinalIgnoreCase)
End Function

Win 8で、2つのストレージアイテムの等価比較をするメソッド(上:C#、下:VB)

 この方法は少々煩雑なだけでなく、致命的な問題を抱えていた。特定のフォルダーが正しく比較できないのである。例えば、上のコメントにも書いたが、[ライブラリ]の下にあるフォルダーがそうだ。エンドユーザーがフォルダーピッカーのドロップダウンで[ライブラリ]を選び(次の画像)、その下のフォルダーを選択した場合には、得られるStorageFolderオブジェクトのパスがString.Empty(=空文字)になってしまうのだ。これでは、例えば[ライブラリ]の下にある[ピクチャ]フォルダーと[ビデオ]フォルダーの区別が付かない。

フォルダーピッカーでドロップダウンを開いたところ(Win 8.1) フォルダーピッカーでドロップダウンを開いたところ(Win 8.1)
この画像のように、トップレベルの選択肢として[ライブラリ]がある(マウスカーソルのあるところ)*4。これを選んで、次にその下のフォルダー(例えば[ピクチャ]フォルダーなど)を選択した場合、アプリに返されるStorageFolderオブジェクトのパスが空文字になってしまう。この画像で[ホームグループ]を選んで進めていっても同じだ。なお、この画像で[PC]を選び、その中の[ピクチャ]フォルダーなどを選んだ場合には、パス文字列が得られる。

*4 [ライブラリ]が表示されない場合は、デスクトップのエクスプローラーで表示設定を変更してほしい。具体的な手順は「Windows TIPS:Windows 8.1のエクスプローラにライブラリ項目を表示させる」を参照。


どんなストレージアイテムでも正しく等価比較をするには?

 Win 8.1では、IStorageItem2インターフェース(Windows.Storage名前空間)が新設された。そのIsEqualメソッドを使えば、正しくストレージアイテムの等価比較ができる。フォルダーを表すStorageFolderクラスも、ファイルを表すStorageFileクラスも、このIStorageItem2インターフェースを実装している。

 2つのストレージアイテム・オブジェクトの等価比較をするメソッドは、次のコードのように書ける。

private static bool AreSame(
                      Windows.Storage.IStorageItem2 item1,
                      Windows.Storage.IStorageItem2 item2)
{
  if (item1 == null)
    return (item2 == null);
  else if (item2 == null)
    return false;

  return item1.IsEqual(item2);
}

Private Shared Function AreSame(
                          item1 As Windows.Storage.IStorageItem2,
                          item2 As Windows.Storage.IStorageItem2
                        ) As Boolean

  If (item1 Is Nothing) Then
    Return (item2 Is Nothing)
  ElseIf (item2 Is Nothing) Then
    Return False
  End If

  Return item1.IsEqual(item2)
End Function

Win 8.1で、2つのストレージアイテムの等価比較を常に正しく行うメソッド(上:C#、下:VB)

 ちなみに、ストレージアイテムのパスが空文字のとき、IsEqualメソッドはその内部でShell32.dllのIShellItem::Compareメソッドを呼び出している。Win 8アプリで同様なことをするには、C++/CXでDLLを作るしかなかったのだ。

まとめ

 Win 8.1では、ファイルアクセス関連のAPIも改善されており、ファイルやフォルダーの存在確認や等価比較が簡単になった。

 UIの大幅な変更や機能追加に目を奪われがちであるが、Win 8.1ではロジックで使うAPIにもさまざまな改良が加えられている。新しくなったAPIについては、次のドキュメントも参照してほしい。

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

WinRT/Metro TIPS

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

@IT Special

- PR -

TechTargetジャパン

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

RSSについて

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

メールマガジン登録

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