O'Reilly logo

Getting Started with Windows 8 Apps by Ben Dewey

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Interacting with the Operating System

One of the many reasons to choose a native app over a web app is to have access to the features available exclusively to native apps. This chapter focuses on these features and what it takes for you to implement them into your app.

Just like the previous chapter, I will be using the Bing Image Search application for context when describing these features. As the name states, search is a critical component to the application and it is where I will start. Once the user has performed a search, the app will have images that can be used throughout the operating system in places like tiles, file pickers, and sharing requests. Table 4-1 shows a list of the features that are leveraged by the Bing Image Search app. All of these features will be described in detail in this chapter.

Table 4-1. Windows 8 Features used by the Bing Image Search app

FeaturesDescription

Search

Search is a charm on the Start Bar that enables searching within any application. Search is activated in different ways depending on whether your app is already running or not.

Tiles

Tiles become Live Tiles when you dynamically send updates to them with relevant content for your application. Tiles can be updated with a variety of templates, in the case of the Bing Image Search application, Tiles can be updated with a single image or a collage of images.

Pickers

Pickers are used throughout Windows to provide information to and from applications and the operating system. The Bing Image Search app uses the FileSavePicker to save images and the FileOpenPicker to provide the searched images to other applications.

Share

Apps can act as either sharing sources or targets. The Bing Image Search application will be used as a share source, meaning that it can share images to other applications. This app will not be used as a share target since it has no use for input data from other apps.

Devices

Windows 8 supports a number of devices such as cameras, light sensors, and accelerometers. The Bing Image Search application uses the Accelerometer to determine when the device is shaken so that it can load more results.

Search

In Chapter 2, I used a TextBox and a Button to load results from the Bing Search API. Although this worked, it didn’t fit into the Windows 8 experience. Windows 8 has completely redesigned search on the operating system and has exposed a consistent experience for all applications and files alike. Windows 8 Search can communicate with your app in two ways:

Your app is in the foreground and currently running

Windows 8 fires an event via the SearchPane.QuerySubmitted handler

Your app is not running

Windows 8 launches your app with the expressed intent of searching and launches via the overridden OnSearchActivated method within your application (App) class.

Declaring Your Search Intentions

In order to display the app’s tile in the SearchPane, you need to specify your intentions via the application manifest file (Package.appxmanifest). This manifest file is located in the root folder of your app and Visual Studio will open a custom screen for defining your app when you open it. Navigate to the Declarations tab, Figure 4-1, select Search from the drop-down menu, and click Add. In the case of the Bing Image Search app, I did not need to specify any parameters because the search logic is handled by the default App entry point. In the event that your app needs an alternative entry point for searching, you could specify it here.

Declaring search

Figure 4-1. Declaring search

Handling SearchPane.QuerySubmitted

SearchPane.QuerySubmitted is an event on a statically accessible object triggered by the operating system. This type of object is not testable because you don’t know when or how the operating system will fire the event. More importantly, you are unable to trigger the event from an automated test. In order to make these events more testable, they need to be converted into messages and routed through the messaging system. When the app runs, the message is sent directly to the handler that performs the actual search. To test the handler, you can easily create a fake message and execute the handler with that message. The mechanism for converting the operating system events into messages that can be handled is the SearchPaneMessagePump (Example 4-1). In addition, this pump can be started when the application launches and can respond to search requests from the operating system regardless of the current page.

Example 4-1. MessagePump adapter to route Windows 8 Searches to the app (SearchPaneMessagePump.cs)

public class SearchPaneMessagePump : IMessagePump
{
    private readonly IHub _messageHub;
    private SearchPane _searchPane;

    public SearchPaneMessagePump(IHub messageHub)
    {
        _messageHub = messageHub;
    }

    public void Start()
    {
        _searchPane = SearchPane.GetForCurrentView();
        _searchPane.QuerySubmitted += OnQuerySubmitted;
    }

    public void Stop()
    {
        if (_searchPane != null)
        {
            _searchPane.QuerySubmitted -= OnQuerySubmitted;
        }
    }

    private async void OnQuerySubmitted(SearchPane sender,
                                SearchPaneQuerySubmittedEventArgs args)
    {
        await _messageHub.Send(new SearchQuerySubmittedMessage(args.QueryText));
    }
}

When started, the SearchPaneMessagePump listens for any QuerySubmitted events on the current SearchPane. When the event occurs a new message is created and sent to the message hub for routing. The logic here is purposefully simple. When creating testable code, it’s imperative that you isolate the operating system from the application; this layer, similar to the adapter pattern mentioned previously, allows the remainder of the code to adapt independently from the operating system. Once a message is sent to the MessageHub, it’s routed to its respective handler. Handlers are where all the work happens.

Example 4-2. Handler code for responding to a Search query from Windows 8 (SearchQuerySubmittedHandler.cs)

    public class SearchQuerySubmittedHandler : IAsyncHandler<SearchQuerySubmittedMessage>
    {
        private readonly ApplicationSettings _settings;
        private readonly IImageSearchService _imageSearchService;
        private readonly INavigationService _navigationService;
        private readonly IStatusService _statusService;

        public SearchQuerySubmittedHandler(ApplicationSettings settings, IImageSearchService
          imageSearchService, INavigationService navigationService, 
          IStatusService statusService)
        {
            _settings = settings;
            _imageSearchService = imageSearchService;
            _navigationService = navigationService;
            _statusService = statusService;
        }

        public async Task HandleAsync(SearchQuerySubmittedMessage message)
        {
            if (!NetworkInterface.GetIsNetworkAvailable())
            {
                _statusService.SetNetworkUnavailable();
                return;
            }

            _statusService.Message = "Loading Images for " + message.Query;
            _statusService.IsLoading = true;

            try
            {
                // Remove any existing searches for this query
                var searches = _settings.Searches;
                var existing = searches.FirstOrDefault(s =>
                  s.Query.Equals(message.Query, StringComparison.CurrentCultureIgnoreCase));
                if (existing != null)
                {
                    searches.Remove(existing);
                }

                // Search Bing
                var images = await _imageSearchService.Search(message.Query,
                                    _settings.Rating, _settings.ImageResultSize);
                if (!images.Any())
                {
                    _statusService.SetBingUnavailable();
                    return;
                }

                // Store results in app settings
                var instance = new SearchInstance()
                                   {
                                       Images = images,
                                       SearchedOn = DateTime.Today,
                                       Query = message.Query
                                   };
                searches.Insert(0, instance);
                _settings.Searches = searches;
                _settings.SelectedInstance = instance;
                await _settings.SaveAsync();

                // Navigate
                _navigationService.Navigate(typeof(SearchResultsPage));
            }
            catch (InvalidOperationException ex)
            {
                var baseEx = ex.GetBaseException();
                if (baseEx is WebException)
                {
                    _statusService.SetBingUnavailable();
                    return;
                }
                throw;
            }
            finally
            {
                _statusService.IsLoading = false;
            }
        }
    }

