WinRt

WinRT : Asynchronous code in SearchPane.SuggestionsRequested

As part of something a lot larger that I am currently working on I wanted to implement the Search charm logic in Windows 8.

Now being the model citizen that I am, I have read all the good technical bumpg around Windows 8, and I paid particular attention to the fact that we really need to keep the UI free, as anything more than 30/300ms (can’t recall which) on a touch system just feels broken.

So what does that mean? Well it means I have been a good chat and played nice and used Async/Await where needed. All good so far.

So now onto the Search charm. I obviously enabled this in the manifest file. Then I wrote the following code which I expected to work

sealed partial class App : Application
{
    public App()
    {
        this.InitializeComponent();
        this.Suspending += OnSuspending;
    }

    protected override async void OnLaunched(LaunchActivatedEventArgs args)
    {
        ....
        ....
        ....
        ....

        SetupSearch();
    }

    private async void OnSuspending(object sender, SuspendingEventArgs e)
    {
        var deferral = e.SuspendingOperation.GetDeferral();
        await SuspensionManager.SaveAsync();
        deferral.Complete();
    }

    private void SetupSearch()
    {
        var searchPane = SearchPane.GetForCurrentView();
        searchPane.SuggestionsRequested += searchPane_SuggestionsRequested;
        searchPane.ResultSuggestionChosen += searchPane_ResultSuggestionChosen;
    }

    void searchPane_ResultSuggestionChosen(SearchPane sender,
        SearchPaneResultSuggestionChosenEventArgs args)
    {
        MessageDialog dialog = new MessageDialog(string.Format("You picked {0}",args.Tag));
        dialog.ShowAsync();
    }

    async void searchPane_SuggestionsRequested(SearchPane sender,
        SearchPaneSuggestionsRequestedEventArgs args)
    {
        var suggestions = Enumerable.Range(1, 2).Select(x => new
        {
            Desc = string.Format("{0}_{1}", args.QueryText, x.ToString()),
            Id = x
        });

        //To simulate waiting on something more meaningful (such as a webservice etc etc)
        await Task.Delay(2000);

        var imageUri = new Uri("ms-appx:///Assets/search40.png");
        var imageSource = Windows.Storage.Streams.
              RandomAccessStreamReference.CreateFromUri(imageUri);
        args.Request.SearchSuggestionCollection.
              AppendSearchSeparator("Demo Suggestions");

        foreach (var suggestion in suggestions)
        {
            args.Request.SearchSuggestionCollection.AppendResultSuggestion(
                 "Suggestion",
              suggestion.Desc,
                suggestion.Id.ToString(),
                imageSource, "");
        }
    }
}

That looked fair enough to me. But when I ran this code I got this Exception

Most strange.

Turns out this is an issue for which there is a solution, that is just rather poorly documented. In fact it borrows a lot from the jQuery Deferred pattern (which you can read more about here)

So what is the solution. Well as I say WinRT has certainly borrowed from the jQuery Deferral / Promise idea. Where we ask for a Deferrable object, do some work, and then complete the Deferrable object.

So here is the code above refactored to use a WinRT Deferrable object. This technique can be used with most of the charms.

sealed partial class App : Application
{
    public App()
    {
        this.InitializeComponent();
        this.Suspending += OnSuspending;
    }

    protected override async void OnLaunched(LaunchActivatedEventArgs args)
    {
        ....
        ....
        ....
        ....

        SetupSearch();
    }

    private async void OnSuspending(object sender, SuspendingEventArgs e)
    {
        var deferral = e.SuspendingOperation.GetDeferral();
        await SuspensionManager.SaveAsync();
        deferral.Complete();
    }

    private void SetupSearch()
    {
        var searchPane = SearchPane.GetForCurrentView();
        searchPane.SuggestionsRequested += searchPane_SuggestionsRequested;
        searchPane.ResultSuggestionChosen += searchPane_ResultSuggestionChosen;
    }

    void searchPane_ResultSuggestionChosen(SearchPane sender,
        SearchPaneResultSuggestionChosenEventArgs args)
    {
        MessageDialog dialog = new MessageDialog(string.Format("You picked {0}",args.Tag));
        dialog.ShowAsync();
    }

    async void searchPane_SuggestionsRequested(SearchPane sender,
        SearchPaneSuggestionsRequestedEventArgs args)
    {
        var suggestions = Enumerable.Range(1, 2).Select(x => new
        {
            Desc = string.Format("{0}_{1}", args.QueryText, x.ToString()),
            Id = x
        });

        //FIXED CODE : Use args.Request.GetDeferral() / deferral.Complete

        var deferral = args.Request.GetDeferral();

        await Task.Delay(2000);

        var imageUri = new Uri("ms-appx:///Assets/search40.png");
        var imageSource = Windows.Storage.Streams.
                RandomAccessStreamReference.CreateFromUri(imageUri);
        args.Request.SearchSuggestionCollection.
                AppendSearchSeparator("Demo Suggestions");

        foreach (var suggestion in suggestions)
        {
            args.Request.SearchSuggestionCollection.AppendResultSuggestion(
                "Suggestion",
                suggestion.Desc,
                suggestion.Id.ToString(),
                imageSource, "");
        }

        deferral.Complete();
    }

}

As always here is a small demo project (requires Windows 8 / Visual Studio 2012)  : AsyncSearchSuggestionDemo.zip

2 thoughts on “WinRT : Asynchronous code in SearchPane.SuggestionsRequested

Leave a comment