Create a blog reader app (C++)

Applies to Windows only

Start to finish, here's how to use C++ and Extensible Application Markup Language (XAML) to develop a Windows Store app that you can use to find and read blogs from RSS 2.0 or Atom 1.0 feeds.

This tutorial assumes that you're already familiar with the concepts in Lessons 1-4 under Create your first Windows Store app using C++.

To study the finished version of this app, you can download it from the MSDN Code Gallery website.

After you complete this tutorial, consider reading Developing an end-to-end Windows Store app using C++ and XAML: Hilo to learn more about how to use modern C++, the Windows Runtime, asynchronous programming, XAML, and development patterns such as Model-View-ViewModel (MVVM) in Windows Store apps developed in C++.

If you'd rather use another programming language, see:

Objectives

This tutorial is designed to help you understand how to create a multi-page Windows Store app, and how and when to use the Visual C++ component extensions (C++/CX) to simplify the work of coding against the Windows Runtime. The tutorial also teaches how to use the concurrency::task class to consume asynchronous Windows Runtime APIs.

The SimpleBlogReader app has these features:

  • Supports multiple blog feeds.
  • Supports Process Lifetime Management (PLM) and correctly saves and reloads its state if the system shuts it down while another task was in the foreground.
  • Adapts to different window sizes and device orientations (landscape or portrait).
  • Uses simple animations and transitions to add life to the user interface.
  • Uses styles to enhance the visual appeal of the app.

Part 1: Creating the project

To start, let's use the C++ Windows Store app Blank App template to create a project.

Hh465045.wedge(en-us,WIN.10).gifTo create the project

  1. In Visual Studio, choose File > New > Project, select Installed > Visual C++ > Windows Store, and then select the Blank App (XAML) template. For more complete instructions, see Hello World in C++.

  2. Name the project "SimpleBlogReader".

Specifying app capabilities

A Windows Store app runs in a security container that has limited access to the file system, network resources, and hardware. When a user installs an app from the Windows Store, Windows looks at the metadata in the Package.appxmanifest file to determine what capabilities the app requires. For example, an app might have to access data from the Internet, documents from the user's Document Library, or the user's webcam and microphone. When the app is installed, it displays to the user the capabilities it needs, and the user must grant permission before it can access those resources. If the app doesn't receive permission for a resource it needs, it won't be allowed access to that resource at run time. Our blog reader requires Internet access, so let's make sure that it's requested.

Hh465045.wedge(en-us,WIN.10).gifTo verify that basic Internet capability is requested

  1. In Solution Explorer, open Package.appxmanifest. The file opens in the Application Manifest Designer.

  2. Select the Capabilities tab.

    Notice that the Internet (Client) check box is already selected, based on settings in the Blank App template. If we needed some other capability, we could select it in this designer.

  3. Close the manifest designer.

Typically, you use the Application Manifest Designer to specify capabilities, and then the designer writes the specifications into the Capabilities element in the Package.appxmanifest.xml file so that you don't have to modify it manually. But let's open Package.appxmanifest.xml in the XML Text Editor to examine how the Capabilities element is structured.


<Capabilities>    
    <Capability Name="internetClient" />
</Capabilities>


For more information about app capabilities, see Manifest Designer.

Part 2: Getting data into an app

