Build Your First Windows 8 App with Microsoft Visual C# and Visual Basic

  • 2/15/2013

Adding search functionality

In this section, you will add the code that enables the searching capability inside the application.

One thing you may notice in a Windows Store application project is the absence of direct references; if you open the References element in the project tree you will not find the classic System. Something assembly. Instead, there is just a .NET for Windows Store apps reference and a Windows reference. These contain all the Windows Runtime classes you need to develop Windows Store apps.

You can add the complete implementation of the search feature inside the application without adding any references; you need only to add a reference if you create your own class library, for which you would need to add a reference to the corresponding assembly. You can find more information about developing custom class libraries in Chapter 5.

In a previous procedure, you added the Search Declaration to the application, letting the operating system include the application in the Search pane. The declaration in the manifest tells the Windows 8 runtime: “I’m a searchable application.” In other words, the system will present the application as a possible target for a search inside the application itself. A search target is the scope for the user’s search, which may be a file in the file system, an installed application, a setting in the control panel, or some text inside a searchable application.

When the user selects the application as the target for his or her search, the application is activated for the search and the search string typed by the user is passed to the application. The idea is simple: the application is the only component that can correctly show the search result; no other component, nor the operating system itself, knows about the data inside the application. The way the application presents the data is tailored to the specific application data. In Chapter 6, you will learn more about search integration as well as about other WinRT APIs, such as Share, Webcam, FilePicker, and so on.

The search feature is implemented by a contract, called a search contract, that regulates the search interaction between an application and the operating system. The search contract states the following:

  • The application needs a registration. This registration is based on the manifest declaration.

  • The declaration can include the executable name, that is, the application .exe file name—the entry point for the application that the system will call when the user chooses the application as the search target.

  • The application will present the data in the appropriate format using a page.

  • The application will receive the search text entered by the user in the entry point. It is the responsibility of the application to present the page with some feedback to the user; the feedback can be the list of items found or a message (in case of search failure). The failure can be a “Not Found” text or graphics, or “Data not available, try again later.” Be as specific as you can with the message.

  • Windows manages the Search History for the user.

  • The application can provide suggestions for the text entered by the user.

Add the search contract

There is a Visual Studio template that provides a simple implementation of a contract that covers all the search points in the preceding list—except for the last one. The first step you will perform in this procedure is to remove the Search Declaration you added in a preceding procedure to explore the default implementation. Then follow the remaining steps to add the search functionality.

  1. Remove the Search Declaration from the manifest opening the Package.appxmanifest. Go to the Declarations tab, look for “Search” in the Supported Declaration list, select it, and click Remove. Save the manifest.

  2. Add a new Search Contract item by right-clicking the project in the Solution Explorer and choosing Add | New Item.

  3. In the Add New Item dialog, select Search Contract and name it SearchPeople.xaml.

    Click OK.

  4. In the dialog that asks you to add all the files you need to implement the contract, click Yes.

Test the default search component

Before doing anything else, you can test the application immediately to fully understand the complete flow. You will implement the people search in the procedure after this one.

  1. Deploy the application from Visual Studio by right-clicking the project element in the Solution Explorer and choosing Deploy.

  2. Press Windows+Q to activate the Search pane.

  3. Type the text you want in the search box and choose MyFirstApp from the application list. The operating system will launch the application (which was not running yet because you just deployed it), and activate the search inside the application using a call to the search contract entry point. The application shows the SearchPeople.xaml page that, obviously, presents no results yet.

  4. Close the application using Alt+F4 or Task Manager.

  5. Start the application from the Start screen.

  6. Press Windows+Q again to start a new search.

  7. Type some text in the search box and choose MyFirstApp in the application list. The result page is identical to the previous one, but the Back button is now enabled because the search target (your application) was already running when you activated the search.

  8. Click the Back button and note that the application is in the same state.

  9. Go to the Start screen and open another application (Mail works fine). Repeat steps 6 through 8. The result will be always a blank page. However, if you click the Back button, you can see the page that shows the previous search; this demonstrates that the application was put into the suspended state and resumed when the search target was activated.

  10. Press Alt+Tab (yes, that key combination still works in Windows 8) to select another application for the foreground.

  11. Go to the Start screen and launch your application. The application presents the search result because Windows 8 suspends the application and restores it if the user comes back.

Now that you have explored the search flow, it’s time to implement the Search Contract template. The template adds the Search Declaration to the Package.appxmanifest, as you can verify by double-clicking the file and selecting the Declarations tab.

This template also modifies the project—among other things, it adds a new page to display the search results (SearchPeople.xaml or whatever name you used in the Add New Item dialog) that you saw in the previous procedure when you chose MyFirstApp as the search target.

This new page is shown when a search is activated. The contract defines the entry point for the “search call” that, by default, is the App class.

