Implementing search
[This post is a part of a series of post about the Social Media Dashboard Sample. This post was written by Peter Bryntesson. For an introductory blog post, click here]
The Social Media Dashboard sample implements the search contract in Windows 8. As nothing similar exists on Windows Phone this blog post will only be about Windows 8 development.
What is the search contract?
The search contract in Windows 8 is used to enable in-app searching in a uniform and consistent way. No matter what the user wants to search for he can do it from a single place, the search charm. Implementing the search contract is easy but since you can be searched when your app is not active or even started, it needs to be ingrained in your start up code. Here is a walk-through of the implementation of search in the Social Media Dashboard. More information about implementing the search contract in your application can be found here.
The first thing we have to do is to let the system now that our app is searchable. This is done by adding a declaration for search in the app manifest:
Next, you need to add a handler in your App object so that the system can activate your app when the user wants to search. It’s called OnSearchActivated and looks like this:
protected async override void OnSearchActivated(
SearchActivatedEventArgs args)
{
// Register the Windows.ApplicationModel.Search.SearchPane.
// GetForCurrentView().QuerySubmitted event in
// OnWindowCreated to speed up searches once the application
// is already running
SearchPane.GetForCurrentView().QuerySubmitted +=
App_QuerySubmitted;
//SearchPane.GetForCurrentView().QueryChanged
//SearchPane.GetForCurrentView().SuggestionsRequested
// If the Window isn't already using Frame navigation,
// insert our own Frame
var previousContent = Window.Current.Content;
var frame = previousContent as Frame;
// If the app does not contain a top-level frame,
// it is possible that this
// is the initial launch of the app. Typically this
// method and OnLaunched
// in App.xaml.cs can call a common method.
if (frame == null)
{
// Create a Frame to act as the navigation context
// and associate it with a SuspensionManager key
frame = new Frame();
SocialMediaDashboard.W8.Common.SuspensionManager.
RegisterFrame(frame, "AppFrame");
if (args.PreviousExecutionState ==
ApplicationExecutionState.Terminated)
{
// Restore the saved session state only when appropriate
try
{
await SocialMediaDashboard.W8.Common.
SuspensionManager.RestoreAsync();
}
catch (SocialMediaDashboard.W8.Common.
SuspensionManagerException)
{
//Something went wrong restoring state.
//Assume there is no state and continue
}
}
bool loadState = (args.PreviousExecutionState ==
ApplicationExecutionState.Terminated);
ExtendedSplash extendedSplash = new ExtendedSplash(
args.SplashScreen, loadState, args.QueryText);
Window.Current.Content = extendedSplash;
}
else
{
frame.Navigate(typeof(SearchResultsPage),
args.QueryText);
Window.Current.Content = frame;
}
// Ensure the current window is active
Window.Current.Activate();
}
This looks very similar to OnLaunched() and it does basically the same thing. What’s special about this function is that we get the searchable query as one property of args. Also, here we register a SearchPane’s QuerySubmitted event handler, which gets called when we do subsequent searches. This code also makes sure that Everything is loaded before we launch the search (By displaying the extended splash screen as described here). Everything related to the actual search is relegated to the SearchResultPage which we simply navigate to.
The code for SearchResultPage code behind class:
public sealed partial class SearchResultsPage :
SocialMediaDashboard.W8.Common.LayoutAwarePage
{
public SearchResultsPage()
{
this.InitializeComponent();
}
protected override void LoadState(
Object navigationParameter,
Dictionary<String, Object> pageState)
{
var queryText = navigationParameter as String;
// Do the search
Items = HubDataSource.Search(queryText);
this.DefaultViewModel["Items"] = Items;
this.DefaultViewModel["QueryText"] = '\u201c' +
queryText + '\u201d';
// Display informational text
// if there are search results.
if (Items.Count() > 0)
VisualStateManager.GoToState(
this, "ResultsFound", true);
else
VisualStateManager.GoToState(
this, "NoResultsFound", true);
progress.Visibility = Visibility.Collapsed;
if (App.configdata != null)
pageTitle.Text = App.configdata.appname;
}
public ObservableCollection<HubDataItem> Items
{ get; set; }
private async void ItemClick(
object sender,
ItemClickEventArgs e)
{
var item = e.ClickedItem;
if (item.GetType() ==
typeof(VideoDataItem))
this.Frame.Navigate(
typeof(VideoDetailPage),
item as VideoDataItem);
else if (item.GetType() ==
typeof(SpotlightVideoDataItem))
this.Frame.Navigate
(typeof(VideoDetailPage),
item as SpotlightVideoDataItem);
else if (item.GetType() ==
typeof(BlogPostDataItem))
{
this.Frame.Navigate(
typeof(ScrollingBlogPostDetailPage),
item as BlogPostDataItem);
}
else if (item.GetType() ==
typeof(BlogFeedDataItem))
{
this.Frame.Navigate(
typeof(GroupFullViewPage),
item as BlogFeedDataItem);
}
else if (item.GetType() ==
typeof(FlickrImageDataItem))
{
this.Frame.Navigate(
typeof(ImageFullViewPage),
item as FlickrImageDataItem);
}
else if (item.GetType() ==
typeof(SpotlightFlickrImageDataItem))
{
this.Frame.Navigate(
typeof(ImageFullViewPage),
item as SpotlightFlickrImageDataItem);
}
else if (item.GetType() ==
typeof(TwitterDataItem))
{
this.Frame.Navigate(
typeof(TwitterPostDetailPage),
item as TwitterDataItem);
}
else if (item.GetType() ==
typeof(CustomFeedDataItem))
{
var customItem = item as CustomFeedDataItem;
if (customItem.Link != null)
{
await Launcher.LaunchUriAsync(
customItem.Link);
}
}
else if (item.GetType() ==
typeof(SpotlightDataItem))
{
if ((item as SpotlightDataItem).TargetItem is
FlickrImageDataItem)
{
this.Frame.Navigate(
typeof(ImageFullViewPage),
(item as SpotlightDataItem).TargetItem as
FlickrImageDataItem);
}
else if ((item as SpotlightDataItem).TargetItem is
VideoDataItem)
{
this.Frame.Navigate(typeof(VideoDetailPage),
(item as SpotlightDataItem).TargetItem as
VideoDataItem);
}
}
}
}
If we look into the LoadState() function first, we can see that it calls the HubDataSource.Search() to get a collection of items to display. For more information on how search is internally implemented, see blog post about the anatomy of Social Media Dashboard here. This function works on in-memory data and therefore we can guarantee the execution time and there is no need to do an asynchronous call. The ItemClick event handler is just a rather convoluted function that acts on the different types of item we can display.
Summary
Implementing the search contract is not complicated but is an important thing to do in order to be Windows 8 compliant and make search within your application more discoverable.
For more information about Windows 8 app development, go here.
For more information about Windows Phone development, go here.