Windows 8–Search Suggestions

I recently started a series of postings from my tour of presentations in EuropeSearchResults and the UK.  Today I’d like to return to Searching (which I started to cover here), and this time take a look at what it takes to have your application offer Search suggestions as the user types into the Search box. 

To examine this, we’ll create a Windows 8 application with the suggestions hard coded, but you can just as easily obtain the suggestions from a web service, or database, etc.

Once again what I’m going to do is strip down one of the Microsoft SDK examples and then build it up from scratch.  You can find the original sample by clicking on Help->Samples in Visual Studio.  The one you want is Search Contract.

To begin, create a new Blank application in Visual Studio and call it SearchQueryList.  The very first thing to do is to double-click on PackageAppxManifest and then click on the Declarations tab.  Choose Search from the available declarations and add it to the project.

Open App.xaml.cs and override OnSearchActivated as follows,

   1: async protected override void OnSearchActivated(SearchActivatedEventArgs args)

   2: {

   3:     await EnsureMainPageActivatedAsync(args);

   4:     if (args.QueryText == "")

   5:     {

   6:         // navigate to landing page.

   7:     }

   8:     else

   9:     {

  10:         MainPage.Current.ProcessQueryText(args.QueryText);

  11:     }

  12: }

This makes a call to EnsureMainPageActivatedAsync, so let’s add that as well, while we are here,

   1: async private Task EnsureMainPageActivatedAsync(IActivatedEventArgs args)

   2: {

   3:     if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)

   4:     {

   5:         // Do an asynchronous restore

   6:  

   7:     }

   8:  

   9:     if (Window.Current.Content == null)

  10:     {

  11:         var rootFrame = new Frame();

  12:         rootFrame.Navigate(typeof(MainPage));

  13:         Window.Current.Content = rootFrame;

  14:     }

  15:  

  16:     Window.Current.Activate();

  17: }

Let’s turn to MainPage.xaml where our UI is dead-simple, just a text block to display the user’s query selection,

   1: <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

   2:     <TextBlock x:Name="StatusBlock"

   3:                Margin="0,0,0,5"

   4:                Text="Ready..."

   5:                FontSize="42"/>

   6: </Grid>

All the interesting work happens in the code behind for MainPage. 

We begin by defining a static reference to MainPage itself, along with a private member of type SearchPane. 

   1: public static MainPage Current;

   2: private SearchPane searchPane;

These are initialized in the constructor,

   1: public MainPage()

   2: {

   3:     this.InitializeComponent();

   4:     Current = this;

   5:     searchPane = SearchPane.GetForCurrentView();

   6: }

In addition, at the top of the file, we create a constant and, more important, we initialize a static array of strings that will serve for our suggestion list,

   1: internal const int SearchPaneMaxSuggestions = 5;

   2:  

   3: private static readonly string[] suggestionList =

   4:     {

   5:         "Shanghai", "Istanbul", "Karachi", "Delhi", "Mumbai", "Moscow", "São Paulo", "Seoul", "Beijing", "Jakarta",

   6:         "Tokyo", "Mexico City", "Kinshasa", "New York City", "Lagos", "London", "Lima", "Bogota", "Tehran", "Ho Chi Minh City",

   7:         "Hong Kong", "Bangkok", "Dhaka", "Cairo", "Hanoi", "Rio de Janeiro", "Lahore", "Chonquing", "Bangalore", "Tianjin",

   8:         "Baghdad", "Riyadh", "Singapore", "Santiago", "Saint Petersburg", "Surat", "Chennai", "Kolkata", "Yangon", "Guangzhou",

   9:         "Alexandria", "Shenyang", "Hyderabad", "Ahmedabad", "Ankara", "Johannesburg", "Wuhan", "Los Angeles", "Yokohama",

  10:         "Abidjan", "Busan", "Cape Town", "Durban", "Pune", "Jeddah", "Berlin", "Pyongyang", "Kanpur", "Madrid", "Jaipur",

  11:         "Nairobi", "Chicago", "Houston", "Philadelphia", "Phoenix", "San Antonio", "San Diego", "Dallas", "San Jose",

  12:         "Jacksonville", "Indianapolis", "San Francisco", "Austin", "Columbus", "Fort Worth", "Charlotte", "Detroit",

  13:         "El Paso", "Memphis", "Baltimore", "Boston", "Seattle Washington", "Nashville", "Denver", "Louisville", "Milwaukee",

  14:         "Portland", "Las Vegas", "Oklahoma City", "Albuquerque", "Tucson", "Fresno", "Sacramento", "Long Beach", "Kansas City",

  15:         "Mesa", "Virginia Beach", "Atlanta", "Colorado Springs", "Omaha", "Raleigh", "Miami", "Cleveland", "Tulsa", "Oakland",

  16:         "Minneapolis", "Wichita", "Arlington", " Bakersfield", "New Orleans", "Honolulu", "Anaheim", "Tampa", "Aurora",

  17:         "Santa Ana", "St. Louis", "Pittsburgh", "Corpus Christi", "Riverside", "Cincinnati", "Lexington", "Anchorage",

  18:         "Stockton", "Toledo", "St. Paul", "Newark", "Greensboro", "Buffalo", "Plano", "Lincoln", "Henderson", "Fort Wayne",

  19:         "Jersey City", "St. Petersburg", "Chula Vista", "Norfolk", "Orlando", "Chandler", "Laredo", "Madison", "Winston-Salem",

  20:         "Lubbock", "Baton Rouge", "Durham", "Garland", "Glendale", "Reno", "Hialeah", "Chesapeake", "Scottsdale",

  21:         "North Las Vegas", "Irving", "Fremont", "Irvine", "Birmingham", "Rochester", "San Bernardino", "Spokane",

  22:         "Toronto", "Montreal", "Vancouver", "Ottawa-Gatineau", "Calgary", "Edmonton", "Quebec City", "Winnipeg", "Hamilton"

  23:     };