The SearchQuerySubmittedHandler (Example 4-2) performs the following steps:

  1. Checks for a valid Internet connection

  2. Notifies the application of its status

  3. Executes the search against the Bing Search API

  4. Stores the results in the settings for retrieval by the ViewModel

  5. Navigates to the SearchResultsPage

In the event that a user has already searched for an item, it will be removed from the history before proceeding to avoid duplicates. All the logic in the handler is specific to the application and isolated here from any outside influence. In addition, you’ll notice that none of the code in this handler is specific to Windows 8. The primary input is a Message, which just contains a string containing the search query.

Note

Using the message hub for searching helps provide more testable code. Unfortunately the topic is far too broad and subjective to discuss here in detail. If you’re interested in my approach to testing the code, you can see the tests for the SearchQuerySubmittedHandler by viewing them online at http://github.com/bendewey/GettingStartedWithMetroApps/blob/master/BingImageSearch/BingImageSearch.Tests/Message/Handlers/SearchQuerySubmittedHandlerTests.cs.

One of the goals from Chapter 3 was to ensure an optimal experience when the users are offline. In order to interact with the app, users would first need to perform a search. This means the SearchQuerySubmittedHandler is an ideal place to verify Internet connectivity before executing the web service call. In addition, I am trapping WebException here and allowing the _statusService to provide a friendly message to the user stating that the application is offline. Figure 4-2 and Figure 4-3 show the messages that are displayed to the user when the app encounters an error performing a search.

Network Unavailable message displayed to the user

Figure 4-2. Network Unavailable message displayed to the user

Bing Search API Unavailable message displayed to the user

Figure 4-3. Bing Search API Unavailable message displayed to the user

In the original version of this class, I tried to optimize the code by using an async void method to save the settings in parallel with navigation. This turned out to have numerous issues with regards to my unit tests, but more importantly it caused issues with the handling of exceptions (see the note below for the reasons to avoid the async void method).

Note

An async method has no return type and simply calls an await on some other code that can use async void RunSomethingAsync() and async Task RunSomethingAsync() interchangeably. Returning a Task allows other code to await on your method, making it appear synchronous, where returning void signifies a fire-and-forget asynchronous block. It’s also important to note that using async void is an anti-pattern because exceptions thrown from within an async void don’t currently bubble up to the application unhandled exception handler and the thread is aborted with no notice. I have a post on the Windows Developer forum that references this issue, and hopefully it will be resolved in a future version (http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/bea154b0-08b0-4fdc-be31-058d9f5d1c4e).

Launching Your App Directly into Search Mode

The second way to interact with the Windows 8 SearchPane is through the OnSearchActivated override method on your application class. This method provides the same information as the SearchPane.QuerySubmitted, but it has a different argument called SearchActivatedEventArgs. The testability concerns and logic are almost identical to the static event, and luckily the approach from the previous section can be completely reused by simply sending a message to the exact same message hub. Example 4-3 shows the App.xaml.cs code needed to send the message.

Example 4-3. OnSearchActivated override (App.xaml.cs)

partial class App
{
    public static ViewModelLocator ViewModelLocator
    {
        get { return (ViewModelLocator)Current.Resources["ViewModelLocator"]; }
    }

    protected override async void OnSearchActivated(SearchActivatedEventArgs args)
        {
                await EnsureShell(args.PreviousExecutionState);
                await ViewModelLocator.Hub.Send(new SearchQuerySubmittedMessage(args.QueryText));
        }

    private async Task EnsureShell(ApplicationExecutionState previousState)
    {
        // setup Container, Shell, and set Window.Current.Content
    }
}

There isn’t much to expand upon here except for the fact that when the application launches, it loads the UI and sends the same message as the SearchPaneMessagePump. Similarly, this message gets routed to the SearchQuerySubmittedHandler, which navigates to the SearchResultsPage, except this time it happens immediately upon launching the app.

In addition to these two entry points, applications can provide autocomplete information to the Windows 8 SearchPane. This can help users find information relevant to your content. I can envision an email application that will provide terms present in recent emails as autocomplete hints. This information is also available via events on the SearchPane and would fit nicely into the existing messaging implementation.

Tiles

Tiles are the first view your user will get of your app. From the moment she sees it in the Windows Store to the first time she launches the app, the Tile is your place to make a first impression. Furthermore, once you’ve engaged your user, it’s important to bring her back; Live Tiles offer a way to update users with current information without having to launch the app again.

Tiles come in two sizes: square and wide. Each size has its own set of templates that can be updated independently or as a single update. Templates come in a number of different formats, from simple text updates to multiple images with text. A full list of templates is available online at http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.notifications.tiletemplatetype.aspx. The Bing Image Search application will utilize three templates listed in Table 4-2.

Table 4-2. TileTemplatesTypes used by the Bing Image Search app

TemplateSample

TileSquarePeekImageAndText04

image with no caption

TileWidePeekImage03

image with no caption

TileWidePeekImageCollection06

image with no caption

Updating the Tile with a Collection of Images

After performing a search, the user will be navigated to the SearchResults page. This page shows a list of images based on the search. From this list, you can provide your first tile update. The template you choose is entirely up to you; I chose to take a random sampling of six images and update the tile with the TileWidePeekImageCollection06 template. You’ll also need to determine whether you want to update the tile automatically or via some user interaction, for example, when the user clicks on an AppBar button. I chose to update the user’s tile automatically when he performs a search. When the SearchResultsPage loads, the IoC container creates a new instance of the SearchResultsPageViewModel. In the constructor of this view model, and Example 4-4, a message is sent to the message hub to update the tile.

