Windows 8 ストアアプリテンプレート - 検索ページ解説

Basic Photo、Entertainment Photoプロジェクトテンプレートで対応している、検索チャーム利用の検索ページについて説明します。

Basic PhotoのSearchResultsPage.xaml、Entertainment PhotoのSearchExtResultsPage.xamlは、Visual Studio 2012に元々用意されているアイテムテンプレート、”検索コントラクト”を使ってプロジェクトに追加しました。このアイテムテンプレートを使ってプロジェクトに検索機能を追加すると、以下の変更がプロジェクトになされます。

  • SearchResultsPage.xaml、SearchResultsPage.xaml.csファイルの追加
  • Package.appxmanifestの宣言タブに”検索”追加 ※この設定でアプリ外からの検索が有効になる
  • App.xaml.csにOnSearchActivatedメソッドが追加される

これでアプリがフロントに来ていない状態も含め、検索チャームからの検索が可能になります。OnSearchActivatedメソッドを覗くと、下から三行目に

frame.Navigate(typeof(SearchResultsPage),args.QueryText);

というコードがあり、ここで、SearchResultsPage.xamlのページを開く指示がなされています。

次にSearchResultsPage.xamlを開いてみてください。
このページは、アプリケーション名の直下に、検索クエリーテキストに合致するアイテムを保持するグループのリストを水平方向に並べたフィルターリスト、その下に合致するアイテム群のリストが表示されるようなデザインになっています。

 SearchResultsPage.xamlの33行目から始まるGrid(x:Name="resultPanel")が検索結果表示の全体の宣言です。その内部は、

  • 44行目から始まるGrid(x:Name="typicalPanel")
      上の図の”All(6)、 Group Title:1(1)、…”を表示している部分
  • 69行目から始まるGridView(x:Name="resultsGridView")
      上の図のItem Tilte: 1等の検索結果アイテムリストを表示している部分

typicalPanel Gridでは、47行目にある

                    ItemsSource="{Binding Source={StaticResource filtersViewSource}}" 

で<ItemsControl…/>のソースを指定し、そのコレクションの子要素の表示方法を56行目からはじまる<ItemsControl.ItemTemplate…/>で指定するという構成になっています。ここでは58行目で宣言されている、

58                            <RadioButton
59                                GroupName="Filters"
60                                IsChecked="{Binding Active, Mode=TwoWay}"
61                                Checked="Filter_Checked"
62                                Style="{StaticResource TextRadioButtonStyle}">
63                                <TextBlock Text="{Binding Description}"  Margin="3,-7,3,10" Style="{StaticResource GroupHeaderTextStyle}" />
64                            </RadioButton>

の60行目と63行目のBinding指定で、filtersViewSourceで指定されたコンテナの要素のプロパティが連結されるようになっています。47行目のfilterViewSourceを更にたどっていくと、16行目に

        <CollectionViewSource x:Name="filtersViewSource" Source="{Binding Filters}"/>

という宣言があり、filtersViewSourceは”Filters”に連結されています。
SearchResultsPageはLayoutAwarePageクラスであり、このクラスはXAML上の表示とC#のコードビハインド側を、DefaultViewModelを通じてModelとViewをつなぐ構造になっています。
C#のコードビハインド側で、DefaultViewModel["Filters"]に既定の構造の要素を持つコンテナを代入してやれば、XAML側のフィルターリストに表示を反映できます。

検索チャームから受信したクエリテキストは、SearchResultsPage.xaml.csのLoadStateメソッドで受けています。Basic Photoアプリテンプレートでは、

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            var queryText = navigationParameter as String;

            // TODO: アプリケーション固有の検索ロジックです。検索プロセスでは、
            //       結果カテゴリのリストを作成する必要があります。
            //
            //       filterList.Add(new Filter("<フィルター名>", <結果数>));
            //
            //       アクティブな状態で開始するには、3 番目の引数として true を渡すフィルターが最初
            //       のフィルター (通常は "All") のみであることが必要です。アクティブ フィルターの
            //       結果は以下の Filter_SelectionChanged で提供されます。

            filteredItems = new List<Data.SampleDataItem>();
            var filterList = new List<Filter>();
            int totalCount = 0;
            foreach (var group in Data.SampleDataSource.GetGroups("AllGroups"))
            {
                int foundCount = 0;
                foreach (var item in group.Items)
                {
                    if (item.Title.Contains(queryText))
                    {
                        filteredItems.Add(item);
                        foundCount++;
                        totalCount++;
                    }
                }
                filterList.Add(new Filter(group.Title, foundCount));
            }
            filterList.Insert(0, new Filter("All", totalCount, true));

            // ビュー モデルを介して結果を通信します
            this.DefaultViewModel["QueryText"] = '\u201c' + queryText + '\u201d';
            this.DefaultViewModel["Filters"] = filterList;
            this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;
        }

