none
WinRTでフォルダ一覧を取得する方法について RRS feed

  • 質問

  • こんにちわ。

    Windowsストアアプリの勉強をしようとしているところです。

    環境は、Windows8.1 VS2013 C#です。

    プログラム経験は、Delphi、C、C++、VBなどで、C#は初めてです。

    質問ですが、ユーザーに任意のフォルダを選ばせて、そのフォルダ配下にあるフォルダの一覧を取得しようとしています。

    とりあえず作ってみたサンプルは以下のようなものです。

                var folderPicker = new FolderPicker();
                folderPicker.ViewMode = PickerViewMode.List;
                folderPicker.SuggestedStartLocation = PickerLocationId.Desktop;
                folderPicker.FileTypeFilter.Add(".zip");
               
                var folder = await folderPicker.PickSingleFolderAsync();

                // StorageFolder オブジェクトのリストを取得しているつもり
                var storageFolderQueryResults = folder.CreateFolderQuery(Windows.Storage.Search.CommonFolderQuery.DefaultQuery);

                // フォルダのリストを取得しているつもり
                var folders = await storageFolderQueryResults.GetFoldersAsync();

                // フォルダ一覧をリストボックスに格納しているつもり
                ListBox1.ItemsSource = folders;  

    フォルダを選択させるところまでは動作しました(パスもデバッガで正しく取得できていることは確認しました)

    ただ、リストボックスには「Windows.Storage.StrageFolder」の文字列がフォルダの数だけ表示されていました。

    自分としては「D:\aaa\bbb」などの形ですべてのフォルダを取得したいのですが、どのようにしたらよいか行き詰っています。

    自分なりに検索したり書籍を探したりしたのですが、解決に至りません。

    何かヒントとなる情報やWebページなどありませんでしょうか?

    よろしくお願いいたします。
    2014年3月12日 10:59