The Search Contract Visual Studio Template also modified the App.xaml.cs file to override the OnSearchActivated method of the base class so that it shows the search result page. Example 3-5 shows the complete code for the App.xaml.cs file.

Example 3-5. Code-behind file for the App class: App.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227

namespace MyFirstApp
{
    /// <summary>
    /// Provides application-specific behavior to supplement the default Application class.
    /// </summary>
    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 void OnLaunched(LaunchActivatedEventArgs args)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate
                // to the first page
                rootFrame = new Frame();

                if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: Load state from previously suspended application
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            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(MainPage), args.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }
            // Ensure the current window is active
            Window.Current.Activate();
        }

        /// <summary>
        /// Invoked when application execution is being suspended.  Application state is saved
        /// without knowing whether the application will be terminated or
        /// resumed with the contents
        /// of memory still intact.
        /// </summary>
        /// <param name="sender">The source of the suspend request.</param>
        /// <param name="e">Details about the suspend request.</param>
        private void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            //TODO: Save application state and stop any background activity
            deferral.Complete();
        }

        /// <summary>
        /// Invoked when the application is activated to display search results.
        /// </summary>
        /// <param name="args">Details about the activation request.</param>
        protected async override void OnSearchActivated(Windows.ApplicationModel.Activation.
                  SearchActivatedEventArgs args)
        {
            // TODO: Register the Windows.ApplicationModel.Search.SearchPane.
                 GetForCurrentView().QuerySubmitted
            // event in OnWindowCreated to speed up searches once the application is already
                 running

            // 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();
                MyFirstApp.Common.SuspensionManager.RegisterFrame(frame, "AppFrame");

                if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    // Restore the saved session state only when appropriate
                    try
                    {
                        await MyFirstApp.Common.SuspensionManager.RestoreAsync();
                    }
                    catch (MyFirstApp.Common.SuspensionManagerException)
                    {
                        //Something went wrong restoring state.
                        //Assume there is no state and continue
                    }
                }
            }

            frame.Navigate(typeof(SearchPeople), args.QueryText);
            Window.Current.Content = frame;

            // Ensure the current window is active
            Window.Current.Activate();
        }
    }
}

The OnLaunched method is the standard code suggested by the Windows Store Application template and is needed to activate the main page when the user launches the application. An application is “launched” when its state is not running.

The OnSearchActivated method is the code for the Search Contract default implementation. The code instantiates the designated page and calls the Activate custom method to pass the received arguments.

The SearchActivatedEventArgs used by the OnSearchActivated method and the LaunchActivated EventArgs used by the OnLaunched methods both implement the IActivatedEventArgs interface.

The first property of the interface is Kind, and it can be one of the values defined in the Activation-Kind enumeration. This property lets the developer ask for the kind of activation during launching; for instance, if the application is launched by the user, this property will be ActivationKind.Launch. However, if the application is launched by the system when the user designates it as search target, the property will be ActivationKind.Search. If the application is activated to receive something from other applications using a Share Contract, the property will be ActivationKind.ShareTarget.

The QueryText property of the SearchActivatedEventArgs contains the text entered by the user in the Search pane. This property is used in the default OnSearchActivated method during the navigation to the search page, as you can see in the following excerpt.

frame.Navigate(typeof(SearchPeople), args.QueryText);
Window.Current.Content = frame;

// Ensure the current window is active
Window.Current.Activate();

As you can see, the search terms are received in the navigationParameter parameter of the LoadState method of the SearchPeople.xaml.cs page and used to build the QueryText property of the user interface in the DefaultViewModel property of the page. Example 3-6 shows the code for this method.

Example 3-6. Extract of SearchPeople.xaml.cs code behind

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var queryText = navigationParameter as String;

    // TODO: Application-specific searching logic.  The search process is responsible for
    //       creating a list of user-selectable result categories:
    //
    //       filterList.Add(new Filter("<filter name>", <result count>));
    //
    //       Only the first filter, typically "All", should pass true as a third argument in
    //       order to start in an active state.  Results for the active filter are provided
    //       in Filter_SelectionChanged below.

    var filterList = new List<Filter>();
    filterList.Add(new Filter("All", 0, true));

    // Communicate results through the view model
    this.DefaultViewModel["QueryText"] = '\u201c' + queryText + '\u201d';
    this.DefaultViewModel["Filters"] = filterList;
    this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;
}

The code in Example 3-6 is relatively simple. The first line defines a local variable called queryText to host the text entered by the user in the search box. This text is passed in the search contract as the QueryText property of the SearchActivatedEventArgs.

The placeholder lets you choose the business logic to look for the text in your data and represents the most important part of this code.

The last three lines of code are useful if you decide to use the default layout to display the search results. The code assigns the text for the query, the filters list and a Boolean to indicate whether to show the filters list in the bindable dictionary (IObservableMap in fact derives from IDictionary). Let’s try to implement the search by reusing the business layer you saw at the beginning of this chapter.

