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.
Blog comments
Lander Donkey
13-Nov-2013 6:06Alex
12-Apr-2014 11:00vivek
25-Jun-2014 5:40