Example 4-4. SearchResultsPageViewModel constructor (SearchResultsPageViewModel.cs)

public SearchResultsPageViewModel(ApplicationSettings settings, IHub hub
                                        /* other dependencies */ )
{
    _settings = settings;
    _hub = hub;

    // setup other depedencies and commands

    _hub.Send(new UpdateTileImageCollectionMessage(_settings.SelectedInstance));
}

Sending this message activates the handler, shown in Example 4-5, which is made up of three steps. The first step retrieves the template for the tile. The second updates the content’s template with the URLs for the data you would like to update. Finally, the item is sent to the TileUpdateManagerAdapter to update the primary live tile. The TileUpdateManagerAdapter is an adapter over the static TileUpdateManager that is available in the Windows. Example 4-6 shows the TileUpdateManagerAdapter.

Note

The code in Example 4-5 uses NotificationExtensions, a project that was provided by Microsoft as part of their App tile and badges sample, which can be found at http://code.msdn.microsoft.com/windowsapps/App-tiles-and-badges-sample-5fc49148. The default API for updating tiles uses a TileUpdateManager.GetTemplate(type) to return an XmlDocument for the template, which can be updated manually.

Example 4-5. Handler code for setting a collection of images to your app tile (UpdateTileImageCollectionHandler.cs)

public class UpdateTileImageCollectionHandler : IHandler<UpdateTileImageCollectionMessage>
{
    private readonly ITileUpdateManager _tileUpdateManager;

    public UpdateTileImageCollectionHandler(ITileUpdateManager tileUpdateManager)
    {
        _tileUpdateManager = tileUpdateManager;
    }

    public void Handle(UpdateTileImageCollectionMessage message)
    {
        var content = TileContentFactory.CreateTileWidePeekImageCollection06();

        content.RequireSquareContent = false;
        content.TextHeadingWrap.Text = "Search for " + message.Instance.Query;

        var images = message.Instance.GetRandomImages(6).ToList();
        UpdateImage(content.ImageMain, images[0]);
        UpdateImage(content.ImageSecondary, images[1]);
        UpdateImage(content.ImageSmallColumn1Row1, images[2]);
        UpdateImage(content.ImageSmallColumn1Row2, images[3]);
        UpdateImage(content.ImageSmallColumn2Row1, images[4]);
        UpdateImage(content.ImageSmallColumn2Row2, images[5]);

        _tileUpdateManager.UpdatePrimaryTile(tile);
    }

    private void UpdateImage(INotificationContentImage imageContent, ImageDetail image)
    {
        imageContent.Src = image.Thumbnail.Url;
        imageContent.Alt = image.Title;
    }
}

Example 4-6. Adapter code to send an update to the Windows8 TileUpdateManager (TileUpdateManagerAdapter.cs)

public class TileUpdateManagerAdapter : ITileUpdateManager
{
    public void UpdatePrimaryTile(ITileNotificationContent content)
    {
        var notification = content.CreateNotification();
        TileUpdateManager.CreateTileUpdaterForApplication().Update(notification);
    }
}

Note

The Bing Image Search application provides updates to the tile via publicly accessible (HTTP) URIs. Tiles can also be updated with local content using the ms-appx://<local-path> syntax.

Updating Multiple Tiles with a Single Command

In the previous example of the image collection, I chose not to provide square tile content by assigning the RequireSquareContent to false. The main reason for this is there aren’t any square tiles that allow for a collection of images. This means the default square tile will continue to be used. What I’d like to do is allow the user to update the square tile on her own via an AppBar button. When the user selects an image, she is navigated to the DetailsPage. This page allows users to do a task specific to the image, such as save, share, update tile, etc. (See Figure 4-4). On the AppBar of the DetailsPage (Example 4-7), there is a button to set the tile. This button links to a command, which can be found on the view model, as seen in Example 4-8.

Image Search Options

Figure 4-4. Image Search Options

Example 4-7. Set Tile AppBar Button (DetailsPage.xaml)

<Button Command="{Binding SetTileCommand}"
        Style="{StaticResource SetTileAppBarButtonStyle}" />

Example 4-8. SetTile Command (DetailsPageViewModel.cs)

public class DetailsPageViewModel
{
    // constructor and dependencies omitted
    public ICommand SetTileCommand { get; set; }

    public void SetTile()
    {
        _messageHub.Send(new UpdateTileMessage(_settings.SelectedImage));
    }
}

As you’ve seen in previous examples, the update tile command is handled by a dedicated message handler in response to the message from the ViewModel. This keeps the ViewModel’s responsibilities constrained to just communicating between the View. Once the message is sent to the message hub, the hub executes the handler with the message. Example 4-9 shows the UpdateTileHandler, which is responsible for performing the actual tile update against the operating system.

Example 4-9. Message handler for updating the title to a single image (Update TileHandler.cs)

public class UpdateTileHandler : IHandler<UpdateTileMessage>
{
    private readonly ITileUpdateManager _tileUpdateManager;

    public UpdateTileHandler(ITileUpdateManager tileUpdateManager)
    {
        _tileUpdateManager = tileUpdateManager;
    }

    public void Handle(UpdateTileMessage message)
    {
        var url = message.Image.MediaUrl;
        if (message.Image.Width > 800 || message.Image.Height > 800)
        {
            // Images > 800px cannot be used as tiles
            url = message.Image.Thumbnail.Url;
        }

        var content = TileContentFactory.CreateTileWidePeekImageAndText01();
        content.TextBodyWrap.Text = message.Image.Title;
        content.Image.Src = url;
        content.Image.Alt = message.Image.Title;

        // Square image substitute
        var squareContent = TileContentFactory.CreateTileSquareImage();
        squareContent.Image.Src = url;
        squareContent.Image.Alt = message.Image.Title;
        content.SquareContent = squareContent;

        _tileUpdateManager.UpdatePrimaryTile(content);
    }
}