Now we can write the code to get the blog feed into the app. The "Developing for Windows" blog exposes the full text of its posts in both RSS and Atom forms. In our app, we want to display the title, author, date, and content from each of the latest blog posts. We can use the classes in the Windows::Web::Syndication namespace to download the feeds. (We could also use those classes to display the data in the UI, but instead we'll create our own data classes so that we can treat RSS and Atom feeds the same way.) We create three classes:

  • FeedData holds info about an RSS or Atom feed.

  • FeedItem holds info about individual blog posts in the feed.

  • FeedDataSource contains methods to download the feeds and initialize our data classes.

We define these classes as public ref classes to enable the binding of data to the XAML elements that display the Title, Author, and so on. We use the Bindable attribute to indicate to the XAML compiler that we are binding dynamically to instances of these types. In a public ref class, public data members are exposed as properties. Properties that have no special logic don't require a user-specified getter and setter—the compiler will supply them. In the FeedData class, notice how IVector is used to expose a public collection type to other Windows Runtime classes and components. We also use the Platform::Collections::Vector class internally as the concrete type that implements IVector. Later, we'll learn how to consume that type.

Hh465045.wedge(en-us,WIN.10).gifTo create custom data classes

  1. In Solution Explorer, on the shortcut menu for the SimpleBlogReader project node, choose Add > New Item.

  2. Select the Header File (.h) option and name it FeedData.h.

  3. Open FeedData.h and then paste the following code into it. Take a moment to look at the code and familiarize yourself with the C++/CX constructs. Notice the #include directive for "pch.h"—that's where to put <string>, <vector>, and other system headers. By default, pch.h includes collection.h, which is required for the Platform::Collections::Vector type. For more information, see Classes and structures (C++/CX).

    
    
    //feeddata.h
    
    #pragma once
    #include "pch.h"
    
    
    namespace SimpleBlogReader
    {
        // To be bindable, a class must be defined within a namespace
        // and a bindable attribute needs to be applied.
        // A FeedItem represents a single blog post.
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedItem sealed
        {
        public:
            FeedItem(void){}
    
            property Platform::String^ Title;
            property Platform::String^ Author;
            property Platform::String^ Content;      
            property Windows::Foundation::DateTime PubDate;      
            property Windows::Foundation::Uri^ Link;
    
        private:
            ~FeedItem(void){}
        };
    
        // A FeedData object represents a feed that contains 
        // one or more FeedItems. 
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedData sealed
        {
        public:
            FeedData(void)
            {
                m_items = ref new Platform::Collections::Vector<FeedItem^>();
            }
    
            // The public members must be Windows Runtime types so that
            // the XAML controls can bind to them from a separate .winmd.
            property Platform::String^ Title;            
            property Windows::Foundation::Collections::IVector<FeedItem^>^ Items
            {
                Windows::Foundation::Collections::IVector<FeedItem^>^ get() {return m_items; }
            }
    
            property Platform::String^ Description;
            property Windows::Foundation::DateTime PubDate;
            property Platform::String^ Uri;
    
        private:
            ~FeedData(void){}
    
            Platform::Collections::Vector<FeedItem^>^ m_items;
        };   
    
        // A FeedDataSource represents a collection of FeedData objects
        // and provides the methods to download the source data from which
        // FeedData and FeedItem objects are constructed. This class is 
        // instantiated at startup by this declaration in the 
        // ResourceDictionary in app.xaml: <local:FeedDataSource x:Key="feedDataSource" />
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedDataSource sealed
        {
        private:
            Platform::Collections::Vector<FeedData^>^ m_feeds;
            std::map<Platform::String^, concurrency::task_completion_event<FeedData^>> m_feedCompletionEvents;
            FeedData^ GetFeedData(Platform::String^ feedUri, Windows::Web::Syndication::SyndicationFeed^ feed);       
    
        public:
            FeedDataSource();
            property Windows::Foundation::Collections::IObservableVector<FeedData^>^ Feeds
            {
                Windows::Foundation::Collections::IObservableVector<FeedData^>^ get()
                {
                    return this->m_feeds;
                }
            }
            void InitDataSource();
            static Windows::Foundation::IAsyncOperation<FeedData^>^ GetFeedAsync(Platform::String^ title);
            static FeedItem^ GetFeedItem(FeedData^ fd, Platform::String^ uniqueiD);
        };
    }
    
    
  4. Now create a new .cpp file: On the shortcut menu for the SimpleBlogReader project node, choose Add > New Item, and then select the C++ File (.cpp) option and name the file FeedData.cpp. Open the file and paste this code into it:

    
    
    #include "pch.h"
    #include "FeedData.h"
    
    
    using namespace std;
    using namespace concurrency;
    using namespace SimpleBlogReader;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Web::Syndication;
    
    
    
    FeedDataSource::FeedDataSource()
    {
        m_feeds = ref new Vector<FeedData^>();
    }
    
    // Retrieve the data for each atom or rss feed and put it
    // into our custom data structures.
    void FeedDataSource::InitDataSource()
    {
        // Left as an exercise: store the urls separately and let the user configure them.
        // It might be more convenient to use Platform::Strings here, but using wstring 
        // serves to demonstrate how standard C++ types can be used here.
        std::vector<std::wstring> urls; 
        urls.push_back(L"http://windowsteamblog.com/windows/b/developers/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/extremewindows/atom.aspx");
    
        urls.push_back(L"http://windowsteamblog.com/windows/b/business/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowssecurity/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/springboard/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx");
        // There is no Atom feed for this blog, so we use the RSS feed.
        urls.push_back(L"http://windowsteamblog.com/windows_live/b/windowslive/rss.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows_live/b/developer/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/ie/b/ie/atom.aspx");
        urls.push_back(L"http://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx");
    
        // If we are resuming, we need to create a map of completion events so that
        // we don't attempt to restore page's state before it's backing data has been loaded.
        // First we create all the  events in an "unset" state, mapped to the urls as keys.
        // we'll set the event after we asynchronously load the feed.
        for( wstring url : urls)
        {
            auto uri = ref new String(url.c_str());
            task_completion_event<FeedData^> e;
            m_feedCompletionEvents.insert(make_pair(uri, e));
        }
    
        SyndicationClient^ client = ref new SyndicationClient();   
    
        // Range-based for loop. Never write a regular for loop again!
        for(wstring url : urls)
        {
            // Create the async operation. feedOp is an 
            // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^
    
            auto uri = ref new String(url.c_str());
            auto feedUri = ref new Uri(uri);
            auto feedOp = client->RetrieveFeedAsync(feedUri);
    
            // Create the task object and pass it the async operation.
            // SyndicationFeed^ is the type of the return value
            // that the feedOp operation will eventually produce.       
    
            // Then, initialize a FeedData object with the feed info. Each
            // operation is independent and does not have to happen on the
            // UI thread. Therefore, we specify use_arbitrary.
            create_task(feedOp)
    
            .then([this, uri]  (SyndicationFeed^ feed) -> FeedData^
            {
                return GetFeedData(uri, feed);
            }, concurrency::task_continuation_context::use_arbitrary())
    
    
            // Append the initialized FeedData object to the list
            // that is the data source for the items collection.
            // This has to happen on the UI thread. By default, a .then
            // continuation runs in the same apartment thread that it was called on.
            // Because the actions will be synchronized for us, we can append 
            // safely to the Vector without taking an explicit lock.
            .then([this] (FeedData^ fd)
            {
                m_feeds->Append(fd);
                m_feedCompletionEvents[fd->Uri].set(fd);
    
                // Write to VS output window in debug mode only. Requires <windows.h>.
                OutputDebugString(fd->Title->Data());
                OutputDebugString(L"\r\n");
            })
    
            // The last continuation serves as an error handler. The
            // call to get() will surface any exceptions that were raised
            // at any point in the task chain.
            .then( [] (task<void> t)
            {
                try
                {
                    t.get();
                }
                // SyndicationClient throws Platform::InvalidArgumentException 
                // if a URL contains illegal characters.
                // We catch this exception for demonstration purposes only.
                // In the current design of this app, an illegal
                // character can only be introduced by a coding error
                // and should not be caught. If we modify the app to allow
                // the user to manually add a new url, then we need to catch
                // the exception.
                catch(Platform::InvalidArgumentException^ e)
                {
                    // For example purposes we just output error to console.
                    // In a real world app that allowed the user to enter
                    // a url manually, you could prompt them to try again.
                    OutputDebugString(e->Message->Data());
                }
            }); //end task chain
        };
    }
    
    
    FeedData^ FeedDataSource::GetFeedData(String^ feedUri, SyndicationFeed^ feed)
    {
    
        FeedData^ feedData = ref new FeedData();
    
        // Knowing this makes it easier to map completion_events 
        // when we resume from termination.
        feedData->Uri = feedUri;
    
        // Get the title of the feed (not the individual posts).
        feedData->Title = feed->Title->Text; 
    
        if (feed->Subtitle->Text != nullptr)
        {
            feedData->Description = feed->Subtitle->Text;
        }	 
        // Use the date of the latest post as the last updated date.
        feedData->PubDate = feed->Items->GetAt(0)->PublishedDate;	
    
        // Construct a FeedItem object for each post in the feed
        // using a range-based for loop. Preferable to a 
        // C-style for loop, or std::for_each.
        for (auto  item : feed->Items)
        {
            auto feedItem = ref new FeedItem();
            feedItem->Title = item->Title->Text; 
            feedItem->PubDate = item->PublishedDate;		
    
            //We only get first author in case of multiple entries.
            feedItem->Author = item->Authors->GetAt(0)->Name; 
    
            if (feed->SourceFormat == SyndicationFormat::Atom10)
            {
                feedItem->Content = item->Content->Text;
                feedItem->Link = ref new Uri(item->Id);
            }
    
            else if (feed->SourceFormat == SyndicationFormat::Rss20)
            {
                feedItem->Content = item->Summary->Text;
                feedItem->Link = item->Links->GetAt(0)->Uri;
            }
    
            feedData->Items->Append(feedItem);
        };
    
        return feedData;
    
    } //end GetFeedData
    
    
    // We use this method to get the proper FeedData object when resuming
    // from shutdown. We need to wait for this data to be populated before
    // we attempt to restore page state. Note the use of task_completion_event
    // which doesn't block the UI thread.
    IAsyncOperation<FeedData^>^ FeedDataSource::GetFeedAsync(String^ uri)
    {
        return create_async([uri]()
        {
            auto feedDataSource = safe_cast<FeedDataSource^>( 
                App::Current->Resources->Lookup("feedDataSource"));
    
            // Does not block the UI thread.
            auto f = feedDataSource->m_feedCompletionEvents[uri];
    
            // In the callers we continue from this task after the event is 
            // set in InitDataSource and we know we have a FeedData^.
            task<FeedData^> t = create_task(f);
            return t;
        });
    }
    
    // We stored the stringID when the app was suspended
    // because storing the FeedItem itself would have required
    // more custom serialization code. Here is where we retrieve
    // the FeedItem based on its string ID.
    FeedItem^ FeedDataSource::GetFeedItem(FeedData^ feed, String^ uniqueId)
    {
        auto itEnd = end(feed->Items);
        auto it = std::find_if(begin(feed->Items), itEnd, 
            [uniqueId] (FeedItem^ fi)
        {
            return fi->Title == uniqueId;
        });
    
        if (it != itEnd)
            return safe_cast<FeedItem^>(*it);
    
        return nullptr;
    }
    
    
    

    The Windows.Web.Syndication.SyndicationClient class retrieves and parses RSS and Atom feeds. Because this operation involves network I/O, the method executes asynchronously. The asynchronous programming model is found throughout the Windows Runtime class libraries. An asynchronous method call returns control immediately to the UI thread so that the UI remains responsive while the operation is being performed on a background thread.

    The Windows Runtime provides a way to invoke asynchronous operations and get their results when they complete. You can program directly against that API, but the preferred approach is to use the task class that's defined in ppltasks.h, which is included in pch.h. The task class invokes these same asynchronous Windows Runtime APIs, but by using it, you can write more concise code, more easily chain asynchronous operations, and handle in one place any exceptions that arise. The task class is used in two places in the file we just created. When you use the task class, the basic steps are always the same:

    1. Create an asynchronous operation by calling a Windows Runtime*Async method such as Windows::Web::Syndication::ISyndicationClient::RetrieveFeedAsync.

    2. Create a task object by calling concurrency::create_task and using the operation as an input parameter.

    3. Define a task that will run when the original task completes by calling task::then and specifying a lambda that takes the return value of the original task as input.

    4. Optionally, call then again, one or more times. These clauses can accept the return value from the previous clause.

    5. Provide a final then clause that handles any exceptions that were thrown anywhere in the chain of operations. This step is optional but highly recommended.

Part 3: Creating a custom date-formatting class

Now is a good time to add one more custom class—an implementation of IValueConverter, which is an interface that you use to convert between arbitrary types in arbitrary ways. Our class is a Date Converter that returns day, month, and year as individual strings when it's given a DateTime argument. Later on, we'll use this class in the visual style that we'll define for grid items and list-view items.

Hh465045.wedge(en-us,WIN.10).gifTo create a class that implements IValueConverter

  • In the SimpleBlogReader project, add a new header named DateConverter.h and then paste this implementation into it:

    
    
    //DateConverter.h
    
    #pragma once
    #include <string> //for wcscmp
    
    namespace SimpleBlogReader
    {
    
    public ref class DateConverter sealed : public Windows::UI::Xaml::Data::IValueConverter  
    {
        public:
        virtual Platform::Object^ Convert(Platform::Object^ value,
                                          Windows::UI::Xaml::Interop::TypeName targetType,
                                          Platform::Object^ parameter,
                                          Platform::String^ language)
        {		
            if(value == nullptr)
            {
                throw ref new Platform::InvalidArgumentException();
            }
            auto dt = safe_cast<Windows::Foundation::DateTime>(value);
            auto param = safe_cast<Platform::String^>(parameter);
            Platform::String^ result;
            if(param == nullptr)
            {
                auto dtf =
                Windows::Globalization::DateTimeFormatting::DateTimeFormatter::ShortDate::get();
                result = dtf->Format(dt);
            }
            else if(wcscmp(param->Data(), L"month") == 0)
            {
                auto month = 
                    ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{month.abbreviated(3)}");
                result = month->Format(dt);
            }
            else if(wcscmp(param->Data(), L"day") == 0)
            {
                auto month = 
                   ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{day.integer(2)}");
                result = month->Format(dt);
            }
            else if(wcscmp(param->Data(), L"year") == 0)
            {
                auto month = 
                    ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{year.full}");
    				        result = month->Format(dt);
            }
            else
            {
                // We don't handle other format types currently.
                throw ref new Platform::InvalidArgumentException();
            }
    
            return result; 
         }
    
        virtual Platform::Object^ ConvertBack(Platform::Object^ value,
                                              Windows::UI::Xaml::Interop::TypeName targetType,
                                              Platform::Object^ parameter,
                                              Platform::String^ language)
        {   
            // Not needed in SimpleBlogReader. Left as an exercise.
            throw ref new Platform::NotImplementedException();
        }
    };
    }
    
    

Hh465045.wedge(en-us,WIN.10).gifTo make the app recognize custom classes

  1. To make our app—in particular, its XAML controls—recognize our custom classes, we first have to include their header files so that they get compiled. We'll do that by adding this code to App.xaml.h:
    
    
    #include "FeedData.h"
    #include "DateConverter.h"
    
    
  2. We want the FeedDataSource and the DateConverter to stay alive for as long as the app is alive, so we’ll store them as app resources. This causes them to be instantiated when the app starts up. We will reference the instances throughout the app—both in XAML and in the code-behind—by using the x:Key value defined in the following code.

    To implement these references, in App.xaml, just before the closing tag of the Application element, type <Application.Resources> (or use dynamic Statement Completion to add this element). Then add the resources FeedDataSource and DateConverter so that the <Application> node looks like this:

    
     <Application
        x:Class="SimpleBlogReader.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:SimpleBlogReader">
        <Application.Resources>
            <local:FeedDataSource x:Key="feedDataSource" />
            <local:DateConverter x:Key="dateConverter" />
        </Application.Resources>
    </Application>
    
    
    
  3. Now is a good time to make sure that the app compiles without errors, even though it doesn't do anything at this point. To compile a debugging instance of the app and display it, press F5. To go back to developing, return to Visual Studio and press Shift+F5.

Part 4: Adding pages and navigation

Now we're ready to create the UI. If we're going to support multiple blogs, we have to add pages to the app and handle the navigation between them. Fortunately, Visual Studio includes several page templates that already implement most of the navigation and orientation functionality we need.

First, we want a page that lists the Windows Team Blogs. For this, we can use an Items Page template. When a reader picks a blog from this page, we load the list of posts for that blog into another page, which we'll base on the Split Page template. We'll also use the Basic Page template to add a detail page so that a user can read individual blog posts without the list view taking up space. Because these templates already support the navigation we need, we don't have to code for that and can concentrate on our main tasks—write custom logic for the LoadState and SaveState methods that are stubbed out in each code-behind class, and add an app bar that has a button to enable forward navigation from the Split page to the Detail page.

Hh465045.wedge(en-us,WIN.10).gifTo add pages to the app

  1. In Solution Explorer, open the shortcut menu for MainPage.xaml and choose Remove. We won't be using this page or its code-behind files in our project. You can either delete it permanently or just remove it.

  2. In App.xaml.cpp, change the #include directive for MainPage.xaml.h to #include ItemsPage.xaml.h.

  3. Still in App.xaml.cpp, press Ctrl+H to find instances of "MainPage" and replace them with "ItemsPage." Notice that the instances of "ItemsPage" have a wavy red underline—that will go away when you add the new page.

  4. Open the shortcut menu for the SimpleBlogReader project and then choose Add > New Item.

  5. In the Add New Item dialog box, in the Installed pane, select Visual C++ > Windows Store.

    In the center pane, select Items Page, accept the default name, and choose the Add button.

    In the dialog box that asks whether you want to add required files automatically, choose Yes. These files contain code to support navigation, and serialization and de-serialization of simple types when the app is terminated and resumed. They are added to the \Common\ folder.

  6. Repeat the previous steps, but select the Split Page template.

  7. Repeat the steps again, but select the Basic Page template and name the page DetailPage.

The Items page will show the list of Windows Team Blogs. The Split page will show the posts for each blog on the left side and the content of the selected post on the right side. The Detail page will show nothing but the content of a selected post, a Back button, and the page title. On this page, instead of loading the post content into the WebView from a string of HTML as we do in the Split page, we navigate to the URL of the post and show the actual web page. After we implement this, the pages of the app will look something like this:

Three-page navigation model

When we examine the XAML and code-behind for the pages we added, it's apparent that these page templates do a lot of work for us. In fact, because it's easy to get lost at first, it helps to understand that each page template has three main sections:

Resources

Styles and data templates for the page are defined in the Resources section. We talk more about this in the Creating a consistent look with styles section.

Visual State Manager

Animations and transitions that adapt the app to different layouts and orientations are defined in the Visual State Manager (VSM). We talk more about this in the Adapting to different layouts section.

App Content

The controls and content that make up the app UI are defined in the root layout panel.

 

Navigating between pages

One of the main activities in a Windows Store app is navigating between pages. A user might be able to choose a forward or back button to move through the pages, or choose an item that opens another page—for example, a page that shows details about the item. Here's the navigation design for SimpleBlogReader:

  • When the app starts, the Items page shows a grid of blog feeds (DataFeed objects). Only the blog titles are displayed, along with descriptions (if they exist) and the dates of the last posts. When the user selects a feed, the app navigates to the Split page.

  • If the app is in the landscape orientation, the Split page shows the list of posts (FeedItem objects) on the left, and a preview of the currently selected post on the right. In portrait orientation, the Split page shows either the list or the preview, but not both at the same time. When the user selects another item in the list, the preview changes.

  • The Split page has an app bar that a user can invoke by swiping from the top of the screen or by right-clicking the mouse. When the user chooses the button on the app bar, the app navigates to the Detail page that shows the blog post as a complete web page.

  • On the Detail page, the user can choose the Back button to go back to the Split page. The Split page also has a Back button to go back to the Items page.

To complicate things, every time we load a page, we must consider whether the navigation is invoked by a user action (forward or back) or by the system resuming the app after it was terminated it at some point in the past.

The SuspensionManager class (in SuspensionManager.h and SuspensionManager.cpp in the \Common\ folder) provides much of the code that's required to save page state when the app is suspended or terminated so that it can be restored later. However, it is up to us to implement the specific logic that's required to load or re-load the pages.

The XAMLUI framework provides a built-in navigation model that uses Frames and Pages and works much like the navigation in a web browser. The Frame control hosts Pages and keeps a navigation history that you can use to go back and forward through pages that have been visited. Also, you can pass data between pages as you navigate.

Navigation history starts when the app is started. The infrastructure that supports navigation is in the App class. In the Visual Studio project templates, a Frame that's named rootFrame is set as the content of the app window. In App.xaml.cpp, let's look at the code that's supplied by the template. Notice that after the rootFrame is set, the app checks its current state because it might be starting from a terminated state or resuming from suspension and might already have its content in memory. If it's starting from a terminated state, it has to load the state that was saved when it was terminated. After the app handles these various cases, it navigates to its first window.



// Default implementation. Don't paste this into SimpleBlogReader.
 
/// <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="e">Details about the launch request and process.</param>
void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ e)
{

#if _DEBUG
    if (IsDebuggerPresent())
    {
        DebugSettings->EnableFrameRateCounter = true;
    }
#endif

    auto rootFrame = dynamic_cast<Frame^>(Window::Current->Content);

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == nullptr)
    {
        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key
        rootFrame = ref new Frame();

        if (e->PreviousExecutionState == ApplicationExecutionState::Terminated)
        {
            // TODO: Restore the saved session state only when appropriate, scheduling the
            // final launch steps after the restore is complete

        }

        if (rootFrame->Content == nullptr)
        {
            // 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(TypeName(ItemsPage::typeid), e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
        }
        // Place the frame in the current Window
        Window::Current->Content = rootFrame;
        // Ensure the current window is active
        Window::Current->Activate();
    }
    else
    {
        if (rootFrame->Content == nullptr)
        {
            // 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(TypeName(ItemsPage::typeid), e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
        }
        // Ensure the current window is active
        Window::Current->Activate();
    }
}


