Mobile Zone is brought to you in partnership with:

I love technology. Plain and simple. You can generally find me writing software, figuring out how to get rid of cable, playing on my Xbox, or traveling around this country speaking about software development. If you're doing something cool with technology, let me know. I'd love to hear your story about something amazing. You can find my blog at jeffblankenburg.com Jeff is a DZone MVB and is not an employee of DZone and has posted 71 posts at DZone. You can read more from them at their website. View Full User Profile

31 Days of Windows 8 | Day #6: Search Contract

11.11.2012
| 2515 views |
  • submit to reddit
This article is Day #6 in a series called 31 Days of Windows 8.  Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.

 

advertisementsample

day5-charms-sideLast time we introduced Contracts by exploring how to add Settings to our applications. Today we’re going to build upon that with Search, and tomorrow Share. Search and Share are two very interesting contracts because they bring life to your app even when it’s not actually running. In the context of search this means your application can potentially expose itself to the user in a new manner.

For the past 10 years, "search” has typically been synonymous with search engines. A few year ago, search actually became mainstream in Windows and if you were like me, you became very used to hitting the start button and just typing the name of your program rather than looking for it. Today in Windows 8 you can just type and those results are nicely displayed.

Better yet, Windows 8 is really extending how we think about search and bringing it right into our Windows Store Apps. Now it’s not really any magic, we just wire up to a few events and display the right things to our users but it changes the typical “entry point” into our applications. Now, rather than our user opening our app, finding your search box and searching, they can hit search and be taken directly to the thing they were looking for inside your app without any other steps.

Getting Set Up

As we have done every day since the beginning of this series, we are going to start with the Blank App template.  Again, the reason we do this is because there is so much extra code to filter through just to find the thing you’re looking for.