Implement the search logic

In the following procedure, you will implement the logic for retrieving the list of people. Although you can implement the logic using a LINQ (Language Integrated Query) query on the results from the business logic component List method, consider passing the search parameter to the business logic component to perform the search in lower layers. Generally speaking, it is a bad idea to filter the entire set of data in memory in the user interface layer. For the sake of simplicity, this sample application has no persistence layer. Thus, you will implement the search in memory inside the business layer.

  1. Add a method to the business logic component (Biz.cs) to filter the data source using the following code:

    public List<Person> GetPeople(String search)
    {
            var list = this.GetPeople();
            return list.Where(p => p.FullName.Contains(search)).ToList();
    }
  2. Add a call to the new GetPeople method from the SearchPeople.xaml.cs LoadState method and assign the result to the DefaultViewModel property. Use the following code as a reference (the lines to add are in bold).

    protected override void LoadState(Object navigationParameter,
        Dictionary<String, Object> pageState)
    {
        var queryText = navigationParameter as String;
    
        // TODO: Application-specific searching logic.  The search process is
        //       responsible for
        //       creating a list of user-selectable result categories:
        //
        //       filterList.Add(new Filter("<filter name>", <result count>));
        //
        //       Only the first filter, typically "All", should pass true as a third
        //       argument
        //       in order to start in an active state.  Results for the active filter
        //       are provided in Filter_SelectionChanged below.
    
        var biz = new Biz();
        var people = biz.GetPeople(queryText);
        this.DefaultViewModel["Results"] = people;
        var filterList = new List<Filter>();
        filterList.Add(new Filter("All", 0, true));
    
        // Communicate results through the view model
        this.DefaultViewModel["QueryText"] = '\u201c' + queryText + '\u201d';
        this.DefaultViewModel["Filters"] = filterList;
        this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;
    }
  3. Open SearchPeople.xaml and find the GridView control named resultGridView. Remove the ItemTemplate default definition and define a new one to show the person name for each result. The following code shows the complete control’s definition:

    <GridView
      x:Name="resultsGridView"
      AutomationProperties.AutomationId="ResultsGridView"
      AutomationProperties.Name="Search Results"
      TabIndex="1"
      Grid.Row="1"
      Margin="0,-238,0,0"
      Padding="110,240,110,46"
      SelectionMode="None"
      IsSwipeEnabled="false"
      IsItemClickEnabled="True"
      ItemsSource="{Binding Source={StaticResource resultsViewSource}}">
               <GridView.ItemTemplate>
                  <DataTemplate>
                     <TextBlock Text="{Binding FullName}" Margin="0,20,0,0"
                        Foreground="#7CFFFFFF" HorizontalAlignment="Left"  />
                  </DataTemplate>
               </GridView.ItemTemplate>
               <GridView.ItemContainerStyle>
                  <Style TargetType="Control">
                     <Setter Property="Height" Value="70"/>
                     <Setter Property="Margin" Value="0,0,38,8"/>
                  </Style>
               </GridView.ItemContainerStyle>
    </GridView>
  4. Deploy the application and test a search from the Search pane, as you learned in the “Test the Default Search Component” procedure.

The last thing you need to do to complete the sample application is to change the DefaultViewModel property value to display the actual number of people retrieved by the search.

Modify the View Model properties

In this procedure, you will modify the code to show the actual number of people retrieved by the search. The procedure is very straightforward.

  1. Modify the LoadState method as follows. The lines in bold represent the updated ones.

    protected override void LoadState(Object navigationParameter,
        Dictionary<String, Object> pageState)
    {
    var queryText = navigationParameter as String;
    
    // TODO: Application-specific searching logic.  The search process is responsible for
    //       creating a list of user-selectable result categories:
    //
    //       filterList.Add(new Filter("<filter name>", <result count>));
    //
    //       Only the first filter, typically "All", should pass true as a third argument
    //       in order to start in an active state.  Results for the active filter are
    //       provided in Filter_SelectionChanged below.
    
    var biz = new Biz();
    var people = biz.GetPeople(queryText);
    this.DefaultViewModel["Results"] = people;
    
    var filterList = new List<Filter>();
    filterList.Add(new Filter("All", people.Count, true));
    
    // Communicate results through the view model
    this.DefaultViewModel["QueryText"] = '\u201c' + queryText + '\u201d';
    this.DefaultViewModel["Filters"] = filterList;
    this.DefaultViewModel["ShowFilters"] = filterList.Count >= 1;
    }

    In practice, the first filter that shows the “All” keyword will contain the actual number of retrieved results and the ShowFilters boolean property indicates whether to show the various filters to the user. Obviously, you have to implement the various filters and the corresponding code.

  2. Kill the application using the Task Manager because the process is probably already running from the previous procedure.

  3. Deploy the application and test it again using the Search pane.