Windows 8.1ストアアプリ向けのファイルアクセス機能の改善点から、例外が出ないファイルやフォルダーの取得と、パスに依存しないファイルやフォルダーの等価比較を紹介。
powered by Insider.NET
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」から無償で入手できる。
MSDNによれば、次のような改善が施されている。
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.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
TryGetItemAsyncメソッドは、名前が一致したファイルまたはフォルダーを返してくる。そこで、例えばファイルだけが欲しいときは、上のコードのようにStorageFileクラス(Windows.Storage名前空間)へのキャストを試みる。これで、TryGetItemAsyncメソッドがフォルダーを返してきたときでも、file変数にはnull/Nothingが入るようになる。
なお、TryGetItemAsyncメソッドは、ファイルやフォルダーの名前として不適切な文字列(例えば、「.」「*」「\」など)を渡した場合には、これまでどおりに例外を発生する。エンドユーザーからの入力をそのまま使う場合には、例外をキャッチするコードを書くべきである。
ストレージアイテム(=ファイルやフォルダー)のオブジェクトが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
この方法は少々煩雑なだけでなく、致命的な問題を抱えていた。特定のフォルダーが正しく比較できないのである。例えば、上のコメントにも書いたが、[ライブラリ]の下にあるフォルダーがそうだ。エンドユーザーがフォルダーピッカーのドロップダウンで[ライブラリ]を選び(次の画像)、その下のフォルダーを選択した場合には、得られるStorageFolderオブジェクトのパスがString.Empty(=空文字)になってしまうのだ。これでは、例えば[ライブラリ]の下にある[ピクチャ]フォルダーと[ビデオ]フォルダーの区別が付かない。
*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
ちなみに、ストレージアイテムのパスが空文字のとき、IsEqualメソッドはその内部でShell32.dllのIShellItem::Compareメソッドを呼び出している。Win 8アプリで同様なことをするには、C++/CXでDLLを作るしかなかったのだ。
Win 8.1では、ファイルアクセス関連のAPIも改善されており、ファイルやフォルダーの存在確認や等価比較が簡単になった。
UIの大幅な変更や機能追加に目を奪われがちであるが、Win 8.1ではロジックで使うAPIにもさまざまな改良が加えられている。新しくなったAPIについては、次のドキュメントも参照してほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.