Once you’ve gotten your new project together (and replaced the default image set…you did that right?  Don’t you remember Day #1You can still download that set of images here.)  Now we’re going to add Search to our application.  Thankfully, basic search is only a click away.

Right-click on your project, and choose Add > New Item… > Search Contract

6-XAML-AddSearchContract

It asks you for a page name to go with this.  I’ve used SearchResults.xaml, but you can call your page whatever you’d like.  When you click the “Add” button, you should be prompted to add some additional files to your project.

6-XAML-ConfirmationBox

We will discuss each of these files briefly a little later, but for now, you should now see a project solution that looks similar to this one:

6-XAML-SolutionExplorer

At this point, you should be able to run your project, choose the Search charm, and start typing away.  Here’s what my app currently looks like when I search for “taco.”

6-XAML-EmptySearchBox

As you can see, it works, but we’re not really providing any value because we don’t have anything to search.  That comes next.  Before we get to that, let’s talk about the philosophy behind search in Windows 8.

Search Philosophy

When you think about traditional Windows 8 applications, they’re generally on the smaller side.  You won’t find a version of Outlook, for example, that manages so many types of data.  Instead, there’s a Calendar app, a Mail app, and a People app.

Windows 8 apps are supposed to do one thing, and do that one thing really well.  Hopefully I am describing the app you’re building right now.  Windows 8 applications can also be searched even when they’re not currently running.  This means that when a user is searching for something, your app’s name and icon should immediately bring certain types of data into their mind.

For example, the iHeartRadio app.  If I’m searching that, I’m likely looking for a radio station.

6-XAML-IHeartRadio

For the Weather application, I’m likely searching for a city.

6-XAML-Weather

Netflix, I’m probably looking for the title of a movie or show.

6-XAML-Netflix

Finally, the People app assumes I’m looking for a person’s name.

6-XAML-People

So, as you’re building your application, ask yourself this question:

“What would users expect to search for in my app?”

Another important discussion about search revolves around the concept of “Search” vs. “Find.”  The Search charm should NOT be used for looking inside a document.  Search should always be something that can be done to your app, even if it’s not currently running.

Here is Microsoft’s specific guidance on how to use the Search Charm.

Making Search Work

So as we saw in our opening paragraphs, adding the Search Contract to your project is only the first step.  Next, we need to provide some valuable data for the user to search.  If you’ll recall in my Day #4 article, I created a small set of elements from the Periodic Table to demonstrate the SemanticZoom control.

We can use that same data in this example.  In case you missed it, here’s the Element class:

namespace Day6_SearchContract
{
    class Element
    {
        public double AtomicWeight { get; set; }
        public int AtomicNumber { get; set; }
        public string Symbol { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public string State { get; set; }
    }
}

For simplicity, I am going to populate a List<Element> in our SearchResults.xaml.cs file.  I’ll start by creating a new method, BuildElementList() and calling it from our constructor, and then creating a new Element object for the 36 elements we’ll be using.

List<Element> elements = new List<Element>();
 
public SearchResults()
{
    this.InitializeComponent();
    BuildElementList();
}
 
private void BuildElementList()
{
    elements.Add(new Element { AtomicNumber = 1, AtomicWeight = 1.01, Category = "Alkali Metals", Name = "Hydrogen", Symbol = "H", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 2, AtomicWeight = 4.003, Category = "Noble Gases", Name = "Helium", Symbol = "He", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 3, AtomicWeight = 6.94, Category = "Alkali Metals", Name = "Lithium", Symbol = "Li", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 4, AtomicWeight = 9.01, Category = "Alkaline Earth Metals", Name = "Beryllium", Symbol = "Be", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 5, AtomicWeight = 10.81, Category = "Non Metals", Name = "Boron", Symbol = "B", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 6, AtomicWeight = 12.01, Category = "Non Metals", Name = "Carbon", Symbol = "C", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 7, AtomicWeight = 14.01, Category = "Non Metals", Name = "Nitrogen", Symbol = "N", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 8, AtomicWeight = 15.999, Category = "Non Metals", Name = "Oxygen", Symbol = "O", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 9, AtomicWeight = 18.998, Category = "Non Metals", Name = "Fluorine", Symbol = "F", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 10, AtomicWeight = 20.18, Category = "Noble Gases", Name = "Neon", Symbol = "Ne", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 11, AtomicWeight = 22.99, Category = "Alkali Metals", Name = "Sodium", Symbol = "Na", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 12, AtomicWeight = 24.31, Category = "Alkaline Earth Metals", Name = "Magnesium", Symbol = "Mg", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 13, AtomicWeight = 26.98, Category = "Other Metals", Name = "Aluminum", Symbol = "Al", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 14, AtomicWeight = 28.09, Category = "Non Metals", Name = "Silicon", Symbol = "Si", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 15, AtomicWeight = 30.97, Category = "Non Metals", Name = "Phosphorus", Symbol = "P", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 16, AtomicWeight = 32.06, Category = "Non Metals", Name = "Sulfur", Symbol = "S", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 17, AtomicWeight = 35.45, Category = "Non Metals", Name = "Chlorine", Symbol = "Cl", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 18, AtomicWeight = 39.95, Category = "Noble Gases", Name = "Argon", Symbol = "Ar", State = "Gas" });
    elements.Add(new Element { AtomicNumber = 19, AtomicWeight = 39.10, Category = "Alkali Metals", Name = "Potassium", Symbol = "K", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 20, AtomicWeight = 40.08, Category = "Alkaline Earth Metals", Name = "Calcium", Symbol = "Ca", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 21, AtomicWeight = 44.96, Category = "Transitional Metals", Name = "Scandium", Symbol = "Sc", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 22, AtomicWeight = 47.90, Category = "Transitional Metals", Name = "Titanium", Symbol = "Ti", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 23, AtomicWeight = 50.94, Category = "Transitional Metals", Name = "Vanadium", Symbol = "V", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 24, AtomicWeight = 51.996, Category = "Transitional Metals", Name = "Chromium", Symbol = "Cr", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 25, AtomicWeight = 54.94, Category = "Transitional Metals", Name = "Manganese", Symbol = "Mn", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 26, AtomicWeight = 55.85, Category = "Transitional Metals", Name = "Iron", Symbol = "Fe", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 27, AtomicWeight = 58.93, Category = "Transitional Metals", Name = "Cobalt", Symbol = "Co", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 28, AtomicWeight = 58.70, Category = "Transitional Metals", Name = "Nickel", Symbol = "Ni", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 29, AtomicWeight = 63.55, Category = "Transitional Metals", Name = "Copper", Symbol = "Cu", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 30, AtomicWeight = 65.37, Category = "Transitional Metals", Name = "Zinc", Symbol = "Zn", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 31, AtomicWeight = 69.72, Category = "Other Metals", Name = "Gallium", Symbol = "Ga", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 32, AtomicWeight = 72.59, Category = "Other Metals", Name = "Germanium", Symbol = "Ge", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 33, AtomicWeight = 74.92, Category = "Non Metals", Name = "Arsenic", Symbol = "As", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 34, AtomicWeight = 78.96, Category = "Non Metals", Name = "Selenium", Symbol = "Se", State = "Solid" });
    elements.Add(new Element { AtomicNumber = 35, AtomicWeight = 79.90, Category = "Non Metals", Name = "Bromine", Symbol = "Br", State = "Liquid" });
    elements.Add(new Element { AtomicNumber = 36, AtomicWeight = 83.80, Category = "Noble Gases", Name = "Krypton", Symbol = "Kr", State = "Gas" });
}

Obviously, you’re going to want to pull your data from another location, but this is an article about searching, not about data sources. :)

If you look in the default Filter_SelectionChanged() method that was created in SearchResults.xaml.cs, you should see some comments that look like this:

// TODO: Respond to the change in active filter by setting this.DefaultViewModel["Results"]// to a collection of items with bindable Image, Title, Subtitle, and Description properties

Right beneath those comments, we are going to add our logic to pull the appropriate records from our data source.  Before we do that, however, there are a bunch of assumptions made for you in this method, and it’s important to understand what those assumptions are before you write anything else.

The first assumption is that your search results are going be a collection of objects that each have an Image, Title, Subtitle, and Description property.  It’s unlikely that your data has those properties, and you don’t necessarily want to force your data to conform to what they’ve set up for you.  The reason for this assumption is pretty simple.

Briefly crack open your SearchResults.xaml page.  Look for the GridView control named “resultsGridView,” as well as the ListView control named “resultsListView.”  We’ve already covered these types of controls on Day #4, but they each have an ItemTemplate already defined.  This is where all the assumptions live.  They are using the default DataTemplate named “StandardSmallIcon300x70ItemTemplate.”  If you take a look at your Common/StandardStyles.xaml file, you’ll find it in there, and it’s trying to bind to those four properties that I mentioned earlier.  We don’t want to use this template, but it’s a good start.

What I have done is copy that entire DataTemplate, and moved it into my SearchResults.xaml page, in the Page.Resources section.  Here’s what I’ve modified that section to look like:

<Page.Resources>
    <CollectionViewSource x:Name="resultsViewSource" Source="{Binding Results}"/>
    <CollectionViewSource x:Name="filtersViewSource" Source="{Binding Filters}"/>
    <common:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
    <!-- TODO: Update the following string to be the name of your app -->
    <x:String x:Key="AppName">Search Contract Example</x:String>
    <DataTemplate x:Key="ModifiedSmallIcon300x70ItemTemplate">
        <Grid Width="294" Margin="6">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" Margin="0,0,0,10" Width="40" Height="40">
                <TextBlock Text="{Binding Symbol}" FontSize="32" FontWeight="Bold" TextAlignment="Center" VerticalAlignment="Center" />
            </Border>
            <StackPanel Grid.Column="1" Margin="10,-10,0,0">
                <TextBlock Text="{Binding Name}" Style="{StaticResource BodyTextStyle}" TextWrapping="NoWrap"/>
                <TextBlock Text="{Binding Category}" Style="{StaticResource BodyTextStyle}" Foreground="{StaticResource ApplicationSecondaryForegroundThemeBrush}" TextWrapping="NoWrap"/>
                <TextBlock Text="{Binding State}" Style="{StaticResource BodyTextStyle}" Foreground="{StaticResource ApplicationSecondaryForegroundThemeBrush}" TextWrapping="NoWrap"/>
            </StackPanel>
        </Grid>
    </DataTemplate>
</Page.Resources> 
You can see that it’s almost identical to the original, but I’ve renamed it to “ModifiedSmallIcon300x70ItemTemplate.”  The other differences are that I changed the Image control to another TextBlock, and changed the Bindings to the properties that my objects possess, Symbol, Name, Category, and State.  I’ve also changed the name of the ItemTemplates being used by the GridView and ListView to point to my modified version as well.  Now we can start providing some results to our SearchResults page.

Remember those comments from earlier?  The ones in the Filter_SelectionChanged() method?  Let’s revisit those, and add some code. 

IEnumerable<Element> searchResults = from el in elements
                                     where el.Name.ToLower().Contains(searchString)
   orderby el.Name ascending
                                     select el;
                
this.DefaultViewModel["Results"] = searchResults;
 
// Ensure results are found
object results;
IEnumerable<Element> resultsCollection;
 
if (this.DefaultViewModel.TryGetValue("Results", out results) &&
    (resultsCollection = results as IEnumerable<Element>) != null &&
    resultsCollection.Count() != 0)
{
    VisualStateManager.GoToState(this, "ResultsFound", true);
    return;
}

I’ve also modified some of the default code in the section below to explicitly call out that I’m using an IEnumerable collection of Element objects.  If our search term actually has some results, this logic will be what shows those results.

At this point, if you run your project and do a search, you should get something that looks like this:

6-XAML-SearchResults

You can also close the app entirely, perform a search on your machine, and then choose your app.  It works exactly the same way.

At this point, you’d think we’re done.  You’re wrong.  We’ve got much more to talk about yet.  Specifically, how to prompt the user with actual search values from your app that match what they’ve already typed.  Let’s take a look at that next.

Defining Search Suggestions

This is actually a pretty simple process, especially compared to what we’ve already accomplished in this article. In our same SearchResults.xaml page, we need to start with a reference to the SearchPane (this also requires the addition of the namespace Windows.ApplicationModel.Search).  We also instantiate the SearchPane object in our constructor.

SearchPane searchPane;
public SearchResults()
{
    this.InitializeComponent();
    BuildElementList();
    searchPane = SearchPane.GetForCurrentView();
}

Once you’ve gotten that set up, the next step is to create an event handler for the SuggestionsRequested event.  Create a new OnNavigatedTo method, like the one below, and add the new event handler.  (Also remove it by creating a OnNavigatingFrom method as well).

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    searchPane.SuggestionsRequested += searchPane_SuggestionsRequested;
}
 
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    base.OnNavigatingFrom(e);
    searchPane.SuggestionsRequested -= searchPane_SuggestionsRequested;
}
 