With the exception of a change in the template, the logic in the single update Tile handler is very similar to the handler for updating a collection of images. I’m retrieving the template, setting its values, and updating the Tile through the TileUpdateManagerAdapter. Depending on the size of your images you may have to perform some resizing. Live tiles do not work with images larger than 800 pixels tall or wide. In the case of the Bing Image Search app, this was easy to resolve by verifying the metadata of the image and downgrading it to the thumbnail view in those scenarios.

It’s also important to note that in Example 4-9 I am specifying an alternative SquareContent tile. The content is identical to the wide content and allows the user to change the tile size on the start screen and still receive the tile updates.

Tiles are a great way to make your app stand out. In addition to updating your Live Tile when the app is running, you can also specify a timer or push notification-based background task to update your tiles as well. Background Tasks are declared in the application manifest similar to how search was declared in the previous section. This is a handy way to provide updates to your app’s Tile in the event that your app hasn’t been launched recently. In addition to providing these images to the Tile, these images can also be provided to other apps directly via pickers.

Pickers

Pickers are used to provide information to and from your app. For example, your app can provide information to the FileOpenPicker, in which case other apps can use your content. Conversely, your app can be the one consuming the content from a FileOpenPicker, which in turn allows your app to receive content from any installed app on the user’s system that is providing information. Typically speaking handling a picker is far more difficult than consuming one. The Bing Image Search app will leverage two of the pickers.

FileOpenPicker

The Bing Image Search app will support the FileOpenPicker for any app that requests image files. This will utilize a custom page that will provide a TextBox and a Button for searching.

FileSavePicker

The Bing Image Search app will utilize the FileSavePicker from the DetailsPage to save an image. This can be used to save a picture to the local filesystem or to save it to another app, like the SkyDrive app, which allows users to save files to the cloud directly through this mechanism.

FileOpenPicker

Declaring the FileOpenPicker in the manifest

Before you can support the FileOpenPicker, you must specify your intentions via the application manifest. This will make your application icon appear in the list of available apps when a user launches a FileOpenPicker. To specify your declaration, open the Package.appxmanifest on the root of your project, and select the Declarations tab. Select File Open Picker from the drop-down list and click Add. In the case of the Bing Image Search app, the FileOpenPicker is limited to supplying only image files (see Figure 4-5), but depending on your needs you can specify other types or choose to support any file type by checking the Supports Any File Type checkbox.

Declaring File Open Picker

Figure 4-5. Declaring File Open Picker

Launching the FileOpenPicker

Once you’ve declared your app, you are ready to hook up the entry point. The Bing Image Search application doesn’t supply any explicit entry point for the FileOpenPicker. In that case, the app will launch the App class as its entry point. In the App class, there is an override void OnFileOpenPickerActivated (Example 4-10), which will execute. The Bing Image Search app will create a custom FilePickerPage, rather than the typical Shell page, and set the current window’s content to that page. In order to handle adding and remove items to/from the picker, you need to use the supplied FileOpenPickerActivatedEventArgs, which provides access to the FileOpenPickerUI. This is the only time you can access this object, so you’ll need to hang on to it. I’ve created a custom wrapper that is registered in the container and is initialized with the current FileOpenPickerUI. This wrapper will also serve as an adapter to allow testability of the FilePickerPageViewModel. Example 4-11 and Example 4-12 show the interface and wrapper used for adding and removing items to/from the picker.

Example 4-10. OnFileOpenPickerActivated override (App.xaml.cs)

protected override void OnFileOpenPickerActivated(FileOpenPickerActivatedEventArgs args)
{
        ViewModelLocator.FileOpenPickerUiManager.Initialize(args.FileOpenPickerUI);
        Window.Current.Content = new FilePickerPage();
        Window.Current.Activate();
}

Example 4-11. IFileOpenPickerUiManager interface (IFileOpenPickerUiManager.cs)

public interface IFileOpenPickerUiManager
{
    void Initialize(FileOpenPickerUI fileOpenPicker);

    FileSelectionMode SelectionMode { get; }
    IReadOnlyList<string> AllowedFileTypes { get; }
    AddFileResult AddFile(string id, IStorageFile storageFile);
    void RemoveFile(string id);
}

Example 4-12. FileOpenPickerUiManager implementation (FileOpenPickerUiManager.cs)

public class FileOpenPickerUiManager : IFileOpenPickerUiManager
{
    private FileOpenPickerUI _fileOpenPicker;

    public void Initialize(FileOpenPickerUI fileOpenPicker)
    {
        _fileOpenPicker = fileOpenPicker;
    }

    public FileSelectionMode SelectionMode
    {
        get { return _fileOpenPicker.SelectionMode; }
    }

    public IReadOnlyList<string> AllowedFileTypes
    {
        get { return _fileOpenPicker.AllowedFileTypes; }
    }

    public AddFileResult AddFile(string id, IStorageFile file)
    {
        return _fileOpenPicker.AddFile(id, file);
    }

    public void RemoveFile(string id)
    {
        _fileOpenPicker.RemoveFile(id);
    }
}

The FilePickerPage is similar to the Bing Simple Search app from Chapter 2. This page contains a TextBox, a Button, and a GridView (Example 4-13). Unlike the first example, this page needs to handle adding and removing files from the picker upon user selection. In addition, the page also needs to support multiple selections. Because this is difficult to accomplish with data binding, I resorted to using the code behind and routing custom events to the ViewModel based on the user selection. Example 4-14 shows the code-behind necessary to facilitate the ItemGridView_SelectionChanged event handler.

Example 4-13. FilePickerPage XAML (FilePickerPage.xaml)

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="87" />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid VerticalAlignment="Center" Margin="120,0,0,0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <TextBox Text="{Binding SearchQuery, Mode=TwoWay}" />
        <Button Content="Search" Command="{Binding SearchCommand}"
                                Margin="12,0,60,0" Grid.Column="1" />
    </Grid>
    <Grid x:Name="ItemPickerContentPanel" Grid.Row="1" Margin="120,0,0,34">
        <GridView x:Name="ItemGridView"
            ItemsSource="{Binding Source={StaticResource CollectionViewSource}}"
            ItemTemplate="{StaticResource ThumbnailItemTemplate}"
            ItemContainerStyle="{StaticResource GridTileStyle}"
            BorderThickness="0" VerticalAlignment="Stretch"
            Grid.Row="1"
            SelectionMode="Multiple"
            SelectionChanged="ItemGridView_SelectionChanged" />
    </Grid>