To enable navigation between pages, we use the Frame control's Navigate, GoForward, and GoBack methods. By using the Navigate(TypeName, Object) method, we can navigate to a new page and pass data to it at the same time. The first parameter is the TypeName of the page that we are navigating to. We use the static typeid operator to obtain the TypeName of a type. In this case, we want to navigate to the Items page as the first page that users encounter when they start or resume the app.

Every page has as a private member a NavigationHelper object, which is auto-generated and is located in the \Common\ folder in NavigationHelper.h and NavigationHelper.cpp. When a page is navigated to, NavigationHelper handles the basic management of the navigation infrastructure. One thing it does is call LoadState and SaveState at the correct times. As an app developer, your task is to add logic to the page's LoadState and SaveState methods so that the page always has the correct data to bind to and can retrieve that data if the app is terminated.

The first parameter in LoadState and SaveState is the page that sent the navigation command. The second parameter is a LoadStateEventArgs that contains, among other things, a PageState property that contains the data we passed in when we called Navigate. SuspensionManager will try to serialize any objects that are passed through this parameter, but without help from us, it can succeed only if the type is a String, a Guid, or a primitive type. Therefore, we won't pass an entire FeedData object, but we can pass a string and use it as a key to look up the specific FeedData or FeedItem object. That's the purpose of the GetFeedAsync and GetFeedItem methods in the FeedDataSource class. We'll look at those methods in more detail later.