void searchPane_SuggestionsRequested(SearchPane sender, SearchPaneSuggestionsRequestedEventArgs args)
{
    args.Request.SearchSuggestionCollection.AppendQuerySuggestions((from el in elements 
                                        where el.Name.ToLower().StartsWith(args.QueryText.ToLower()) || el.Symbol.ToLower().StartsWith(args.QueryText.ToLower())
                                        orderby el.Name ascending
                                        select el.Name).Take(5));
}
You can see that I am simply providing the SearchSuggestionCollection with a set of (maximum) five string values as recommendations.  This is the most it can take, so I’m limiting my results to five at most. 

That’s it!  If you search for an element in this application now, you should see (based on my query) that if your typed characters match either an element name or symbol, and you’ll get up to five recommendations.


Forcing Search When Keystrokes Are Detected

There’s one last trick we should talk about, and that’s allowing the user to open the search box simply by typing.  By adding one simple statement to our OnLaunched and OnSuspending methods in our App.xaml.cs file, we can enable this functionality throughout our entire application.

Add this line to your OnLaunched method:

 
SearchPane.GetForCurrentView().ShowOnKeyboardInput = true;
 

And this line to your OnSuspending method to disable it when the app is closed:

 
SearchPane.GetForCurrentView().ShowOnKeyboardInput = false;
 

And that’s it!  Run your app, and just start typing.  The Search Charm will automatically open and start capturing your keystrokes!


Declarations

There’s one last thing that I want to cover in this article, and this is more for your knowledge than something that actually needs to be done.  When you added the Search Contract to your project, there were several files added to your project, but there was also a declaration made in your appmanifest file.  If you open that file, and navigate to the Declarations tab, you should see something that looks like this:

6-XAML-Declarations

Without this declaration, none of the search functionality we’ve added will be allowed to work.  You’ll get an error saying that access is denied.

6-XAML-DeclarationError

Just remember that you need to declare that you’re using Search before your application will actually be allowed to use those APIs.

Summary

Today, we took took a very quick look into adding Search into your application. The Search contract provides a new way for your app to expose your application to it’s users. Now, you just need to determine what exactly you want to expose. Getting yourself in front of your users ensures people are engage in your application. The more ways they use it, the better life is.

You can download the entire sample solution here here:

downloadXAML

Next time, we’re going to the final step in our contracts exploration, Sharing.  See you then!

Published at DZone with permission of Jeff Blankenburg, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)