In the OnNavigatedTo method we register to be notified of three events:

  • Query suggestions are requested
  • A Query suggestion is chosen
  • The Query is submitted
   1: protected override void OnNavigatedTo(NavigationEventArgs e)

   2: {

   3:     SearchPane.GetForCurrentView().QuerySubmitted += 

   4:         new TypedEventHandler<SearchPane, SearchPaneQuerySubmittedEventArgs>

   5:             (OnQuerySubmitted);

   6:  

   7:     searchPane.SuggestionsRequested += 

   8:         new TypedEventHandler<SearchPane, SearchPaneSuggestionsRequestedEventArgs>

   9:             (OnSearchPaneSuggestionsRequested);

  10:  

  11:     searchPane.ResultSuggestionChosen += OnSearchPaneResultSuggestionChosen;

  12: }

Key to this discussion is the implementation of OnSearchPaneSuggestionsRequested – the method that is called as each letter is typed into the query pane,

Let’s take this method apart, step by step.

We begin by extracting the query string and making sure it isn’t empty,

   1: private void OnSearchPaneSuggestionsRequested(

   2:     SearchPane sender, SearchPaneSuggestionsRequestedEventArgs e)

   3: {

   4:     var queryText = e.QueryText;

   5:     if (string.IsNullOrEmpty(queryText))

   6:     {

   7:        NotifyUser("Use the search pane to submit a query");

   8:     }

   9:     else

  10:     {

We pull out the request which will hold our suggestions (unless we have an exact match), initialize a local boolean to false to keep track of whether we have found a recommendation, and begin iterating through each string in our suggestion list, so that we can compare it with the query string,

   1: var request = e.Request;

   2: bool isRecommendationFound = false;

   3: foreach (string suggestion in suggestionList)

   4: {

The first thing we check for is an exact match,

   1: if (suggestion.CompareTo(queryText) == 0)

   2: {

If we get an exact match we’re going to show the match prepended by an image that we’ve placed in the Assets folder,

   1: RandomAccessStreamReference image = 

   2:     RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/windows-sdk.png"));

   3:  

   4: request.SearchSuggestionCollection.AppendResultSuggestion(

   5:     suggestion,              // text to display 

   6:     "found " + suggestion,   // details

   7:     "tag " + suggestion,     // tags usable when called back by ResultSuggestionChosen event

   8:     image,                   // image if any

   9:     "image of " + suggestion // image alternate text

  10:     );

  11: isRecommendationFound = true;

Notice that in this case the string we’re assembling (with the image and the matching city name) is added by calling the AppendResultsSuggestion method on the SearchSuggestionCollection obtained from the request object we extracted earlier.  See the figure below for what this looks like,

SingleSearchResult

If we don’t get an exact match we look for a partial match and if we get one, we add the string to the suggestion list,

   1: else  // otherwise, this is a suggestion

   2:     if (suggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase))

   3:     {

   4:         // Add suggestion to Search Pane

   5:         request.SearchSuggestionCollection.AppendQuerySuggestion(suggestion);

   6:     }

We then add some code to break the search off if we have 5 or more hits,

   1: if (request.SearchSuggestionCollection.Size >= MainPage.SearchPaneMaxSuggestions)

   2: {

   3:     break;

   4: }

You can see what this looks like in the figure at the top of this article.

Finally, we update the TextBlock depending on whether we found any matches and if we found an exact match,

   1: if (request.SearchSuggestionCollection.Size > 0)

   2: {

   3:     // Demo: show if it is a recommendation or just a partial suggestion

   4:     string prefix = isRecommendationFound ? "* " : "";

   5:     NotifyUser(prefix + "Suggestions provided for query: " + queryText);

   6: }

   7: else

   8: {

   9:     NotifyUser("No suggestions provided for query: " + queryText);

  10: }

Once again, Windows is doing all the heavy lifting of obtaining the query and creating the suggestion list; all we have to do is to find the matching values and add them to the SearchSuggestionCollection in response to the SuggestionsRequested event.

You can find the complete source code for this project here

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Data, Essentials, Metro, Mini-Tutorial, Windows 8, WinRT and tagged . Bookmark the permalink.

7 Responses to Windows 8–Search Suggestions

Comments are closed.