</Grid>

Example 4-14. FileOpenPickerPage code behind showing the custom routing needed in lieu of data binding commands (FileOpenPickerPage.xaml.cs)

public sealed partial class FileOpenPickerPage
{
    protected FileOpenPickerPageViewModel ViewModel
    {
        get { return DataContext as FileOpenPickerPageViewModel; }
    }

    private void ItemGridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var vm = ViewModel;
        if (vm == null) return;

        foreach(var image in e.AddedItems)
        {
            vm.AddImage(image);
        }
        foreach(var image in e.RemovedItems)
        {
            vm.RemoveImage(image);
        }
    }
}

Handling the FileOpenPickerUI

Now that the XAML and page are set up to communicate with the ViewModel, the actual files can be sent to the picker. In addition to searching, the FileOpenPickerPageViewModel (Example 4-15) handles adding and removing items to and from the FileOpenPickerUiManager shown earlier. This object is injected by the container into the constructor of the ViewModel, and when an item is added by the page, the ViewModel downloads the item and adds it to the manager.

Example 4-15. ViewModel for the FileOpenPicker page (FileOpenPickerPageViewModel.cs)

public class FileOpenPickerPageViewModel : BindableBase
{
    private readonly ApplicationSettings _settings;
    private readonly IFileOpenPickerUiManager _fileOpenPicker;

    public FileOpenPickerPageViewModel(ApplicationSettings settings,
      IFileOpenPickerUiManager fileOpenPicker /* other dependenceies */)
    {
        _settings = settings;
        _fileOpenPicker = fileOpenPicker;
        // ViewModel setup code omitted
    }

    // Search code omitted for clarity

    public async void AddImage(object item)
    {
        var image = item as ImageResult;
        if (image == null) return;

        if (_fileOpenPicker.AllowedFileTypes.Any(ext =>
                        ext == "*" || image.MediaUrl.EndsWith(ext)))
        {
            var file = await _settings.GetTempFileAsync(image.MediaUrl);
            var result = _fileOpenPicker.AddFile(image.MediaUrl, file);
        }
    }

    public void RemoveImage(object item)
    {
        var image = item as ImageResult;
        if (image == null) return;

        _fileOpenPicker.RemoveFile(image.MediaUrl);
    }
}

It’s important to note that not all requests can handle the supported file types. Your app is responsible for handling these cases and providing only files that are contained within the AllowedFileTypes list. In the event that you supply an invalid file, you can check the AddFileResult object that is returned from the call to AddFile.

The downloading of the file is an asynchronous method handled by the ApplicationSettings class and shown in Example 4-16. This method uses an extremely useful API, the BackgroundDownloader, this object is used as the primary method for downloading static content. The BackgroundDownloader is extremely easy to use, and with only a few lines of code I am able to download a file and provide it to anything that needs a StorageFile. The actual code for the BackgroundDownloader is accessed via the BackgroundDownloadAdapter, which can be found in Example 4-17.

Example 4-16. ApplicationSettings GetTempFileAsync method to download files for the file picker (ApplicationSettings.cs)

public async Task<StorageFile> GetTempFileAsync(string uri)
{
    return await CreateAndDownloadFile(uri);
}

private async Task<StorageFile> CreateAndDownloadFile(string uri, string filename = null)
{
    filename = filename ?? Regex.Replace(uri, "https?://|[/?&#]", "");
    StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename,
      CreationCollisionOption.ReplaceExisting);
    await _backgroundDownloader.StartDownloadAsync(new Uri(uri), file);
    return file;
}

Example 4-17. BackgroundDownloader Adapter (BackgroundDownloaderAdapter.cs)

public class BackgroundDownloaderAdapter : IBackgroundDownloader
{
        public IAsyncOperationWithProgress<DownloadOperation, DownloadOperation>
                                StartDownloadAsync(Uri uri, IStorageFile storageFile)
        {
                return new BackgroundDownloader().CreateDownload(uri, storageFile)
                                .StartAsync();
        }
}