回答

  • フォルダーの一覧を取得するのはできていますね (ユーザーがキャンセルしたときの対処が抜けていますが)。
    その部分をメソッドに切り出しておくと、次のようになります。

    private async System.Threading.Tasks.Task<IReadOnlyList<Windows.Storage.StorageFolder>> 
      ユーザーにフォルダーを選択してもらう()
    {
      var folderPicker = new Windows.Storage.Pickers.FolderPicker();
      folderPicker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
      folderPicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop;
      folderPicker.FileTypeFilter.Add(".zip");
    
      var folder = await folderPicker.PickSingleFolderAsync();
      if (folder == null)
        return null;
    
      // StorageFolder オブジェクトのリストを取得する
      var storageFolderQueryResults = folder.CreateFolderQuery(Windows.Storage.Search.CommonFolderQuery.DefaultQuery);
    
      // フォルダのリストを取得して返す
      return await storageFolderQueryResults.GetFoldersAsync();
    }


    得られたフォルダーの一覧は、StorageFolder オブジェクトのコレクションです。
    それを、
    ListBox1.ItemsSource = folders;
    のようにしてリストボックスに与えると、リストボックスの各アイテムにそれぞれの StorageFolder オブジェクトがバインドされます。デフォルトでは、バインドされたオブジェクトの ToString メソッドの結果を表示するので、「Windows.Storage.StrageFolder」という文字列が表示されたのです。

    これを解決する方法は 2つあります。

    【DisplayMemberPath を指定する】
    バインドされたオブジェクトの「Path」プロパティを表示に使え、と指示します。

    private async void Button1_Click(object sender, RoutedEventArgs e) { IReadOnlyList<Windows.Storage.StorageFolder> folders = await ユーザーにフォルダーを選択してもらう(); // フォルダ一覧の各StorageFolderを、リストボックスの各リストボックスアイテムにバインドする ListBox1.ItemsSource = folders; // DisplayMemberPathを指定して、バインドされたデータの「Path」プロパティを表示させる ListBox1.DisplayMemberPath = "Path"; }

     
    【データテンプレートを使う】

    XAMLの側で、バインドされたオブジェクトをどのように表現するかを指定します。
    コードビハインド側では ItemsSource に folders をセットするだけです。

    private async void Button1_Click(object sender, RoutedEventArgs e)
    {
      IReadOnlyList<Windows.Storage.StorageFolder> folders 
        = await ユーザーにフォルダーを選択してもらう();
    
      // フォルダ一覧の各StorageFolderを、リストボックスの各リストボックスアイテムにバインドする
      // ……略……
    
      // 別解: テンプレートを使う (コードからはバインドするだけ)
      ListBox2.ItemsSource = folders;
    }

    XAML を次にようにします。

    <ListBox x:Name="ListBox2" Width="500">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <Grid>
            <TextBlock Foreground="Blue" FontSize="18" FontStyle="Italic"
                       Text="{Binding Path}" />
          </Grid>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

    以上 2通りの実行結果は、次の画像のようになります。

    画面の右側がデータテンプレートを使ったやり方です。文字が色付きでイタリックになっているのが分かります。

    バインドしたデータをデータテンプレートで表現するやりかたは、最初は難しく感じるでしょうが、表現力が格段に高まる上に、データを更新するコードが簡単になるというメリットがあります。
    データバインディングについては次の記事を参考にしていただければ幸いです。
    WinRT/Metro TIPS:データ・コレクションをバインドするには?
    ・上の記事の前後の、WinRT/Metro TIPS のバインド関連の記事
    連載:Windowsストア・アプリ開発入門:第5回 データを画面に表示する


    biac [ http://bluewatersoft.cocolog-nifty.com/ ]




    • 編集済み biacMVP 2014年3月13日 1:35 参考リンク追加
    • 回答の候補に設定 星 睦美 2014年3月13日 1:41
    • 回答としてマーク kazchan 2014年3月13日 7:56
    2014年3月13日 1:22
  • > 例えば仮想リストビューのように一瞬で表示させ、スクロールしたタイミングで描画イベントが発生したら表示するといったことができないか

    次のドキュメントが起点になると思います。

    ・MSDN: リストまたはグリッドによる仮想化の使用

    ポイントは、UI の仮想化だけで済む (「ユーザーにフォルダーを選択してもらう」メソッドの処理は短時間) か、データの段階的な仮想化 (=遅延ローディング) まで必要になるか、というところです。
    ※ ListView などを、StackPanel や ScrollViewer の中に入れてしまうと、UI の仮想化が効かないので注意

    > 最終的にはGridViewやListViewなどへ画像やコメント付きでフォルダイメージを表示させることを考えています

    個々のアイテムに画像を表示するのは、たしかに時間が掛かりますね。
    とりあえずダミー画像を表示しておいて、後から本物の画像を順次表示していくサンプルコードが次にあります。

    ・MSDN blogs: Incremental update item data for ListViewBase controls in windows 8.1
     (英語の記事ですが、コードを読む分にはなんとかなるでしょう)


    biac [ http://bluewatersoft.cocolog-nifty.com/ ]

    • 回答の候補に設定 星 睦美 2014年3月13日 5:13
    • 回答としてマーク kazchan 2014年3月13日 7:57
    2014年3月13日 4:11

すべての返信

  • フォルダーの一覧を取得するのはできていますね (ユーザーがキャンセルしたときの対処が抜けていますが)。
    その部分をメソッドに切り出しておくと、次のようになります。

    private async System.Threading.Tasks.Task<IReadOnlyList<Windows.Storage.StorageFolder>> 
      ユーザーにフォルダーを選択してもらう()
    {
      var folderPicker = new Windows.Storage.Pickers.FolderPicker();
      folderPicker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
      folderPicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop;
      folderPicker.FileTypeFilter.Add(".zip");
    
      var folder = await folderPicker.PickSingleFolderAsync();
      if (folder == null)
        return null;
    
      // StorageFolder オブジェクトのリストを取得する
      var storageFolderQueryResults = folder.CreateFolderQuery(Windows.Storage.Search.CommonFolderQuery.DefaultQuery);
    
      // フォルダのリストを取得して返す
      return await storageFolderQueryResults.GetFoldersAsync();
    }


    得られたフォルダーの一覧は、StorageFolder オブジェクトのコレクションです。
    それを、
    ListBox1.ItemsSource = folders;
    のようにしてリストボックスに与えると、リストボックスの各アイテムにそれぞれの StorageFolder オブジェクトがバインドされます。デフォルトでは、バインドされたオブジェクトの ToString メソッドの結果を表示するので、「Windows.Storage.StrageFolder」という文字列が表示されたのです。

    これを解決する方法は 2つあります。

    【DisplayMemberPath を指定する】
    バインドされたオブジェクトの「Path」プロパティを表示に使え、と指示します。

    private async void Button1_Click(object sender, RoutedEventArgs e) { IReadOnlyList<Windows.Storage.StorageFolder> folders = await ユーザーにフォルダーを選択してもらう(); // フォルダ一覧の各StorageFolderを、リストボックスの各リストボックスアイテムにバインドする ListBox1.ItemsSource = folders; // DisplayMemberPathを指定して、バインドされたデータの「Path」プロパティを表示させる ListBox1.DisplayMemberPath = "Path"; }

     
    【データテンプレートを使う】

    XAMLの側で、バインドされたオブジェクトをどのように表現するかを指定します。
    コードビハインド側では ItemsSource に folders をセットするだけです。

    private async void Button1_Click(object sender, RoutedEventArgs e)
    {
      IReadOnlyList<Windows.Storage.StorageFolder> folders 
        = await ユーザーにフォルダーを選択してもらう();
    
      // フォルダ一覧の各StorageFolderを、リストボックスの各リストボックスアイテムにバインドする
      // ……略……
    
      // 別解: テンプレートを使う (コードからはバインドするだけ)
      ListBox2.ItemsSource = folders;
    }

    XAML を次にようにします。

    <ListBox x:Name="ListBox2" Width="500">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <Grid>
            <TextBlock Foreground="Blue" FontSize="18" FontStyle="Italic"
                       Text="{Binding Path}" />
          </Grid>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

    以上 2通りの実行結果は、次の画像のようになります。

    画面の右側がデータテンプレートを使ったやり方です。文字が色付きでイタリックになっているのが分かります。

    バインドしたデータをデータテンプレートで表現するやりかたは、最初は難しく感じるでしょうが、表現力が格段に高まる上に、データを更新するコードが簡単になるというメリットがあります。
    データバインディングについては次の記事を参考にしていただければ幸いです。
    WinRT/Metro TIPS:データ・コレクションをバインドするには?
    ・上の記事の前後の、WinRT/Metro TIPS のバインド関連の記事
    連載:Windowsストア・アプリ開発入門:第5回 データを画面に表示する


    biac [ http://bluewatersoft.cocolog-nifty.com/ ]




    • 編集済み biacMVP 2014年3月13日 1:35 参考リンク追加
    • 回答の候補に設定 星 睦美 2014年3月13日 1:41
    • 回答としてマーク kazchan 2014年3月13日 7:56
    2014年3月13日 1:22
  • あれから色々検索して、http://d.hatena.ne.jp/garicchi/?of=27 に記載されている内容を参考にソースを変更してみました。

                // ユーザーに任意のフォルダを選ばせる下準備
                var folderPicker = new FolderPicker();
                folderPicker.ViewMode = PickerViewMode.List;
                folderPicker.SuggestedStartLocation = PickerLocationId.Desktop;
                folderPicker.FileTypeFilter.Add(".zip");

                // ユーザーに任意のフォルダを選ばせる
                var folder = await folderPicker.PickSingleFolderAsync();

                // フォルダー内のファイルを StorageFolder オブジェクトにグループ化し、ファイルを
                // 列挙するためのクエリ結果オブジェクトを作成
                var query = folder.CreateFolderQuery(CommonFolderQuery.DefaultQuery);

                // クエリ結果のすべてのフォルダを取得
                var list = await query.GetFoldersAsync();

                // リストボックスへ反映
                ListBox1.ItemsSource = list.Select(x => x.Path);

    一応、これでフォルダの一覧は取得できました。

    ただC#および.NETは全く無知でしたので、未だに何故これで取得できるのか理解できていません。

    Delphi7くらいまでのVCL(とオブジェクト)やWin32 APIを使ってC言語ででゴリゴリやるのは慣れているのですが、今回のような処理の実現には役立ちませんでした。

    かといって、コピペで理解しないまま進めるのは駄目だと思いますので、勉強したいと思っています。

    C#の本は結構あるのですが、ストアアプリを含めてC#や.Netを勉強できるようなお勧めの書籍があればご紹介願えないでしょうか?

    ストアアプリに絞って勉強したいので、ストアアプリでは削除されている.Netの機能については省かれているか、その旨の記載と代替方法が書かれているものが欲しいです(英語は機械翻訳も含めてさっぱり理解不能なので、日本語の書籍かWebページの紹介をしていただけると助かります)。

    よろしくお願いいたします。


    • 編集済み kazchan 2014年3月13日 1:31
    2014年3月13日 1:30
  • ご返信ありがとうございます。

    返信に気付かず、経過を自身の投稿に返信していました。

    内容を試したみたところ、無事取得と表示ができました。

    コードの例示と、XAMLでの方法も教えていただきありがたく思います。

    知識が不足しているので勉強します。示していただいた記事も参考にさせていただきます。

    直接は関係無い事項で恐縮ですが、リストボックスに表示されるまで結構時間がかかっているのを何とかできないかと思っています。

    具体的には指定したフォルダ配下には7千個程度のフォルダがあり、リストボックスに表示されるまでに4~5秒待たされます(Core i7 4770k 16GB 7200rpmのHDD)。GridViewへの表示もほぼ同じくらいの時間がかかっています。

    これを、例えば仮想リストビューのように一瞬で表示させ、スクロールしたタイミングで描画イベントが発生したら表示するといったことができないかと思っています(Windowsエクスプローラーのファイル表示のようなイメージです)。

    最終的にはGridViewやListViewなどへ画像やコメント付きでフォルダイメージを表示させることを考えていますが、画像やコメントを付加していくともっと遅くなると思いますので、何か回避策が無いものかと。

    デスクトップアプリ(主にVC++で作っていました)なら数万枚の画像データのサムネイルでも(事前にリストを作成しておけば)、仮想リストビューなどで一瞬で表示できるのでユーザーを待たせる必要が無いので使っていたのですが、ストアアプリでの実現方法がわから無い状態です。調べてみてはいるのですが、やはりC#と.NETの知識ゼロでは辛いので、どこかへ誘導していただけたらありがたいです。

    とは言え、当初の疑問点は解決(内容については勉強します)しましたので、質問自体は解決済みとしたいと思います。前述の追加質問については余裕があればどなたか回答いただけると嬉しいです。

    よろしくお願いいたします。

    2014年3月13日 2:43
  • >  ListBox1.ItemsSource = list.Select(x => x.Path);

    これは、StorageFolder のコレクションから、StorageFolder.Path (=string型) のコレクションを作り出し、それを ListBox の ItemsSource に与えています。リストボックスの各アイテムには、StorageFolder.Path の文字列がバインドされるわけですから、お望みの結果になったわけです。

    このような LINQ を使う方法は、余計な処理をするだけになるので一般には行いませんが、表示順を変えたり、一部を抜いたりするような場合には逆に必須となります。

    > ストアアプリを含めてC#や.Netを勉強できるようなお勧めの書籍

    「これ一冊で!」といえるような本は、ちょっとないかもしれません。

    Windows 8.1 用の Windows ストアアプリを開発するために必要な知識は、次のようなものです。
    ・言語: C# および XAML
    ・利用するライブラリ: .NET Framework および Windows Runtime (WinRT と略す)

    このうち WinRT は、Windows 8 と Windows 8.1 の間でずいぶん変わりました。そこを完全に押さえている本は、ちょっと見当たらないようです。
    Windows 8 用の書籍をベースにして、Windows 8.1 との差分は Web で埋めることになるでしょう。「最強」の書籍 (それゆえボリュームがすごい) を紹介しておきます。

    ・書籍: プログラミングWindows 第6版 (Windows 8(巻末に8.1の補足あり)/C#)
    ・Web: 連載:Windowsストア・アプリ開発入門 (Windows 8.1/C#)


    biac [ http://bluewatersoft.cocolog-nifty.com/ ]

    2014年3月13日 2:54
  • > 例えば仮想リストビューのように一瞬で表示させ、スクロールしたタイミングで描画イベントが発生したら表示するといったことができないか

    次のドキュメントが起点になると思います。

    ・MSDN: リストまたはグリッドによる仮想化の使用

    ポイントは、UI の仮想化だけで済む (「ユーザーにフォルダーを選択してもらう」メソッドの処理は短時間) か、データの段階的な仮想化 (=遅延ローディング) まで必要になるか、というところです。
    ※ ListView などを、StackPanel や ScrollViewer の中に入れてしまうと、UI の仮想化が効かないので注意

    > 最終的にはGridViewやListViewなどへ画像やコメント付きでフォルダイメージを表示させることを考えています

    個々のアイテムに画像を表示するのは、たしかに時間が掛かりますね。
    とりあえずダミー画像を表示しておいて、後から本物の画像を順次表示していくサンプルコードが次にあります。

    ・MSDN blogs: Incremental update item data for ListViewBase controls in windows 8.1
     (英語の記事ですが、コードを読む分にはなんとかなるでしょう)


    biac [ http://bluewatersoft.cocolog-nifty.com/ ]

    • 回答の候補に設定 星 睦美 2014年3月13日 5:13
    • 回答としてマーク kazchan 2014年3月13日 7:57
    2014年3月13日 4:11
  • お勧めの情報ありがとうございます。

    書籍はボリュームもそうですが、お値段もそれなりにしますね(専門書としては安い方だとは思いますけど)。しかし有用なものであれば購入しておいて損は無いと思うので、書店で確認してみます。

    ご紹介のWebは先日から少し見始めたところでした。もしかしてこの連載のご著者さまでしょうか?

    ありがたく拝見して、サンプルを理解していきたいと思います。

    自分で調べられる程度に早くなりたいですが、Windowsアプリ(とはいってもハード試験用の簡単なもの)やデバイスドライバ、組み込みやファーム開発、OS無しのハード制御ばかりしていたので、昨今のスマホやタブレットのアプリ開発は浦島太郎状態です。

    私はWindowsの方がAndroidやiOSよりも好きなので、何とか作れるように勉強していきます。

    ご助言、ありがとうございました。

    2014年3月13日 4:45
  • フォーラム オペレーターの星 睦美です。
    biac さん、参考になる情報をありがとうございます。

    kazchan さん、こんにちは。
    フォーラム での回答がお役にたちましたら、今後もユーザー同士の情報交換がより活発になりますように投稿者から[回答としてマーク] いただければ幸いです。


    フォーラム オペレーター 星 睦美 - MSDN Community Support


    2014年3月13日 5:16
  • > ご紹介のWebは先日から少し見始めたところでした。もしかしてこの連載のご著者さまでしょうか?

    ご愛読ありがとうございます。その筆者です。
    投稿の下のハンドル (ユーザーID) をクリックするとプロファイルのページが開きます。私の場合は、プロファイルページの左側に Facebook と twitter のリンクを載せております。

    Windows ストアアプリは、「Reimagined Windows」(Windows 8 発表時のキャッチフレーズ、「ゼロからイメージし直した Windows」) のクライアント側のカナメになります。従来のデスクトップ用 Windows アプリとは、そのコンセプトは大きく異なります。一言でいうと「安心して使えるモバイル」とでもなりましょうか。
    ※ そのためにマルウェアを徹底的に排除しようとしています: 「Windowsストアアプリでウイルスを作るには!?

    組み込みの世界から来ると、とまどうことが多々あると思いますが、一歩ずつ進んでいってください。


    biac [ http://bluewatersoft.cocolog-nifty.com/ ]

    2014年3月13日 6:21