Hh465045.wedge(en-us,WIN.10).gifTo navigate from the App class to the Items page

  1. In App.xaml.cpp, the App::OnLaunched method checks to see whether the rootFrame already exists and if it doesn't, the code creates one. If we are creating a new rootFrame, then we also have to download and initialize our feed items. This code is reached when the app is started after being terminated by the system or closed by the user. If neither of these events occur, the feed items remain in memory.

    In App.xaml.cpp, in the OnLaunched method, add this code after the rootFrame = ref new Frame(); statement:

    
    
    
        FeedDataSource^ feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        if (feedDataSource->Feeds->Size == 0)
        {
            feedDataSource->InitDataSource();
        }
    
    
    

    When the Navigate method is invoked in App::OnLaunched, it eventually causes the ItemsPage::LoadState event handler to be called. As discussed earlier, we can't pass the FeedDataSource object or even the Feeds property because we don't want to have to serialize it ourselves. Instead, we store a reference to the FeedDataSource as a resource in App.xaml, and here we just access it. We still have to use the Feeds property to initialize the DefaultViewModel for ItemsPage. Here, the Feeds Vector is associated with a key that's named "Items" and is inserted into the DefaultViewModel member for ItemsPage. DefaultViewModel is a Windows::Foundation::Collections::IObservableMap. Each page has its own DefaultViewModel. After you initialize the DefaultViewModel with some data, the ItemsViewSource::View property will point to your data. If the elements in the collection are bindable, then the items will show up in the UI.

  2. In ItemsPage.xaml.cpp, replace the LoadState method with this code:

    
    
    void ItemsPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;	// Unused parameter
        (void)e;	// Unused parameter
    
        // This is the first page to load on startup. The feedDataSource was constructed when the app loaded
        // in response to this declaration in app.xaml: <local:FeedDataSource x:Key="feedDataSource" />
        // and was initialized aynchronously in the OnLaunched event handler in app.xaml.cpp. 
        // Initialization might still be happening, but that's ok. 
        auto feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
    
        // In ItemsPage.xaml (and every other page), the DefaultViewModel is set as DataContext:
        // DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
        // Because ItemsPage displays a collection of feeds, we set the Items element
        // to the FeedDataSource::Feeds collection. By comparison, the SplitPage sets this element to 
        // the Items collection of one FeedData object.
        this->DefaultViewModel->Insert("Items", feedDataSource->Feeds);
    
    }
    
    

Now press F5 to run the app. Notice that without any changes yet to the template code, some of the data that we passed to the Items page is already showing up in the grid squares. It looks something like this (items might be arranged differently depending on your screen resolution):

Items page

The only thing left to do on the Items page is to tell it what to do when the user chooses one of its items.

Hh465045.wedge(en-us,WIN.10).gifTo navigate from the Items page to the Split page

  1. When the user chooses a blog in the ItemsPage collection, we navigate from the Items page to the Split page. To enable this navigation, we want GridView items to behave like buttons instead of selected items. To make the GridView items respond like buttons, we set the SelectionMode and IsItemClickEnabled properties as shown in the next code block. We then add a handler for the ItemClicked event of the GridView. In ItemsPage.xaml find the opening tag for the GridView element that is named itemGridView that looks like this:

    
    
     <GridView
                x:Name="itemGridView"
                AutomationProperties.AutomationId="ItemsGridView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.RowSpan="2"
                Padding="116,136,116,46"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                SelectionMode="None"
                IsItemClickEnabled="true"
                IsSwipeEnabled="false">
    
    
    

    Now, position the cursor just before the closing bracket and press Enter to create a new line after IsSwipeEnabled="false". Type Margin="0,-10,0,10" and then on a new line start typing ItemClick="itemGridView_ItemClick" . Notice that when your type ItemClick=, auto-complete suggests itemGridView_ItemClick—accepting the auto-complete suggestion stubs out the code-behind for you.

  2. In ItemsPage.xaml.cpp, add this include directive so that SplitPage is recognized:

    
    #include "SplitPage.xaml.h"
    
    
    
  3. If you used the suggested default name to stub out the code-behind, as described in step 2, the itemGridView_ItemClick event handler prototype appears in ItemsPage.xaml.h and an implementation stub appears in ItemsPage.xaml.cpp. Just paste this code into the stub implementation:

    
    
    
        // We must manually cast from Object^ to FeedData^.
        auto feedData = safe_cast<FeedData^>(e->ClickedItem);
    
        // Store the current URI so that we can look up the
        // correct feedData object on resume.
        App::Current->Resources->Insert("CurrentFeed", feedData);
    
        // Navigate to SplitPage and pass the title of the selected feed.
        // SplitPage will receive this in its LoadState method in the navigationParamter.
        this->Frame->Navigate(TypeName(SplitPage::typeid), feedData->Title);
    
    
    
  4. When the Items page navigates to the Split page, it causes the SplitPage::LoadState method to be called. As in ItemsPage, LoadState has to determine how it has been navigated to, and what the app's previous state was. The code comments in the next example provide more detail—you'll probably recognize much of it from looking at App::OnLaunched and ItemsPage::LoadState.

    Now open SplitPage.xaml.cpp and use this code to replace the entire LoadState method:

    
    void SplitPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        // If we are navigating forward from ItemsPage, there is no existing page state.
        if (e->PageState == nullptr)
        {
            // Current feed was set in the click event in ItemsPage. We don't pass it in
            // through navigationParameter because on suspension, the default serialization 
            // mechanism will try to store that value but fail because it can only handle 
            // primitives, strings, and Guids.
            auto fd = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed"));
    
            // Insert into the ViewModel for this page to initialize itemsViewSource->View
            this->DefaultViewModel->Insert("Feed", fd);
            this->DefaultViewModel->Insert("Items", fd->Items);
    
            // When this is a new page, select the first item automatically unless logical page
            // navigation is being used (see the logical page navigation #region below).
            if (!UsingLogicalPageNavigation() && itemsViewSource->View != nullptr)
            {
                this->itemsViewSource->View->MoveCurrentToFirst();
            }
            else
            {
                this->itemsViewSource->View->MoveCurrentToPosition(-1);
            }
        }
    
        // e>PageState != null means either (1) we are returning from DetailPage
        // or (2) we are resuming from termination. If (1), then we still have our
        // state and don't have to do anything. If (2), then we have to restore the page.
        else if (!this->DefaultViewModel->HasKey("Feed"))
        {
            // All we stored is the Uri string for the feed, not the object. 
            auto uri = safe_cast<String^>(e->PageState->Lookup("Uri"));
    
            // FeedDataSource::InitDataSource has already been called. 
            // It's an asynchronous operation, so our FeedData might not
            // be available yet. GetFeedAsync uses a task_completion_event to  
            // wait (on its own thread) until the specified FeedData is available.
            // The next three methods follow the basic async pattern in C++:
            // 1. Call the async method.
            auto feedDataOp = FeedDataSource::GetFeedAsync(uri);
    
            // 2. Create a task from it.
            auto feedDataTask = create_task(feedDataOp);
    
            // 3. Define the work to be performed after the task completes.
            feedDataTask.then([this, e](FeedData^ feedData)
            {
                // Now we have the feedData, so it's safe to get the FeedItem
                // synchronously. Inserting into DefaultViewModel
                // initializes the itemsViewSource-View object.
                this->DefaultViewModel->Insert("Feed", feedData);
                this->DefaultViewModel->Insert("Items", feedData->Items);
    
                // DetailPage has to get the new Uri from this value.
                App::Current->Resources->Insert("CurrentFeed", feedData);
    
                // Now that we have a FeedData^, we can call GetFeedItem safely and
                // pass in the title that we stored before the app was terminated.
                auto itemTitle = safe_cast<String^>(e->PageState->Lookup("SelectedItem"));
                auto selectedItem = FeedDataSource::GetFeedItem(feedData, itemTitle);
    
                if (selectedItem != nullptr)
                {
                    this->itemsViewSource->View->MoveCurrentTo(selectedItem);
                }
            });
        }
    }
    
    
    

    When the app is resumed from termination, we don't have a FeedItem object, we only have a string. Therefore, we have to use the string to look up the FeedItem—but we can't until the FeedItem has been downloaded by FeedDataSource. Nevertheless, we don't want to wait for all of the feeds to download; we want to wait only until the one we need is ready. All of this synchronization happens in this LoadState method and in the FeedDataSource::GetFeedAsync method. After we have the FeedData object, we can call the GetFeedItem method synchronously to get the FeedItem for the selected post so that we can populate the preview pane.

    Note  We insert the Items property separately into the DefaultViewModel for the Split page to make the items accessible for XAML data-binding.

    No extra work is required to navigate back to the Items page. The template includes code to handle the BackButton.Click event and call the Frame.GoBack method.

  5. When you press F5 to run the app at this point, when you click on a feed data item, you will notice that the blog text in the preview pane on the Split page is displaying raw HTML. To fix that, we have to change the layout that's used for the title and content of the selected blog post. If the app is running, close it by returning to Visual Studio and pressing Shift+F5.

