ファイルの検索と情報の取得

 Metro Style App では、デスクトップアプリのように自由にファイルアクセスはできません。
 アクセスできるのはアプリ毎に割り当てられたローカルフォルダーと、ドキュメント、ピクチャなどのライブラリフォルダーに限定されます。

 アクセスできるフォルダーについては普通にファイルを列挙して取得する事もできますが、それ以外にフォルダー内のファイルを検索して指定した条件に合わせて取得する事もできます。
 また、個々のファイルについて、ファイルの種類に応じた詳細情報を取得する事もできます。

 これらの機能を使って、ファイルをリスト表示するアプリを作ってみたいと思います。
 アプリの概要は以下のような感じです。

  • ピクチャフォルダー以下のファイルをグループ分けしてリスト表示する。
  • 各ファイルのサムネイル、ファイル名、追加情報を表示する。

テンプレート

 プロジェクト作成時のテンプレートとしてグリッドアプリケーションを使用します。

 グリッドアプリケーションのテンプレートは、グループ分けした項目表示画面、グループの詳細表示画面、項目の詳細表示画面から構成されています。
 今回は、最初に表示されるグループ分けした項目表示画面だけを使います。

機能

 ピクチャフォルダーにアクセスするためには、アプリケーションマニフェストで機能を追加する必要があります。
 ピクチャフォルダーアクセスに必要な機能は“画像ライブラリ”ですので、この機能にチェックを入れておきます。

画面

 画面についてはテンプレートをそのまま使用しますが、サンプルデータを表示している部分は不要ですので削除しておきます。
 画面が定義されている GroupedItemsPage.xaml を開き、CollectionViewSource の d:Source を削除します。

 削除箇所はサンプルデータをデザイン時に表示させるための設定です。
 この設定があるため Visual Studio 上でデザインしている時でもデータが表示された状態になっていますので、削除するとデザイン時にはデータが表示されなくなります。

データモデル

 テンプレートにはサンプルデータを表示するためのデータモデルクラスが含まれています(DataModel/SampleDataSource.cs)。
 このサンプルを参考に、不要な情報を削除したデータモデルクラスを作成します。

 グループ/項目に共通して必要な情報を持つクラスと、グループ/項目それぞれのクラスがあるという構成は同じですが、詳細表示をする時に必要な情報は削除しています。

// 共通データクラス
public abstract class FileDataCommon : FileLooker.Common.BindableBase
{
    public FileDataCommon(String uniqueId, String title, String subtitle, String imagePath)
    {
        this._uniqueId = uniqueId;
        this._title = title;
        this._subtitle = subtitle;
        this._imagePath = imagePath;
    }

    private string _uniqueId = string.Empty;
    public string UniqueId
    {
        get { return this._uniqueId; }
        set { this.SetProperty(ref this._uniqueId, value); }
    }

    private string _title = string.Empty;
    public string Title
    {
        get { return this._title; }
        set { this.SetProperty(ref this._title, value); }
    }

    private string _subtitle = string.Empty;
    public string Subtitle
    {
        get { return this._subtitle; }
        set { this.SetProperty(ref this._subtitle, value); }
    }

    private ImageSource _image = null;
    private String _imagePath = null;
    public ImageSource Image
    {
        get
        {
            return this._image;
        }
        set
        {
            this.SetProperty(ref this._image, value);
        }
    }
}

// 項目データクラス
public class FileDataItem : FileDataCommon
{
    public FileDataItem(String uniqueId, String title, String subtitle, String imagePath, FileDataGroup group)
        : base(uniqueId, title, subtitle, imagePath)
    {
        this._group = group;
    }

    private FileDataGroup _group;
    public FileDataGroup Group
    {
        get { return this._group; }
        set { this.SetProperty(ref this._group, value); }
    }
}

// グループデータクラス
public class FileDataGroup : FileDataCommon
{
    public FileDataGroup(String uniqueId, String title, String subtitle, String imagePath)
        : base(uniqueId, title, subtitle, imagePath)
    {
    }

