かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Universal Windows Platform appのAutoSuggestBox

Windows 10 Insider Preview 10074 + VS2015 RC時点の情報です

UWP appになって検索ボックスが汎用化されてAutoSuggestBoxという名前のコントロールに置き換えられています。Windows 8.1になって、検索ボックスこそ至高という感じで登場したのに、いきなりお役御免です!ひどい。(あ、SearchBox自体はあるよ!)

愚痴っててもしょうがないので使っていきます。

デフォルトの見た目と挙動

まんまTextBoxです。

検索アイコンの出し方

検索ボックスとして使いたいときは、TextBoxの右側に虫眼鏡アイコンが出てほしいところです。これはQueryIconプロパティにIconElement(FontIconやSymbolIconなど)をセットすればOKです。RCの段階ではプロパティウィンドウが何もやってくれないので手書きXAMLで作ります。

QueryIconにSymbolIconのSymbolプロパティに設定する文字列を指定することで、簡単にSymbolIconを設定できるようになっています。虫眼鏡の場合はFindと設定すればOKです。

<!-- これが -->
<AutoSuggestBox HorizontalAlignment="Left" Margin="10,10,0,0" Width="364" >
    <AutoSuggestBox.QueryIcon>
        <SymbolIcon Symbol="Find" />
    </AutoSuggestBox.QueryIcon>
</AutoSuggestBox>

<!-- こう書ける -->
<AutoSuggestBox HorizontalAlignment="Left" Margin="10,10,0,0" Width="364" QueryIcon="Find" />

f:id:okazuki:20150508235630p:plain

サジェストの出し方

ItemsSourceプロパティがあるので、そいつにコレクションを突っ込んでやればOKです。一番それっぽい動きをさせるにはTextChangedイベントでユーザー入力のときのみにItemsSourceを更新する方法です。

<AutoSuggestBox HorizontalAlignment="Left" Margin="10,10,0,0" Width="364" QueryIcon="Find" 
                TextChanged="AutoSuggestBox_TextChanged"/>
private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
    if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
    {
        // ユーザーの入力のときItemsSourceに適切な値を入れる
        sender.ItemsSource = Enumerable.Range(0, 10).Select(x => sender.Text + x);
    }
}

実行すると、以下のように検索候補が出るようになります。

f:id:okazuki:20150509000207p:plain

検索条件の確定時の処理

QuerySubmittedイベントが飛んできます。こんな感じで検索結果をイベント引数から取得できます。

private async void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
    await new MessageDialog(args.QueryText).ShowAsync();
}

文字列以外の検索

シンプルな文字列の場合は上記方法でいいのですが、オブジェクトを検索候補として使いたいということもあるかと思います。例えば以下のPersonクラス。

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

これを、TextChangedイベントでItemsSourceに突っ込むことができます。

private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
    if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
    {
        // ユーザーの入力のときItemsSourceに適切な値を入れる
        sender.ItemsSource = Enumerable.Range(0, 10).Select(x => new Person
        {
            Name = sender.Text,
            Age = x
        });
    }
}

このままだとToStringした結果が検索候補に出るのでItemTemplateで見た目を整えます。

<AutoSuggestBox HorizontalAlignment="Left" Margin="10,43,0,0" Width="364" QueryIcon="Find" 
                TextChanged="AutoSuggestBox_TextChanged" QuerySubmitted="AutoSuggestBox_QuerySubmitted">
    <AutoSuggestBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Name}" Style="{ThemeResource CaptionTextBlockStyle}" />
                <TextBlock Text="{Binding Age}" Style="{ThemeResource BodyTextBlockStyle}" />
            </StackPanel>
        </DataTemplate>
    </AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>

こうすると、以下のようになります。

f:id:okazuki:20150509001124p:plain

検索条件を選択肢から選んだときの処理を書きます。ここでオブジェクトから文字列へマッピングします。イベントは、SuggestionChosenになります。

private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
    var selectedItem = args.SelectedItem as Person;
    sender.Text = string.Format("{0}さん {1}才", selectedItem.Name, selectedItem.Age);
}

こうすると選択候補を選ぶとxxxさん xx才と表示されます。

最後に、検索結果が確定したときの処理になります。このとき、文字入力からの検索なのか、検索候補を選択した検索なのかを判定して処理を分けることができます。コードを示します。

private async void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
    if (args.ChosenSuggestion == null)
    {
        // 文字入力による検索
        await new MessageDialog(args.QueryText + "と入力されました").ShowAsync();
    }
    else
    {
        // 検索候補選択による入力
        var selectedItem = args.ChosenSuggestion as Person;
        await new MessageDialog(selectedItem.Name + "さんが選ばれました").ShowAsync();
    }
}

まとめ

SearchBoxなんてなかった。