Hh465045.wedge(en-us,WIN.10).gifTo implement SplitPage::SaveState

  • In ItemsPage, we didn't have to save page state because we always show all of the items. However, in SplitPage, we have to save the currently selected item so that the app can start from exactly this state if it's terminated and resumed. As mentioned earlier, because we are relying on the SuspensionManager to do the serialization work for us, we can only save strings and numbers, not FeedItem objects. Therefore, for the currently selected item, we save both the title of the FeedItem object and the URI of the FeedData object so that we can find the item again after termination and resumption.

    In SplitPage.xaml.cpp, find the SaveState method and then use this code to replace it:

    
    void SplitPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        if (itemsViewSource->View != nullptr)
        {
            auto selectedItem = itemsViewSource->View->CurrentItem;
            if (selectedItem != nullptr)
            {
                auto feedItem = safe_cast<FeedItem^>(selectedItem);
                auto itemTitle = feedItem->Title;
                e->PageState->Insert("SelectedItem", itemTitle);
            }
    
            // Save the feed title also.
            auto feedData = safe_cast<FeedData^>(this->DefaultViewModel->Lookup("Feed"));
            e->PageState->Insert("Uri", feedData->Uri);
        }
    }
    
    

We have to make a few more changes to finish adding functionality to our pages. After we add this code, we can move on to styling and animating.

Hh465045.wedge(en-us,WIN.10).gifTo modify the bindings and layout in SplitPage and ItemsPage

  1. First, in SplitPage.xaml, change the Grid named titlePanel to span 2 columns:

    
    <!-- Back button and page title -->
    <Grid x:Name="titlePanel" Grid.ColumnSpan="2">
    
    
  2. Next, because we used a key named "Feed" when we added our data to the DefaultViewModel, we have to change the binding in the page title to bind to the Feed property. In SplitPage.xaml, change the Text binding of the TextBlock named pageTitle to bind to Feed.Title, like this:

    
    <TextBlock x:Name="pageTitle" Text="{Binding Feed.Title}" 
        Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
        IsHitTestVisible="false" TextWrapping="WrapWholeWords" 
        VerticalAlignment="Bottom" Margin="0,0,30,40"/>
    
    
  3. In ItemsPage.xaml, the page title is bound to a static resource that has the key AppName. Change the text in this resource to Windows Team Blogs, like this:

    
    <x:String x:Key="AppName">Windows Team Blogs</x:String>
    
    

Hh465045.wedge(en-us,WIN.10).gifTo add a WebView control to SplitPage.xaml

  1. In SplitPage.xaml, we also want to change the layout that's used to show the title and content of the selected blog post. To do that, use the following layout to replace the ScrollViewer named itemDetail. Notice that much of this XAML resembles our previous work in MainPage.xaml. The purpose of the Rectangle element is explained later in this article.

    In SplitPage.xaml, collapse the existing ScrollViewer element, delete it, and then paste in this markup:

    
            <!-- Details for selected item -->
            <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Column="1"
                Grid.RowSpan="2"
                Padding="70,0,120,0"
                DataContext="{Binding SelectedItem, ElementName=itemListView}"
                HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Enabled"
                ScrollViewer.ZoomMode="Disabled">
    
                <Grid x:Name="itemDetailGrid" Margin="0,60,0,50">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
    
                    <Image Grid.Row="1" Margin="0,0,20,0" Width="180" Height="180" Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                    <StackPanel x:Name="itemDetailTitlePanel" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2">
                        <TextBlock x:Name="itemTitle" Margin="0,-10,0,0" Text="{Binding Title}" Style="{StaticResource SubheaderTextBlockStyle}"/>
                        <TextBlock x:Name="itemSubtitle" Margin="0,0,0,20" Text="{Binding Subtitle}" Style="{StaticResource SubtitleTextBlockStyle}"/>
                    </StackPanel>
                 <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" Grid.Row="2" Grid.Column="1" Margin="0,15,0,20">
                        <Grid>
                            <WebView x:Name="contentView" />
                            <Rectangle x:Name="contentViewRect" />
                        </Grid>
                    </Border>
                </Grid>
            </ScrollViewer>
    
    
    
  2. In SplitPage.xaml.cpp, modify the event handling code that causes the WebView to update when the ListView selection changes. The ItemListView_SelectionChanged function signature and implementation are already there. We just have to add these lines at the end of the method:

    
    
        // Sometimes there is no selected item, e.g. when navigating back
        // from detail in logical page navigation.
        auto fi = dynamic_cast<FeedItem^>(itemListView->SelectedItem);
        if(fi != nullptr)
        {
            contentView->NavigateToString(fi->Content);
        }    
    
    
    

Hh465045.wedge(en-us,WIN.10).gifTo add a WebView control to DetailPage.xaml

  • In DetailPage.xaml, we have to bind the title text to the blog post title and add a WebView control to show the blog page. To do that, replace the Grid that contains the back button and page title with this Grid and WebView:

    
    <!-- Back button and page title -->
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <AppBarButton x:Name="backButton" Height="95" Margin="10,46,0,0"
                Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}" 
                AutomationProperties.Name="Back"
                AutomationProperties.AutomationId="BackButton"
                AutomationProperties.ItemType="Navigation Button"
                Icon="Back"/>
        <TextBlock x:Name="pageTitle" Text="{Binding Title}" 
                   Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                   IsHitTestVisible="false" TextWrapping="NoWrap" 
                   VerticalAlignment="Bottom" Margin="0,0,30,40"/>
    </Grid>
    <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" 
            Grid.Row="1" Margin="120,15,20,20">
        <WebView x:Name="contentView" />
    </Border>
    
    
    

Hh465045.wedge(en-us,WIN.10).gifTo implement LoadState and SaveState in the DetailPage class

  1. Add the following private data members to the DetailPage class in DetailPage.xaml.h:

    
    private:
            Platform::String^ m_itemTitle;
            Platform::String^ m_feedUri;
    
    
    
  2. Add the following using statement to DetailPage.xaml.cpp:

    e
    
    using namespace concurrency;
    
    
  3. In DetailPage.xaml.cpp, add code to the LoadState method to navigate to the blog post and set the DataContext of the page. As in SplitPage, we have to determine the previous state of the app. The updated method looks like this:

    
    
    void DetailPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
    	(void) sender;	// Unused parameter
    	(void) e;	// Unused parameter
    
        // Lookup the URL for the blog title that was either
        // (a) passed to us in this session or
        // (b) saved in the SaveState method when our app was suspended.
        m_itemTitle = safe_cast<String^>(e->NavigationParameter);
    
        // We are navigating forward from SplitPage
        if (e->PageState == nullptr)
        {
            auto feedData = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed"));
            m_feedUri = feedData->Uri;
    
            auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle);
            if (feedItem != nullptr)
            {
                DefaultViewModel->Insert("Title", m_itemTitle);
                // Display the web page.
                contentView->Navigate(feedItem->Link);
            }
        }
    
        // We are resuming from suspension:
        else
        {
            // We are resuming, and might not have our FeedData object yet
            // so must get it asynchronously and wait on the result.
            String^ uri = safe_cast<String^>(e->PageState->Lookup("FeedUri"));
            auto feedDataOp = FeedDataSource::GetFeedAsync(uri); //URL
            auto feedDataTask = create_task(feedDataOp);
    
            feedDataTask.then([this, e](FeedData^ feedData)
            {
                App::Current->Resources->Insert("CurrentFeed", feedData);
    
                m_feedUri = feedData->Uri;
                m_itemTitle = safe_cast<String^>(e->PageState->Lookup("Item"));
                auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle);
    
                if (feedItem != nullptr)
                {
                    DefaultViewModel->Insert("Title", m_itemTitle);
                    // Display the web page.
                    contentView->Navigate(feedItem->Link);
                }
            });
        }
    }
    
    
  4. In contrast to LoadState, SaveState has only two lines of code. As in SplitPage, we save the URI of the feed because we need it in LoadState to look up the FeedItem if the app is resumed from termination. In DetailPage.xaml.cpp, use the following code to replace the existing SaveState method:

    
    
    void DetailPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
    	   (void) sender;	// Unused parameter
        // Store the itemTitle in case we are suspended or terminated.
        e->PageState->Insert("Item", m_itemTitle);
        e->PageState->Insert("FeedUri", m_feedUri);
    }
    
    
    
  5. Press F5 to make sure that the app builds, and then return to Visual Studio and press Shift+F5 to stop running the build.