    private ObservableCollection<FileDataItem> _items = new ObservableCollection<FileDataItem>();
    public ObservableCollection<FileDataItem> Items
    {
        get { return this._items; }
    }
}

public sealed class FileDataSource
{
    private ObservableCollection<FileDataGroup> _itemGroups = new ObservableCollection<FileDataGroup>();
    public ObservableCollection<FileDataGroup> ItemGroups
    {
        get { return this._itemGroups; }
    }

    public FileDataSource()
    {
    }
}

起動処理

 テンプレートではアプリ起動時にサンプルデータを作成して画面に渡していますので、この部分をサンプルではなく作成したデータモデルを渡すように変更します。
 変更箇所は App.xaml.cs の OnLaunched メソッドです。

//rootFrame.Navigate(typeof(GroupedItemsPage), sampleData.ItemGroups);
rootFrame.Navigate(typeof(GroupedItemsPage), new FileDataSource().ItemGroups);

ファイルの検索と情報の取得

 画面が表示されるタイミング(GroupedItemsPage.xaml.cs の OnNavigatedTo)でファイルの検索と情報の取得を開始します。

 まずファイルの検索を行います。
 KnownFolders.PicturesLibrary でピクチャフォルダーを取得後、CreateFolderQueryWithOptions メソッドを呼び出して検索を行います。
 今回は検索オプションとして CommonFolderQuery.GroupByMonth を指定します。
 この指定を行うと、フォルダー配下のファイル(サブフォルダーも含む)を検索し、月毎(デジカメで撮影した画像の場合は撮影日時で検索)にグループ化します。検索結果はグループを仮想フォルダーとして扱い、フォルダー一覧の形で取得する事ができます。

 次にファイルの情報を取得します。
 仮想フォルダー内のファイルは StorageFile の形で取得できます。StorageFile の Properties からファイル種類に応じた情報を取得できます。
 今回は画像ファイルが対象ですので、GetImagePropertiesAsync メソッドで画像の情報を取得します。
 情報は色々取得できますが、画像の幅/高さと DateTaken(撮影日時)を取得してみました。
 これらの情報とサムネイルを使用して項目を追加して行きます。
 情報の取得は非同期に実行されますので、追加する度に順次画面に表示されていきます。

private ObservableCollection<FileDataGroup> _source;

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    this.DefaultViewModel["Groups"] = e.Parameter;

    _source = (ObservableCollection<FileDataGroup>)e.Parameter;

    var picturesFolder = KnownFolders.PicturesLibrary;

    StorageFolderQueryResult queryResult =
        picturesFolder.CreateFolderQueryWithOptions(
            new QueryOptions(CommonFolderQuery.GroupByMonth));

    IReadOnlyList<StorageFolder> folderList = await queryResult.GetFoldersAsync();

    foreach (StorageFolder folder in folderList)
    {
        var group = new FileDataGroup(folder.FolderRelativeId, folder.DisplayName, "", folder.Path);
        _source.Add(group);

        IReadOnlyList<StorageFile> fileList = await folder.GetFilesAsync();

        foreach (StorageFile file in fileList)
        {
            var prop = await file.Properties.GetImagePropertiesAsync();

            var sb = new StringBuilder();
            sb.AppendLine(prop.DateTaken.ToString());
            sb.AppendFormat("{0}x{1}", prop.Width, prop.Height);

            var item = new FileDataItem(file.FolderRelativeId, file.Name, sb.ToString(), file.Path, group);

            using (var thumb = await file.GetThumbnailAsync(ThumbnailMode.DocumentsView, 250))
            {
                if (thumb != null)
                {
                    var bmp = new BitmapImage();
                    bmp.SetSource(thumb);
                    item.Image = bmp;
                }
            }

            group.Items.Add(item);
        }
    }
}

実行してみると以下のように月毎にグループ化されて画像が表示されます。

グリッドアプリケーションのテンプレートには、画面を切り替えた時の処理も含まれています。
ポートレート表示や、スナップ表示をした時には以下のような画面になります。

ポートレート表示

・スナップ表示