Now that the page is successfully adding and removing content to the picker its job is done. Testing the FileOpenPicker can be tricky, though. In Visual Studio, if you run you app, you will launch the app using the standard OnLaunching entry point. To trigger the OnFileOpenPickerActivated entry point, you need to access your app from the FileOpenPicker. Microsoft provides a sample app, which can aid in testing, but I’ve found that the mail application is a great tool for testing the FileOpenPicker (http://code.msdn.microsoft.com/windowsapps/File-picker-app-extension-0cb95155). Just launch the mail app, create a new message, and click Add Attachment. This will launch the FileOpenPicker.

FileSavePicker

The Bing Image Search application uses the FileSavePicker to save an image. Consuming the pickers is far easier than supporting them. All that you need to do is open the dialog with your necessary parameters and handle the response. The beauty is that your app doesn’t really care where the StorageFile is coming from, just that you can write some data to it. In addition, you don’t have to create any declarations, to use a picker you just create one, tell it to pick files, and leverage the results in any app. Example 4-18 shows XAML and Example 4-19 shows the code in the DetailsPageViewModel that sends the SaveImageMessage.

Example 4-18. Save Image AppBar (DetailsPage.xaml)

<Button x:Name="SaveCommand"Command="{Binding SaveCommand}"
        Style="{StaticResource SaveAppBarButtonStyle}" />

Example 4-19. Save Command (DetailsPageViewModel.cs)

public class DetailsPageViewModel
{
    // constructor and dependencies omitted
    public ICommand SaveCommand { get; set; }

    public async Task Save()
    {
        await_messageHub.Send(new SaveImageMessage(_settings.SelectedImage));
    }
}

In line with other messages in the system, the SaveImageMessage is handled by the corresponding SaveImageHandler (Example 4-20). The SaveImageHandler is responsible for opening the FileSavePicker. However, before it can do this, it needs to create a new FileSavePicker object, define the file type filters, and provide a suggested name. In the case of the Bing Image Search app, I am using the actual filename from the URL as the suggested filename. Once the picker is defined, you can call PickSaveFileAsync, which will actually prompt the user with a new Windows 8 file save dialog (Figure 4-6). The result of this call will be a StorageFile for the selected file, or null if the user clicks Cancel. With that selected file, I can kick off a download from a remote location, which will automatically save directly to the selected file. When the process is complete, I update the _statusService for the application.

File Save Picker

Figure 4-6. File Save Picker

Example 4-20. SaveImageHandler

public class SaveImageHandler : IAsyncHandler<SaveImageMessage>
{
    private readonly IPickerFactory _pickerFactory;
    private readonly IBackgroundDownloader _backgroundDownloader;
    private readonly IStatusService _statusService;

    public SaveImageHandler(IPickerFactory pickerFactory,
        IBackgroundDownloader backgroundDownloader,
        IStatusService statusService)
    {
        _pickerFactory = pickerFactory;
        _backgroundDownloader = backgroundDownloader;
        _statusService = statusService;
    }

    public async Task HandleAsync(SaveImageMessage message)
    {
        // Set up and launch the Open Picker
        var filename = GetFilenameFromUrl(message.Image.MediaUrl);
        var extension = System.IO.Path.GetExtension(filename);
        var picker = _pickerFactory.CreateFileSavePicker();
        picker.SuggestedFileName = filename;
        picker.FileTypeChoices.Add(extension.Trim('.').ToUpper(),
                        new string[] { extension });

        var saveFile = await picker.PickSaveFileAsync();
        if (saveFile != null)
        {
            await _backgroundDownloader.StartDownloadAsync(
                                new Uri(message.Image.MediaUrl), saveFile);

            _statusService.TemporaryMessage =
              string.Format("Image {0} saved.", saveFile.Name);
        }
    }

    private string GetFilenameFromUrl(string url)
    {
        var uri = new System.Uri(url);
        return uri.Segments[uri.Segments.Length - 1];
    }
}

In the SaveImageHandler, you may have noticed that I didn’t create a new FileSavePicker in the handler directly. This is not testable for a number of reasons. First off, the FileSavePicker launches a new dialog and requires user interaction. This workflow cannot be automated. In addition, just creating a new FileSavePickerAdapter in the Handle method wouldn’t allow for providing alternative implementations. In order to solve these concerns, I create a PickerFactory object, which returns a new FileSavePickerAdapter for every request in production. This factory can then be customized by a test to return whatever is needed. The PickerFactory and FileSavePickerAdapter can be seen in Example 4-21 and Example 4-22, respectively. The FileSavePickerAdapter is a bit more in depth than other adapters because it contains properties that need to be mapped as well. It purposefully has no external business logic because this class cannot be tested.

Example 4-21. PickerFactory

public class PickerFactory : IPickerFactory
{
        public IFileSavePickerAdapter CreateFileSavePicker()
        {
                return new FileSavePickerAdapter();
        }
}

Example 4-22. FileSavePickerAdapter

public class FileSavePickerAdapter : IFileSavePickerAdapter
{
        public FileSavePickerAdapter()
        {
                CommitButtonText = String.Empty;
                FileTypeChoices = new Dictionary<string, IList<string>>();
                SuggestedFileName = String.Empty;
                // The system default is DocumentsLibrary, but this app is all about images
                SuggestedStartLocation = PickerLocationId.PicturesLibrary;
        }

        public string CommitButtonText { get; set; }
        public string DefaultFileExtension { get; set; }
        public IDictionary<string, IList<string>> FileTypeChoices { get; set; }
        public string SuggestedFileName { get; set; }
        public PickerLocationId SuggestedStartLocation { get; set; }

        public async Task<IStorageFile> PickSaveFileAsync()
        {
                var picker = new FileSavePicker();
                picker.CommitButtonText = CommitButtonText;
                picker.SuggestedFileName = SuggestedFileName;
                picker.SuggestedStartLocation = SuggestedStartLocation;
                if (DefaultFileExtension != null)
                {
                        picker.DefaultFileExtension = DefaultFileExtension;
                }

                foreach(var choice in FileTypeChoices.Keys)
                {
                        picker.FileTypeChoices.Add(choice, FileTypeChoices[choice]);
                }

                return await picker.PickSaveFileAsync();
        }
}

Pickers are a compelling reason to develop a native app over a web app. Communicating with a user’s filesystem is not likely to be included in modern web browsers. Pickers also allow apps to communicate with other applications on the user’s machine regardless of the intent and without any knowledge of each other. This is not something that is typically done in Windows, or something that is easily accomplished in any other operating system for that matter.

Sharing

If you have ever written code that communicated with the Windows Clipboard, then sharing should be very familiar. When the user activates the Share charm, an event fires where you can assign text, URIs, data, images, or raw data. On the other end, apps can set up a declaration in their app manifest, which will show the Tile on the share page when the specified type is shared by another application. That target app can then use the content however it sees fit, for example, to send an email.

The Bing Image Search app will act as a sharing source and will provide links and images, also known as bitmap data to other applications. Figure 4-7 shows a screenshot of the sharing pane in action. If the user activates sharing when he is on the search results page, then the app will provide a link to Bing’s image search site for that query. If he selects an image and navigates to the details page, then the app will provide the bitmap data for the selected image.

Bing Image Search with Sharing

Figure 4-7. Bing Image Search with Sharing

ShareDataRequestedMessagePump

When the user opens the Sharing pane, the DataTransferManager.DataRequested event fires. This is very similar to Search from earlier in this chapter, so I followed the same pattern. The event is handled by an application-level message pump. The message pump’s job is to convert system events into application messages and route them through the message hub. Unlike the message pump from Search, which acted on information from the event, sharing requires some information be provided to the event. Example 4-23 shows a simplified comparison. Notice how only the DataRequested—Sharing—event requires some content, in this case a string called "MyAppContent".

Example 4-23. Windows 8 Event Differences

// event registration code omitted

private void OnQuerySubmitted(SearchPane sender,
                                        SearchPaneQuerySubmittedEventArgs args)
{
        // use args.QueryText to search
}

private void OnDataRequested(DataRequestManager sender,
                                        DataRequestManagerArgs args)
{
        // use args.Request.SetText("MyAppContent") or equivalent.
}

Since the app will be sending different types of content, the event handler will send out one of two messages:

ShareUriMessage

Sharing from the search results page will provide a Uri. The handler will just supply the Uri directly.

ShareImageDetailsMessage

Sharing from the details page will provide an object of type ImageResult. The handler will download the image and provide the data from there.

These messages are sent based on the ShareDataRequestedMessagePump.DataToShare property, which any page can supply. The current data to share will always be set to the last item specified. Example 4-24 shows how the ShareDataRequestedMessagePump creates a message based on the DataToShare. Another thing to note, is that unlike the search message pump, the share message pump registers its event on an adapter. The reason for this extra level of abstraction is because this message pump isn’t as simple as the one for search, so there are areas that I want to test. In addition to test the handlers, we need to have access to create mocks of the DataRequestedEventArgs so that we can ensure that the handlers are working. To do this, the DataTransferManagerAdapter implements the IDataTransferManager interface and wraps the event args in a new SettableDataRequestedEventArgs, which is supplied to the message. Now the handler test can create a test message with a fake event args.

Example 4-24. ShareDataRequestedPump(ShareDataRequestedPump.cs)

public class ShareDataRequestedPump : IShareDataRequestedPump
{
    private readonly IDataTransferManager _dataTransferManager;
    private readonly IHub _hub;

    public ShareDataRequestedPump(IDataTransferManager dataTransferManager, IHub hub)
    {
        _dataTransferManager = dataTransferManager;
        _hub = hub;
    }

    public void Start()
    {
        _dataTransferManager.DataRequested += OnDataRequested;
    }

    public void Stop()
    {
        _dataTransferManager.DataRequested -= OnDataRequested;
    }

    public object DataToShare { get; set; }

    void OnDataRequested(DataTransferManager sender, SettableDataRequestedEventArgs args)
    {
        if (DataToShare == null) return;

        if (DataToShare is Uri)
        {
            var message = new ShareUriMessage((Uri)DataToShare, sender, args);
            _hub.Send(message);
            return;
        }

        if (DataToShare is ImageResult)
        {
            var message = new ShareImageResultsMessage((ImageResult)DataToShare,
                sender, args);
            _hub.Send(message);
            return;
        }
    }
}

ShareUriHandler

Each of these messages has a dedicated handler. The ShareUriHandler is the easiest, so I will start with that. The ShareUriHandler handles the ShareUriMessage. This message contains the data to share—in this case, the URI to share—and the event args, which I will use to set the URI on. Example 4-25 shows the ShareUriHandler and the code needed to respond to the message. This code is fairly straightforward. When the message is received, the handler sets the Title and the URI for the specified data and its job is done.

Example 4-25. ShareUriHandler

public class ShareUriHandler : IHandler<ShareUriMessage>
{
    public void Handle(ShareUriMessage message)
    {
        var request = message.DataRequestedEventArgs.Request;

        request.Data.Properties.Title = "Bing Image Search Link";
        request.Data.SetUri(message.Link);
    }
}

ShareImageResultsHandler

When the Bing Image Search app shares images, more specifically images from the Web, these images need to be downloaded before they can be shared. This becomes slightly complicated because downloading the image data to provide a stream is an asynchronous process.

The new async and await keywords make the code very simple from a readability standpoint, but the code still executes the way it always has. The reason this is important is because DataRequestManager is expecting the user to set some shareable content before the completion of the event. Example 4-26 shows a very rough translation of what I’m trying to say. The OnDataRequestedWithoutAsync handler sets the data on the event in the second phase of the operation, which is inside a lambda expression. The event handler has already returned control to the caller without anything being set. The way to get around this is to use a new feature in the Windows Runtime called a Deferral. OnDataRequestedWithDeferral. Example 4-26 shows an example of a Deferral.

Example 4-26. Example of why deferrals are needed with async code

// BAD: This code will not work because of the await
private async void OnDataRequested(DataRequestManager sender,
                                        DataRequestManagerArgs args)
{
        // code before await
        var file = await DownloadFile();
        // code after await
        args.Request.SetBitmap(file);
        // event handler returned to caller
}

// BAD: This code will not work because event handler returns prematurely
private void OnDataRequestedWithoutAsync(DataRequestManager sender,
                                        DataRequestManagerArgs args)
{
        // code before await
        DownloadFile().ContinueWith(file =>
        {
                // code after await
                args.Request.SetBitmap(file);
        });
        // event handler returned to caller
}

private void OnDataRequestedWithDeferral(DataRequestManager sender,
                                        DataRequestManagerArgs args)
{
        var deferral = args.Request.GetDeferral();
        try
        {
                // code before await
                var file = await DownloadFile();
                // code after await
                args.Request.SetBitmap(file);
        }
        finally
        {
                deferral.Complete();
        }
        // event handler returned to caller
}

private async Task<IStorageFile> DownloadFile() {}

Example 4-27 shows the full ShareImageDetailsHandler. This handler sets the title and the description of the data being shared. It also defines a callback to lazily load the share image when the caller needs it. This will potentially save the user from downloading the image if he prematurely exits out of the Sharing pane. Inside the callback, the handler downloads the image and applies it using the deferral pattern.

Example 4-27. Handler code for sharing and image (ShareImageResultsHandler.cs)

public class ShareImageResultsHandler : IHandler<ShareImageResultsMessage>
{
    private readonly ApplicationSettings _settings;

    public ShareImageResultsHandler(ApplicationSettings settings)
    {
        _settings = settings;
    }

    public void Handle(ShareImageResultsMessage message)
    {
        var image = message.Image;
        if (image.MediaUrl != null)
        {
            var request = message.DataRequestedEventArgs.Request;
            request.Data.Properties.Title = "Bing Search Image";
            request.Data.Properties.Description =
                string.Format("Sharing {0} originally from {1}", image.Title, image.MediaUrl);
            request.Data.SetDataProvider(StandardDataFormats.Bitmap, async dpr =>
            {
                var deferral = dpr.GetDeferral();

                var shareFile = await _settings.GetShareFileAsync(image.MediaUrl);
                var stream = await shareFile.OpenAsync(FileAccessMode.Read);
                dpr.SetData(RandomAccessStreamReference.CreateFromStream(stream));

                deferral.Complete();
            });
        }
    }
}

The beauty of sharing is that the Bing Image Search app can provide links and images without any knowledge of how consuming applications may use the information. While the Bing Image Search app is providing standard links and images, you could also envision providing more proprietary file type information and opening up the entire ecosystem of Windows apps that communicate with your apps, content, or files in ways you hadn’t imagined.

Note

The Bing Image Search app doesn’t support being a share target. You will need to setup a declaration in your app manifest to display your Tile on the list, then your app will be activated with the entry point specified or into your App.OnSharingTargetActivated override as the default. For more information on handling Sharing as a target, see the samples online at http://code.msdn.microsoft.com/windowsapps/Sharing-Content-Target-App-e2689782.

Sensors

WinRT is meant to support modern hardware and sensors in a way Windows has never done in the past. One of those sensors that is of interest to the Bing Image Search app is the Accelerometer. The API for this sensor allows you to see when the user is physically shaking the device.

The events from the Accelerometer is different from other events like Search and Sharing because it happens so rapidly and subscribing to these events can, in some cases, actually turn on the physical hardware and ultimately drain the user’s battery. Because of this, you want to be sparing in the use of the Accelerometer. In the case of the Bing Image Search app, I will be using this event only on the SearchResultsPage to load more images. Example 4-28 shows the registration and handling of the Accelerometer event.

Example 4-28. Accelerometer registration (SearchResultsPageViewModel.cs)

public class SearchResultsPageViewModel : BindableBase
{
    private readonly INavigationService _navigationService;
    private readonly IAccelerometer _accelerometer;

    public SearchResultsPageViewModel(INavigationService navigationService,
                IAccelerometer accelerometer /* other depedencies */)
    {
        _navigationService = navigationService;
        _accelerometer = accelerometer;

        _accelerometer.Shaken += accelerometer_Shaken;
        _navigationService.Navigating += NavigatingFrom;
    }

    private void NavigatingFrom(object sender, NavigatingCancelEventArgs e)
    {
        _accelerometer.Shaken -= accelerometer_Shaken;
        _navigationService.Navigating -= NavigatingFrom;
    }

    private void accelerometer_Shaken(object sender, object e)
    {
        LoadMore();
    }
}

The SearchResultsPageViewModel registers the Accelerometer.Shaken event when the ViewModel loads. Luckily, the ViewModel has an AppBar already set up for loading more images, so when the Shaken event occurs, I just call the same method for the LoadMoreCommand. The final piece of the Accelerometer is remembering to unsubscribe from the event when you no longer need it. To handle this, you’ll notice an event called NavigatingFrom, which is fired whenever the NavigationService changes pages. I use this event to shut down the Accelerometer.

LockScreen

In addition to the high level native features that I’ve already discussed, Windows 8 has quite a few little features that apps may want to leverage. One of these features is the LockScreen. This screen displays a custom image for the user before they log in. On the DetailsPageViewModel, there is an AppBar button (Example 4-29 and Example 4-30). Similar to Set Tile and Save Image buttons, this button sends a SetLockScreenMessage to the message hub.

Example 4-29. Set Lock Screen AppBar Button (DetailsPage.xaml)

<Button x:Name="SetLockScreenCommand"Command="{Binding SetLockScreenCommand}"
        Style="{StaticResource SetLockScreenAppBarButtonStyle}" />

Example 4-30. SetLockScreen command (DetailsPageViewModel.cs)

public class DetailsPageViewModel
{
    // constructor and dependencies omitted
    public ICommand SetLockScreenCommand { get; set; }

    public async Task SetLockScreen()
    {
        await_messageHub.Send(new SetLockScreenMessage(_settings.SelectedImage));
    }
}

The message is handled by the SetLockScreenHandler (Example 4-31), which saves the image, updates the lock screen, and sets the status to display to the user. I’m using the ApplicationSettings to save the selected image as a new image in application storage called LockScreen.jpg. The reason I am storing it in application storage as a single name rather than using the actual name of the image, has to do with file storage. If I saved a unique image every time the user set the LockScreen, then I would have to clean up any other images and ensure I wasn’t retaining unnecessary copies of files. By using the same name, when a user updates the lock screen to a new image, any old image is automatically overwritten. To make this handler testable I’ve also created a LockScreenAdapter, and as you can see in Example 4-32, it is very simple.

Example 4-31. Message Handler for setting the Lock Screen to the specified image (SetLockScreenHandler.cs)

public class SetLockScreenHandler : IAsyncHandler<SetLockScreenMessage>
{
    private readonly ApplicationSettings _settings;
    private readonly ILockScreen _lockScreen;
    private readonly IToastNotificationManager _toastNotification;

    public SetLockScreenHandler(ApplicationSettings settings, ILockScreen lockScreen,
        IStatusService statusService)
    {
        _settings = settings;
        _lockScreen = lockScreen;
        _statusService = statusService;
    }

    public async Task HandleAsync(SetLockScreenMessage message)
    {
        var file = await _settings.GetLockScreenFileAsync(message.Image.MediaUrl);
        await _lockScreen.SetImageFileAsync(file);

        _statusService.TemporaryMessage = string.Format("Image {0} set as lock screen.",
            message.Image.Title);
    }
}

Example 4-32. LockScreenAdapter (LockScreenAdapter.cs)

public class LockScreenAdapter : ILockScreen
{
    public async Task SetImageFileAsync(IStorageFile file)
    {
        await LockScreen.SetImageFileAsync(file);
    }

    public async Task SetImageStreamAsync(IRandomAccessStream stream)
    {
        await LockScreen.SetImageStreamAsync(stream);
    }
}

Summary

Now that your app can take advantage of native features and participate in the new Windows 8 experience, you should get ready to start deploying your app. In the past, this meant creating installers, preparing deployment servers, and possibly creating CD-ROMs. Luckily this is not the case for Windows 8, and the Windows Store can help resolve the deployment issues from the past.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required