Part 5: Adding app bars

Much of the navigation in our blog reader app will probably occur as a user goes back and forth between the Items page and the Split page, and when he or she browses the offerings on those pages. But on the Split page, we must also provide a way for the user to access the detailed view of the blog post. We could put a button somewhere on the page, but that would distract from the core app experience of browsing and reading. Instead, let's put the button in an app bar that's hidden until the user wants it. In this section, we'll add an app bar that has a button to navigate to the Detail page.

An app bar is a piece of UI that's hidden by default, and is shown or dismissed when a user swipes from the edge of the screen or clicks the right mouse button. It can offer navigation UI, commands, and tools to the user. An app bar can appear at the top of the page, at the bottom of the page, or both. We recommend that you put navigation in the top app bar, and tools and commands in the bottom app bar.

To add an app bar in XAML, we assign an AppBar control to the TopAppBar or BottomAppBar property of a Page and then embed an AppBarButton element in the AppBar

Hh465045.wedge(en-us,WIN.10).gifTo add a button to the Split page app bar

  1. In SplitPage.xaml, paste the following markup after the closing Page.Resources tag to create a navigation bar that contains an AppBarButton that points forward:

    
    
    <Page.TopAppBar>
        <AppBar Padding="10,0,10,0">
            <Grid>
               <AppBarButton x:Name="fwdButton" Height="95" Margin="150,46,0,0"
                   Command="{Binding NavigationHelper.GoForwardCommand, ElementName=pageRoot}" 
                   AutomationProperties.Name="Forward"
                   AutomationProperties.AutomationId="ForwardButton"
                   AutomationProperties.ItemType="Navigation Button"
                   HorizontalAlignment="Right"
                   Icon="Forward"/>
             </Grid>
         </AppBar>
    </Page.TopAppBar>
    
    
  2. Now, after the Icon attribute, but before the />, type a space and then Click="fwdButton_Click". (You can just accept the auto-complete default.) Doing this creates the event handlers in the code-behind pages.

Hh465045.wedge(en-us,WIN.10).gifTo add navigation to the Detail page

  1. Add the following #include directive to SplitPage.xaml.cpp:

    
    #include "DetailPage.xaml.h"
    
    
    
  2. In SplitPage.xaml.cpp, add this code to the fwdButton_Click method body:

    
    // Navigate to the appropriate destination page, and configure the new page
    // by passing required information as a navigation parameter.
    
    auto selectedItem = dynamic_cast<FeedItem^>(this->itemListView->SelectedItem);
    
    // selectedItem will be nullptr if the user invokes the app bar
    // and clicks on "view web page" without selecting an item.
    if (this->Frame != nullptr && selectedItem != nullptr)
    {
        auto itemTitle = safe_cast<String^>(selectedItem->Title);
        this->Frame->Navigate(TypeName(DetailPage::typeid), itemTitle);
    }
    
    
    

At this point, the core app functionality is complete! Build it and then run it to test the navigation between pages. When you choose an item on the Items page, the app should navigate to the Split page. On the Split page, test the Back button to go back to the Items page, or swipe from the top of the screen (or right-click) to invoke the app bar and then tap or click its button to go to the Detail page. On the Detail page, use the Back button to return to the Split page. As you select different items on the Split page, a preview of the item should appear. Next, we'll improve the look of the UI, and add support for portrait orientation.

Part 6: Adding animations and transitions

When we talk about animations, we often think of objects bouncing around on the screen. But in XAML, an animation is just a way to change the value of a property on an object. This makes animations useful for a lot more than just bouncing things around. In our blog reader app, we adapt our UI to different layouts and orientations by using some built-in animations and orientation logic from the Windows.UI.Xaml.Media.Animation namespace.

Adding theme animations

A theme animation is a pre-configured animation—for example, the PopInThemeAnimation makes a view slide in from right to left when the page loads. Increasing the value of the FromHorizontalOffset property makes the effect more extreme. Here, we put a PopInThemeAnimation in a Storyboard and make it a resource in DetailPage.xaml. Then we set the target of the animation to be the Border that surrounds our web content. This animates the Border and everything in it.

Hh465045.wedge(en-us,WIN.10).gifTo add a theme animation to the Detail page

  1. Paste the following XAML snippet into the Page.Resources node in DetailPage.xaml:

    
    
    <Storyboard x:Name="PopInStoryboard">
        <PopInThemeAnimation  Storyboard.TargetName="contentViewBorder" FromHorizontalOffset="400"/>
    </Storyboard>
    
    
    
  2. Paste the following code into the beginning of the DetailPage::LoadState method in DetailPage.xaml.cpp. Our override of LoadState is called by the OnNavigatedTo method in the base class LayoutAwarePage:

    
    // Run the PopInThemeAnimation. 
    Windows::UI::Xaml::Media::Animation::Storyboard^ sb = dynamic_cast<Windows::UI::Xaml::Media::Animation::Storyboard^>(this->FindName("PopInStoryboard"));
    if (sb != nullptr)
    {
        sb->Begin();
    }    
    //... rest of method as before
    
    
    

Adding theme transitions

A theme transition is a set of animations and a Storyboard that are combined into a pre-packaged behavior that we can attach to a UI element. A ContentThemeTransition is used with a ContentControl, and is automatically triggered when the content of the control changes.

In our app, let's add a theme transition to the TextBlock that holds the post titles in the Split page list view. When the content of the TextBlock changes, the ContentThemeTransition is triggered and runs automatically. The animation is predefined, and so we don't have to do anything to run it. We just attach it to the TextBlock.

Hh465045.wedge(en-us,WIN.10).gifTo add a theme transition to SplitPage.xaml

  • In SplitPage.xaml, the TextBlock that's named pageTitle is an empty-element tag. Because we embed a theme transition in the TextBlock, we have to change the TextBlock so that it has opening and closing tags. Replace the existing tag with this markup:

    
    
    <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" Style="{StaticResource HeaderTextBlockStyle}"
        IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40">
        <TextBlock.Transitions>
            <TransitionCollection>
                <ContentThemeTransition />
             </TransitionCollection>
        </TextBlock.Transitions>
    </TextBlock>
    
    
    

    When the content of the TextBlock changes, the ContentThemeTransition is triggered and runs automatically. The animation is predefined, and we don't have to do anything to run it. We just attach it to the TextBlock. For more info, and a list of theme animations and transitions, see QuickStart: Animations.

Part 6: Using styles to create a consistent look

We want our users to have a seamless experience when they move between the Windows Team Blogs website and our blog reader app. The default dark theme of our new Windows UI doesn't match the Windows Team Blogs website very well. This is most evident on the Detail page, where we load the actual blog page into a WebView, as shown here:

Detail page with dark theme.

To give our app a consistent appearance that we can update as required, we use brushes and styles. A Brush lets us define a look in one place and use it wherever we need it. By using a Style, we can set values for the properties of a control and reuse those settings across our app.

Before we get into the details, let's look at how to use a brush to set the background color of the pages in our app. Each page in our app has a root Grid that has a Background property that defines the background color of the page. We could set each page background individually, like this: <Grid Background="Blue">. However, a better way is to define a Brush as a resource and use it to define the background color of all of our pages. That's how the Visual Studio templates do it: <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">.

That's fine for setting the Background property of the Grid, but you typically need to set more than one property to get the look you want. You can group settings for any number of properties together as a Style, and then apply the Style to the control.

Where a resource is defined determines the scope in which it can be used. You can define resources in an page-level XAML file, in the app-level App.xaml file, or in a separate resource-dictionary XAML file. (A resource-dictionary XAML file can be shared across apps, and more than one resource dictionary can be merged in one app.)

In our blog reader app, we define resources in App.xaml to make them available across our entire app. And we also have some resources defined in the XAML files for individual pages. If resources that have the same key are defined in both App.xaml and in a page, the resource in the page overrides the resource in App.xaml. In the same way, a resource defined in App.xaml will override a same-key resource defined in a separate resource-dictionary file. For more info, see Quickstart: Styling controls.

