65.9K
CodeProject is changing. Read more.
Home

WinRT: Asynchronous Code in SearchPane.SuggestionsRequested

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0 vote)

Mar 4, 2014

CPOL

1 min read

viewsIcon

12558

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