となっていて、SampleDataSourceに登録されたグループ、グループを介して検索クエリーテキストに合致するSampleDataItemを数え上げ、グループ毎にFilterインスタンスを生成して、filterListに格納、最後にDefaultViewModelのFiltersに登録しています。この操作で、検索結果表示ページのフィルターリストで表示される項目が決定されています。
同時に、このページのメンバーのfilteredItemsリストに、条件に合致したSampleDataItemの追加も行っています。
更にSearchResultsPage.xamlをよく見ていくと、103行目から

103                <ComboBox
104                    ItemsSource="{Binding Source={StaticResource filtersViewSource}}"
105                    Visibility="{Binding ShowFilters, Converter={StaticResource BooleanToVisibilityConverter}}"
106                    Margin="20,0,20,20"
107                    SelectionChanged="Filter_SelectionChanged"
108                    HorizontalAlignment="Left"/>

と宣言されています。この結果としてDefaultViewModel[”Filters"]を更新すると、SearchResultsPage.xaml.csのFilter_SelectionChangedがコールされることになります。

        void Filter_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // 選択されたフィルターを確認します
            var selectedFilter = e.AddedItems.FirstOrDefault() as Filter;
            if (selectedFilter != null)
            {
                // 対応する Filter オブジェクト内に結果をミラー化し、
                // いない場合に RadioButton 表現を使用して変更を反映できるようにします
                selectedFilter.Active = true;

                // TODO: this.DefaultViewModel["Results"] をバインド可能な Image、Title、および Subtitle の
                //       バインドできる Image、Title、Subtitle、および Description プロパティを持つアイテムのコレクションに設定します

                // 結果が見つかったことを確認します
                var items = new List<Data.SampleDataItem>();
                foreach (var item in filteredItems)
                {
                    if (selectedFilter.Name == "All")
                    {
                        items.Add(item);
                    }
                    else
                    {
                        if (item.Group.Title == selectedFilter.Name)
                        {
                            items.Add(item);
                        }
                    }
                }

                this.DefaultViewModel["Results"] = items;

                object results;
                ICollection resultsCollection;
                if (this.DefaultViewModel.TryGetValue("Results", out results) &&
                    (resultsCollection = results as ICollection) != null &&
                    resultsCollection.Count != 0)
                {
                    VisualStateManager.GoToState(this, "ResultsFound", true);
                    return;
                }
            }

            // 検索結果がない場合は、情報テキストを表示します。
            VisualStateManager.GoToState(this, "NoResultsFound", true);
        }

このメソッドでは、フィルター(グループ)の選択に合わせて、filteredItemsに格納されている検索クエリーテキストに合致する項目の中から、選択されたグループに所属する(Allの場合は全部)SampleDataItemのみを抽出してitemsに格納し、DefaultViewModel[”Results"]に代入しています。検索結果は前の方で言及した、GridView(x:Name="resultsGridView")の定義に従って表示されることになります。

…と、少々説明が長くなってしまいましたが、検索ロジックだけを変える場合は、LoadStateメソッドのロジックだけ変えれば十分です。ちなみにBasic Photoアプリテンプレートでは、検索クエリーテキストを含む(Contains)SampleDataItemを検索ヒットとしています。

デザインを変える場合は・・・、その前にもう一つ説明を加えておかないといけませんね。先ほどチラッと説明した103行目から始まるComboBoxです。これは、97行目から始まるGrid(x:Name="snappedPanel")の内部構造です。このGridで、Snap表示のデザインを規定しています。Full表示時との対比を示すと

Full表示

Snap表示

フィルターリスト

44行目~ItemsControlで表示

103行目~ComboBoxで表示

検索結果項目

69行目~GridViewで表示

124行目~ListViewで表示

 

 

 

 

 

 

 

 

こうなっています。

なので、デザインを変える場合には、Full表示のItemsControlとGridViewの変更だけでなく、Snap表示のComboBoxとListViewも忘れずに変更してください。

検索結果の各項目の表示は、CommonフォルダーのStandardStyles.xamlで定義されている、Full表示の場合:StandardSmallIcon300x70ItemTemplate、Snap表示の場合:StandardSmallIcon70ItemTemplateで規定されています。
項目表示のデザインを変えたい場合には、そちらに手をいれてくださいね。