In a Style definition, we have to have a TargetType attribute and a collection of one or more Setter elements. We set the TargetType to a string that specifies the type that the Style is applied to, in this case, a Panel. If you try to apply a Style to a control that doesn't match the TargetType attribute, an exception occurs. Each Setter element requires a Property and aValue. These settings indicate which control property the setting applies to, and the value to set for that property.

To change the Background of our pages, we replace the ApplicationPageBackgroundThemeBrush with our own custom brush. For our custom brush, we use the Color #FF0A2562, which is a nice blue that fits with the colors on the Windows Team Blogs website. To replace the system theme brush, we create a Style that's based on LayoutRootStyle, and change the Background property there.

Hh465045.wedge(en-us,WIN.10).gifTo set the background color for all pages

  1. To define a new style for the layout root, paste these brush and style definitions into the <Application.Resources> node in App.xaml:

    
    
      <SolidColorBrush x:Key="WindowsBlogBackgroundBrush" Color="#FF0A2562"/>
    
      <Style x:Key="WindowsBlogLayoutRootStyle" TargetType="Panel">
          <Setter Property="Background" Value="{StaticResource WindowsBlogBackgroundBrush}"/>
      </Style>
    
    
  2. The root Grid element on each page of our app currently uses a Background attribute to set the page background color. Let's change that in ItemsPage.xaml, SplitPage.xaml, and DetailPage.xaml to reference WindowsBlogLayoutRootStyle, which we just added to App.xaml, so that every page uses the same background:

    
    
     <Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
    
    
  3. Press F5 to build and run the app, and examine the blue pages.

Control and data templates

To give our app more of the look and feel of the Windows Team Blogs website, we also use custom data templates in addition to Brushes and Styles.

Hh465045.wedge(en-us,WIN.10).gifTo add a control template for the date

  • In the <Application.Resources> node in App.xaml, add a ControlTemplate that defines a square block that shows the date. We define this in App.xaml so that we can reference it in both ItemsPage.xaml and SplitPage.xaml.

    
    
     <ControlTemplate x:Key="DateBlockTemplate">
        <Canvas Height="86" Width="86"  Margin="8,8,0,8" 
            HorizontalAlignment="Left" VerticalAlignment="Top">
             <TextBlock TextTrimming="WordEllipsis" TextWrapping="NoWrap" 
                 Width="Auto" Height="Auto" Margin="8,0,4,0" 
                 FontSize="32" FontWeight="Bold">
                 <TextBlock.Text>
                     <Binding Path="PubDate" 
                     Converter="{StaticResource dateConverter}"
                         ConverterParameter="month"/>
                 </TextBlock.Text>
             </TextBlock>
    
             <TextBlock TextTrimming="WordEllipsis" TextWrapping="Wrap" 
                 Width="40" Height="Auto" Margin="8,0,0,0"
                 FontSize="34" FontWeight="Bold" Canvas.Top="36">
                 <TextBlock.Text>
                     <Binding Path="PubDate" 
                         Converter="{StaticResource dateConverter}"
                         ConverterParameter="day"/>
                 </TextBlock.Text>
             </TextBlock>
             <Line Stroke="White" StrokeThickness="2" 
                 X1="54" Y1="46" X2="54" Y2="80"/>
    
             <TextBlock TextWrapping="Wrap" 
                 Width="20" Height="Auto" 
                 FontSize="{StaticResource ControlContentThemeFontSize}"
                 Canvas.Top="42" Canvas.Left="60">
                 <TextBlock.Text>
                     <Binding Path="PubDate"
                     Converter="{StaticResource dateConverter}"
                     ConverterParameter="year"/>
                 </TextBlock.Text>
             </TextBlock>
         </Canvas>
    </ControlTemplate>
    
    
    

    Notice that this ControlTemplate defines parameters—"day," "month," and "year"—that are passed to the Convert function that we created in the DateConverter earlier. Because we format the day, month and year as independent elements, each with a different font size, we had to write new functionality in the Convert method to return the parts of the date separately.

Hh465045.wedge(en-us,WIN.10).gifTo add data templates to the Items page

  1. In ItemsPage.xaml, we add these elements to the <Page.Resources> node to define the look of the grid items.

    
    
    
      <!-- light blue -->
            <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF557EB9"/>
    
            <!-- Grid Styles -->
            <Style x:Key="GridTitleTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
                <Setter Property="FontSize" Value="26.667"/>
                <Setter Property="Margin" Value="12,0,12,2"/>
            </Style>
    
            <Style x:Key="GridDescriptionTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
                <Setter Property="VerticalAlignment" Value="Bottom"/>
                <Setter Property="Margin" Value="12,0,12,60"/>
            </Style>
    
            <DataTemplate x:Key="DefaultGridItemTemplate">
                <Grid HorizontalAlignment="Left" Width="250" Height="250"
                    Background="{StaticResource BlockBackgroundBrush}" >
                    <StackPanel>
                        <TextBlock Text="{Binding Title}" Style="{StaticResource GridTitleTextStyle}" Margin="5,5,5,5" />
                        <TextBlock Text="{Binding Description}" Style="{StaticResource GridDescriptionTextStyle}" Margin="5,5,5,5" />                    
                    </StackPanel>
                    <StackPanel VerticalAlignment="Bottom" Orientation="Horizontal"
                            Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                        <TextBlock Text="Last Updated" Margin="12,4,0,8" Height="42"/>
                        <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}" Margin="12,4,12,8" Height="42"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
    
        
    
    
    
  2. Also in ItemsPage.xaml, update the GridView element that's named itemGridView so that it refers to the DefaultGridItemTemplate resource we just added and doesn't use the default inline template. Replace the entire GridView element (from opening tag through closing tag) with this one, which consists of just a single self-closing tag:

    
    
     <!-- Horizontal scrolling grid -->
            <GridView
                x:Name="itemGridView"
                AutomationProperties.AutomationId="ItemsGridView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.RowSpan="2"
                Padding="116,136,116,46"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                SelectionMode="None"
                ItemTemplate="{StaticResource DefaultGridItemTemplate}"
                IsItemClickEnabled="true"
                IsSwipeEnabled="false"
                ItemClick="itemGridView_ItemClick">
            </GridView>
    
    
    

    Note the reference to the reference to our new data template in this line: ItemTemplate="{StaticResource DefaultGridItemTemplate}".

Hh465045.wedge(en-us,WIN.10).gifTo add data templates to the Split page

  1. In SplitPage.xaml, add the following elements to the <Page.Resources> node to help define the look of the list items. Notice that there are two data templates, one for landscape mode and one for portrait mode:

    
    
    <!-- Green    -->
    <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF6BBD46"/>
    
    <DataTemplate x:Name="LandscapeItemTemplate">
        <Grid HorizontalAlignment="Stretch" Width="Auto" Height="110" Margin="10,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <!-- Green date block -->
            <Border Background="{StaticResource BlockBackgroundBrush}" Width="110" Height="110" />
            <ContentControl Template="{StaticResource DateBlockTemplate}" />
            <StackPanel Grid.Column="1"  HorizontalAlignment="Left"  Margin="12,8,0,0">
                <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap" MaxHeight="72" Foreground="#FFFE5815" />
                    <TextBlock Text="{Binding Author}" FontSize="18.667" />
            </StackPanel>
        </Grid>
    </DataTemplate>
    
    <DataTemplate x:Name="PortraitItemTemplate">
        <Grid HorizontalAlignment="Stretch" Width="Auto" Height="110" Margin="10,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
             </Grid.ColumnDefinitions>
             <!-- Green date block -->
             <Border Background="{StaticResource BlockBackgroundBrush}" Width="110" Height="110" />
             <ContentControl Template="{StaticResource DateBlockTemplate}" />
             <StackPanel Grid.Column="1"  HorizontalAlignment="Left"  Margin="12,8,0,0">
                 <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap" MaxHeight="72" Foreground="#FFFE5815" />
                 <TextBlock Text="{Binding Author}" FontSize="18.667" />
             </StackPanel>
         </Grid>
    </DataTemplate>
    
    
    
  2. Next in SplitPage.xaml, we have to update the ItemTemplate property in the ListView element named itemListView so that it uses one of two custom data templates, depending on device orientation (either landscape mode or portrait mode). Here we specify the default template in XAML, and later we'll learn how to switch to the portrait mode template by using VisualState XAML elements and some code-behind to detect the orientation change.

    In SplitPage.xaml, replace the existing ListView element with this one:

    
    
             <!-- Vertical scrolling item list -->
            <ListView
                x:Name="itemListView"
                AutomationProperties.AutomationId="ItemsListView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.Row="1"
                Margin="-10,-10,0,0"
                Padding="120,0,0,60"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"
                SelectionChanged="ItemListView_SelectionChanged"
                ItemTemplate="{StaticResource LandscapeItemTemplate}">
    
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="0,0,0,10"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
    
    

With our styles applied, our app fits with the look and feel of the Windows Team Blogs website:

Items page with styles applied

Split page with styles applied

