Windows 8 XAML Tips - Async Search Suggestions

By Fons Sonnemans, posted on
2755 Views 3 Comments

There are a lot of examples on the web how you can add Search Suggestions to the Search Charm of a Windows 8 Metro application. But all the examples I found only demonstrate how to implement a synchronous version. In this blog I will show you how you can do this also asynchronous. I use this technique to fill my Search Suggestion by calling a web service asynchronous. Something which is very common because your data will problably be stored in the Web/Cloud.

Implement Search Charm

To demonstrate the solution I created a new project in Visual Studio 2012 using the 'Grid App (XAML)' project template. Next I added the 'Search Contract' item template to the project. This adds an SearchResultsPage to the project and adds the Search Declaration to the Package.appxmanifest.

In my App.cs file I added the InitSearch() method which I call from the OnLaunched() method. In this InitSearch() method I subscribe the app on SuggestionsRequested event of the current SearchPane view.

sealed partial class App : Application
{
    /// <summary>
    /// Initializes the singleton Application object.  This is the first line of authored code
    /// executed, and as such is the logical equivalent of main() or WinMain().
    /// </summary>
    public App()
    {
        this.InitializeComponent();
        this.Suspending += OnSuspending;
    }

    /// <summary>
    /// Invoked when the application is launched normally by the end user.  Other entry points
    /// will be used when the application is launched to open a specific file, to display
    /// search results, and so forth.
    /// </summary>
    /// <param name="args">Details about the launch request and process.</param>
    protected override async void OnLaunched(LaunchActivatedEventArgs args)
    {
        // Do not repeat app initialization when already running, just ensure that
        // the window is active
        if (args.PreviousExecutionState == ApplicationExecutionState.Running)
        {
            Window.Current.Activate();
            return;
        }

        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key
        var rootFrame = new Frame();
        SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // Restore the saved session state only when appropriate
            await SuspensionManager.RestoreAsync();
        }

        if (rootFrame.Content == null)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter
            if (!rootFrame.Navigate(typeof(GroupedItemsPage), "AllGroups"))
            {
                throw new Exception("Failed to create initial page");
            }
        }

        // Place the frame in the current Window and ensure that it is active
        Window.Current.Content = rootFrame;
        Window.Current.Activate();

        InitSearch();
    }

    private void InitSearch() {
        var view = SearchPane.GetForCurrentView();
        view.SuggestionsRequested += view_SuggestionsRequested;

        //view.QueryChanged += view__QueryChanged;
        //view.QuerySubmitted += view_QuerySubmitted;
        //view.ResultSuggestionChosen += view_ResultSuggestionChosen;
        //view.VisibilityChanged += view_VisibilityChanged;
    }

    private void view_SuggestionsRequested(SearchPane sender, 
                                    SearchPaneSuggestionsRequestedEventArgs args) {
        try {
            var list = Enumerable.Range(1, 4).Select(n => args.QueryText + " " + n);
            args.Request.SearchSuggestionCollection.AppendQuerySuggestions(list);
        } catch (Exception ex) {
            Debug.WriteLine(ex.Message);
        }
    }

In this example the SuggestionsRequested() method creates 4 query suggestions which are the querytext with the suffix 1 to 4. This will look like this if you run the app and start a search for the word 'demo'.

Problem

To simulate an async network call I added an awaited call to the Task.Delay() method in my view_SuggestionsRequested() method (line 5). I also added the async keyword to make the compiler happy. It is required if you use the await keyword.

private async void view_SuggestionsRequested(SearchPane sender, 
                                             SearchPaneSuggestionsRequestedEventArgs args) {
    try {
        // Simulate an async network call
        await Task.Delay(100);
        var list = Enumerable.Range(1, 4).Select(n => args.QueryText + " " + n);

        args.Request.SearchSuggestionCollection.AppendQuerySuggestions(list);
    } catch (Exception ex) {
        Debug.WriteLine(ex.Message);
        // A method was called at an unexpected time. (Exception from HRESULT: 0x8000000E)
    }
}

If I run the app the Search Suggestions don't work anymore. The catch blocks writes this message of the exception to the output window: 'A method was called at an unexpected time. (Exception from HRESULT: 0x8000000E)'.

Solution

The solution is quite simple but badly documented in the MSDN documentation of the SearchPane.SuggestionsRequested event. It states in a note that if you want to respond to this event asynchronously, you must use SearchPaneSuggestionsRequestedEventArgs.Request.GetDeferral. So in my code I retrieved the Deferral (line 5). And I called the Complete() method on it after the list is appended to the SearchSuggestionCollection (line 14).

private async void view_SuggestionsRequested(SearchPane sender,
                                             SearchPaneSuggestionsRequestedEventArgs args) {
    try {
        // Get the deferral
        var deferral = args.Request.GetDeferral();

        // Simulate an async network call
        await Task.Delay(100);
        var list = Enumerable.Range(1, 4).Select(n => args.QueryText + " " + n);

        args.Request.SearchSuggestionCollection.AppendQuerySuggestions(list);

        // Mark the deferral as Complete
        deferral.Complete();
    } catch (Exception ex) {
        Debug.WriteLine(ex.Message);
    }
}

This fixed the problem and my app runs like a charm ;-)

Closure and download

I hope you like my solution. You can download my code here.

Cheers,

Fons

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.

Leave a comment

Blog comments

Lander Donkey

13-Nov-2013 6:06
thank you

Alex

12-Apr-2014 11:00
Maybe you can put the deferral.Complete(); in the finally block ? I don't know what happened if this line is not call ^^

vivek

25-Jun-2014 5:40
Man this is brilliant. I spent a lot of time (almost one whole day) to fix this issue. Wouldn't have been able to unless I saw your blog :) Awesome.