Detail page with styles applied

By using styles and basing them on other styles, we can quickly define and apply different looks for our app. In the next section, we combine what we know about animations and styles to make our app fluidly adapt to various layouts and orientations while it's running.

Part 7: Adapting to different layouts

The Visual Studio templates for Windows Store apps do not include code that handles changes to the display orientation. We have to write that code ourselves.

To use XAML to transition between different views, we use the VisualStateManger to define VisualStateGroups for each page. Each VisualStateGroup contains one or more VisualState definitions. At run time, in the SizeChanged event handler, you call VisualStateManager::GoToState to cause the page to use the specified VisualState to render the UI. In this app, the Items page and the Split page each use two VisualStateGroups, one for landscape orientation, which is the default, and one for portrait orientation. A VisualState is essentially a set of transforms on XAML elements. Each VisualState has animations that specify which page elements to change.

In the Creating a consistent look with styles section of this tutorial, we created styles and templates to give our app a custom look. The default landscape view uses these styles and templates. To keep the custom look in portrait mode, we also have to create some custom styles and templates for that view.

Hh465045.wedge(en-us,WIN.10).gifTo adapt to portrait mode in ItemsPage.xaml

  1. Here we have to make a design decision. We use a GridView to display items in landscape mode. A GridView is designed to scroll horizontally. If we want the items to scroll vertically in portrait mode, we would have to use a ListView. However, if we do that, the app has to load two separate controls at run time, which can increase loading times and memory footprint. (Only by testing can we know whether the cost of having an extra control is significant.) In this app, to keep things simple, let's just use the same GridView for both orientations. We'll still have horizontal scrolling in portrait orientation, but we can make the items a bit narrower so that they're more proportionate to the portrait rectangle. When we do this, we also have to adjust some font sizes to avoid excessive text wrapping in the titles. To make this set of changes, we define a VisualState and populate it with animations to modify each specific element.

    In ItemsPage.xaml, add the following style to the <Page.Resources> node. It will give the item titles a smaller font so that they fit better in the narrower rectangle. We consume it from the data template that we'll add in the next step.

    
    <Style x:Key="GridTitlePortraitTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
        <Setter Property="FontSize" Value="20"/>
        <Setter Property="Margin" Value="12,0,12,2"/>
    </Style>
    
    
  2. Now add this DataTemplate to the <Page.Resources> node to define the template that the GridView will use in portrait orientation:

    
    <DataTemplate x:Key="PortraitGridItemTemplate">
        <Grid HorizontalAlignment="Left" Width="160" Height="250"
        Background="{StaticResource BlockBackgroundBrush}">
            <StackPanel>
                <TextBlock Text="{Binding Title}" Style="{StaticResource GridTitlePortraitTextStyle}" Margin="5,5,5,5"/>
                <TextBlock Text="{Binding Description}" Style="{StaticResource GridDescriptionTextStyle}" Margin="5,5,5,5" TextTrimming="CharacterEllipsis"/>
            </StackPanel>
            <StackPanel VerticalAlignment="Bottom" 
                Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                <TextBlock Text="Last Updated:"/>
                <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}"/>
            </StackPanel>
        </Grid>
    </DataTemplate> 
    
    
  3. Now let's tell the page about the new data template. This is where VisualState comes in. Still in ItemsPage.xaml, add this code just above the closing tag for the root Grid element:

    
     <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="DefaultLayout"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="ItemTemplate">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitGridItemTemplate}"/>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Padding">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="40,136,116,40"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    

    Let's take a moment to look at this XAML. We've added a VisualStateManager.VisualStateGroups element, and nested a group in it—ViewStates. That group contains two states, one named "DefaultLayout" for landscape mode and one named "Portrait" for portrait mode. Notice that "DefaultLayout" is empty—that means that it is identical to the XAML for the page. When the device is in landscape orientation and the page receives a SizeChanged event, it will use the page XAML to render itself. When the device is in portrait orientation, the page will load the default XAML and then make the changes specified by the animations. The GridView will use a different item template, and we'll also adjust the padding slightly to better fit the vertical dimensions of the portrait orientation.

  4. Now add a SizeChanged event to the root Page element by typing SizeChanged as the last attribute in the element. As you start to type, the UI will offer to complete the statement and help you to create an event handler. If you accept the UI suggestions, you won't have to add the method signature and implementation manually. To create the event handler, start typing pageRoot_SizeChanged and then accept the UI suggestion when it matches that event name.
  5. Now we have to add the code-behind to invoke the appropriate VisualState when the device orientation changes. In ItemsPage.xaml.cpp, add this code to the empty pageRoot_SizeChanged method:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "Portrait", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "DefaultLayout", true);
    }
    
    

Hh465045.wedge(en-us,WIN.10).gifTo adapt to portrait mode in SplitPage.xaml

  1. In portrait mode, the Split page treats each of its two columns as a logical page. When a user is looking at the list, it occupies the entire screen and the detail column is collapsed. When the user chooses an item, the list column collapses and the detail column occupies the screen. Therefore, SplitPage.xaml requires two VisualStates for portrait mode—SinglePane shows just the list side, and SinglePane_Detail shows the detail side. For each animation, look at the target, the target property, and the value that's assigned to that property. These VisualStates are included in SplitPage.xaml by default, and we won't change them in this app. Also, look at the #pragma region Logical page navigation to see how the page logic determines which VisualState to invoke in portrait mode.

    We are going to make one change to that code so that SplitPage behaves as expected even when the screen width is 768 pixels. The UsingLogicalPageNavigation member function will return false if the MinimumWidthForSupportingTwoPanes is defined as 768. We want logical page navigation to be invoked even at that exact resolution, therefore we must either change the < operator in the function to <= as shown in the following snippet, or else change the definition of MinimumWidthForSupportingTwoPanes to 769 in SplitPage.xaml.h.

    
    bool SplitPage::UsingLogicalPageNavigation()
    {
    	return Windows::UI::Xaml::Window::Current->Bounds.Width <= MinimumWidthForSupportingTwoPanes;
    }
    
    
  2. Oddly, SplitPage.xaml does not specify a SizeChanged event handler by default, so we must do that now. In the root Page element opening tag, start typing SizeChanged and then use auto-complete to finish the statement and stub out the code-behind. Then, in SplitPage.xaml.cpp, add this code to the empty method stub:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "SinglePane", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "PrimaryView", true);
    }
    
    

Hh465045.wedge(en-us,WIN.10).gifTo adjust the WebView margin in portrait mode

  1. In DetailPage.xaml, we just have to adjust the margin of our WebView in portrait mode so that it uses all of the available space. To do this, we add two VisualStates—and in the "Portrait" state, we add an animation to change the value of the Margin property on contentViewBorder. Paste the following element just before the last closing Grid tag:

    
    
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="Landscape"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentViewBorder" Storyboard.TargetProperty="Margin">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="20,5,20,20"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    
    
  2. Still in DetailPage.xaml, add a SizeChanged event to the root Page element. As before, start typing SizeChanged="pageRoot_SizeChanged" and then use auto-complete to stub out the code-behind.
  3. In DetailPage.xaml.cpp, add this code to the empty new pageRoot_SizeChanged method:
    
    if (e->NewSize.Height / e->NewSize.Width >= 1)
    {
        VisualStateManager::GoToState(this, "Portrait", true);
    }
    else
    {
        VisualStateManager::GoToState(this, "Landscape", true);
    }
    
    

Going further: Adding a splash screen

The first impression an app makes comes from the splash screen that's shown when a user starts the app. As the app initializes its resources, the splash screen assures that user that the app is working an then disappears when the first page of the app is ready to replace it.

A splash screen consists of a background color and an image. In our app, we've just used the default background and image—SplashScreen.png—that are supplied by the project template. But you could change the color and image if you want to by using the Application UI tab in the Manifest Editor to modify settings in Package.appxmanifest.

Here's just one idea for a splash screen for our blog reader:

Splash screen image.

You could also extend the splash by using the properties and methods of the SplashScreen class—for example, by getting the coordinates of the splash screen and using them to place the first page of the app. And you could find out when the splash screen is dismissed so that you know it's time to start any content-entrance animations for the app.

What's next

This tutorial how to use built-in page templates from Microsoft Visual Studio Express 2012 for Windows 8 to build a multi-page app, and how to navigate and pass data between the pages. We learned how to use styles and templates to make our app fit the personality of the Windows Team Blogs website. We also learned how to use theme animations and an app bar to make our app fit the personality of a Windows Store app. Finally, we learned how to adapt our app to various layouts and orientations so that it always looks its best.

Our app is almost ready to submit to the Windows Store. For more info about how to submit an app to the Windows Store, see:

Related topics

Roadmap for Windows Store apps using C++
Develop Windows Store apps using Visual Studio 11

 

 

Show:
© 2014 Microsoft. All rights reserved.