Create a blog reader Universal Windows Platform app (C++)

Start to finish, here's how to use C++ and XAML to develop a Universal Windows Platform (UWP) app that you can deploy to Windows 10. The app reads blogs from RSS 2.0 or Atom 1.0 feeds.

This tutorial assumes that you're already familiar with the concepts in 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.

For this tutorial we’ll use Visual Studio Community 2015 or later. If you are using another edition of Visual Studio, the menu commands might be slightly different.

For tutorials in other programming languages, 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:

  • Access RSS and Atom feed data over the internet.
  • Display a list of feeds and feed titles.
  • Provide two ways to read a post, as simple text or as a web page .
  • Support Process Lifetime Management (PLM) and correctly saves and reloads its state if the system shuts it down while another task was in the foreground.
  • Adapt to different window sizes and device orientations (landscape or portrait).
  • Enable a user to add and remove feeds.

Part 1: Set up the project

To start, let's use the C++ Blank App (Universal Windows) template to create a project.

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

  • In Visual Studio, choose File > New > Project, select Installed > Visual C++ > Windows > Universal. , In the middle pane, choose and then select the Blank App (Universal Windows) template. Name the solution “SimpleBlogReader.” For more complete instructions, see Create a "Hello, world" app (C++).

Let’s begin by adding all the pages. It's easier to do them all at once this way because as we start coding each page has to #include the page it navigates to.

Hh465045.wedge(en-us,WIN.10).gifAdd the Windows app pages

  1. Actually, we begin with destruction. Right click on MainPage.xaml and then choose Remove and then click on Delete to permanently delete the file and its code-behind files. This is a blank page type that lacks the navigation support we require. Now right click on the project node and choose Add > New Item. Add new item in Visual C++
  2. In the left pane, choose XAML and in the middle pane choose Items Page. Call it MainPage.xaml and click OK. You’ll see a message box that asks if it’s OK to add some new files to the project. Click Yes. In our startup code we need to reference the SuspensionManager and NavigationHelper classes that are defined in those files, which Visual Studio puts in a new Common folder.
  3. Add a SplitPage and accept the default name.
  4. Add a BasicPage and call it WebViewerPage.

We’ll add the user interface elements to those pages later.

Hh465045.wedge(en-us,WIN.10).gifAdd the Phone app pages

  1. In Solution Explorer, expand the Windows Phone 8.1 project. Right-click on MainPage.xaml, choose Remove > Permanently Delete,
  2. Add a new XAML Basic Page and call it MainPage.xaml. Click Yes just as you did for the Windows project.
  3. You might notice that the variety of page templates is more limited in the phone project; we use only basic pages in this app. Add three more basic pages and call them FeedPage, TextViewerPage and WebViewerPage.

Part 2: Create a data model

Store apps based on Visual Studio templates loosely embody a MVVM architecture. In our app, the model is comprised of classes that encapsulate blog feeds. Each XAML page in the app represents a particular view of that data, and each page class has its own view model which is a property called DefaultViewModel and is of type Map<String^,Object^>. This map stores the data that the XAML controls on the page bind to, and it serves as the data context for the page.

Our model consists of three classes. The FeedData class represents the top-level URI and metadata for a blog feed. The feed at https://blogs.windows.com/windows/b/buildingapps/rss.aspx is an example of what a FeedData encapsulates. A feed has a list of blog posts, which we represent as FeedItem objects. Each FeedItem represents one post, and contains the title, content, Uri and other metadata. The post at https://blogs.windows.com/windows/b/buildingapps/archive/2014/05/28/using-the-windows-phone-emulator-for-testing-apps-with-geofencing.aspx is an example of a FeedItem. The first page in our app is a view of the Feeds, the second page is a view of FeedItems for a single feed, and the last two pages provide different views of a single post: as plain text or as a web page.

The FeedDataSource class contains a collection of FeedData items along with methods for downloading them.

To recap:

  • 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 data-binding; the XAML controls cannot interact with standard C++ classes. 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 Windows::Foundation::Collections::IVector is used to expose a public collection type. We use the Platform::Collections::Vector class internally as the concrete type that implements IVector.

Both the Windows and the Windows Phone projects will use the same data model, so we’ll put the classes in the shared project.

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

  1. In Solution Explorer, on the shortcut menu for the SimpleBlogReader.Shared project node, choose Add > New Item. Select the Header File (.h) option and name it FeedData.h.

  2. Open FeedData.h and then paste the following code into it. Notice the #include directive for "pch.h"—that's our precompiled header and it's where to put system headers that don't change much or at all. By default, pch.h includes collection.h, which is required for the Platform::Collections::Vector type, and ppltasks.h, which is required for concurrency::task and related types. These headers include both <string> and <vector> which our app needs, so we don't have to explicitly include them.

    //feeddata.h
    
    #pragma once
    #include "pch.h"
    
    namespace SimpleBlogReader
    {
    
        namespace WFC = Windows::Foundation::Collections;
        namespace WF = Windows::Foundation;
        namespace WUIXD = Windows::UI::Xaml::Documents;
        namespace WWS = Windows::Web::Syndication;
    
    
        /// <summary>
        /// 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.
        /// </summary>
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedItem sealed
        {
        public:
            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){}
        };
    
        /// <summary>
        /// A FeedData object represents a feed that contains 
        /// one or more FeedItems. 
        /// </summary>
        [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 WFC::IVector<FeedItem^>^ Items
            {
                WFC::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;
        };
    }
    

    The classes are ref classes because the Windows Runtime XAML classes need to interact with them to data-bind to the user interface. The [Bindable] attribute on those classes is also required for databinding. The binding mechanism won't see them without that attribute.

Part 3: Download the data

The FeedDataSource class contains the methods that download the feeds, and also has some other helper methods. It also contains the collection of the downloaded feeds that gets added to the "Items" value in the DefaultViewModel of the main app page. FeedDataSource uses the Windows::Web::Syndication::SyndicationClient class to do the downloading. Because network operations can take time, these operations are asynchronous. When a feed download is completed, the FeedData object is initialized and added to the FeedDataSource::Feeds collection. This is an IObservable<T> which means that the UI will be notified when an item is added, and will display it in the main page. For async operations we use the concurrency::task class and related classes and methods from ppltasks.h. The create_task function is used to wrap IAsyncOperation and IAsyncAction function calls in the Windows API. The task::then member function is used to execute code that must wait until after the task completes.

A nice feature of the app is that the user doesn’t have to wait for all the feeds to download. They can click on a feed as soon as it appears, and go to a new page that displays all the items for that feed. That’s an example of a “fast and fluid” user interface that is made possible by doing a lot of work on background threads. We’ll see it in action after we add the main XAML page.

However, asynchronous operations do add complexity—"fast and fluid" is not "free". If you have read the earlier tutorials, you know that an app that's not currently active might be terminated by the system in order to free up memory, and then restored when the user switches back to it. In our app, we don't save all the feed data when we shut down, because that would take a lot of storage and might mean we end up with stale data. We always download the feeds whenever we start up. But that means we have to account for the scenario in which the app resumes from termination and immediately tries to display a FeedData object that hasn’t finished downloading yet. We need to ensure that we don't try to display data until it is available. In this case we can't use the then method, but we can use a task_completed_event. This event will prevent any code from trying to access a FeedData object until that object is finished loading.

Hh465045.wedge(en-us,WIN.10).gif

  1. Add the FeedDataSource class to FeedData.h., as part of the namespace SimpleBlogReader:

        /// <summary>
        /// A FeedDataSource represents a collection of FeedData objects
        /// and provides the methods to retrieve the stores URLs and 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" /> 
        /// </summary>
        [Windows::UI::Xaml::Data::Bindable]
        public ref class FeedDataSource sealed
        {
        private:
            Platform::Collections::Vector<FeedData^>^ m_feeds;
            FeedData^ GetFeedData(Platform::String^ feedUri, WWS::SyndicationFeed^ feed);
            concurrency::task<WFC::IVector<Platform::String^>^> GetUserURLsAsync();
            void DeleteBadFeedHandler(Windows::UI::Popups::UICommand^ command);
    
        public:
            FeedDataSource();
            property Windows::Foundation::Collections::IObservableVector<FeedData^>^ Feeds
            {
                Windows::Foundation::Collections::IObservableVector<FeedData^>^ get()
                {
                    return this->m_feeds;
                }
            }
            property Platform::String^ CurrentFeedUri;
            void InitDataSource();        
    
        internal:
            // This is used to prevent SplitPage from prematurely loading the last viewed page on resume.
            concurrency::task_completion_event<FeedData^> m_LastViewedFeedEvent;
            concurrency::task<void> RetrieveFeedAndInitData(Platform::String^ url, WWS::SyndicationClient^ client);
        };
    
  2. Now create a file called FeedData.cpp in the shared project and paste in this code:

    #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::Foundation::Collections;
    using namespace Windows::Web::Syndication;
    using namespace Windows::Storage;
    using namespace Windows::Storage::Streams;
    
    FeedDataSource::FeedDataSource()
    {
           m_feeds = ref new Vector<FeedData^>();
           CurrentFeedUri = "";
    }
    
    ///<summary>
    /// Uses SyndicationClient to get the top-level feed object, then initializes 
    /// the app's data structures. In the case of a bad feed URL, the exception is 
    /// caught and the user can permanently delete the feed.
    ///</summary>
    task<void> FeedDataSource::RetrieveFeedAndInitData(String^ url, SyndicationClient^ client)
    {
           // Create the async operation. feedOp is an 
           // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^
           auto feedUri = ref new Uri(url);
           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 pass to the continuation. The continuation can run
           // on any thread.
           return create_task(feedOp).then([this, url](SyndicationFeed^ feed) -> FeedData^
           {
                  return GetFeedData(url, feed);
           }, concurrency::task_continuation_context::use_arbitrary())
    
                  // Append the initialized FeedData object to the items collection.
                  // This has to happen on the UI thread. By default, a .then
                  // continuation runs in the same apartment that it was called on.
                  // We can append safely to the Vector from multiple threads
                  // without taking an explicit lock.
                  .then([this, url](FeedData^ fd)
           {
                  if (fd->Uri == CurrentFeedUri)
                  {
                         // By setting the event we tell the resuming SplitPage the data
                         // is ready to be consumed.
                         m_LastViewedFeedEvent.set(fd);
                  }
    
                  m_feeds->Append(fd);
    
           })
    
                  // The last continuation serves as an error handler.
                  // get() will surface any unhandled exceptions in this task chain.
                  .then([this, url](task<void> t)
           {
                  try
                  {
                         t.get();
                  }
    
                  catch (Platform::Exception^ e)
                  {
                         // Sometimes a feed URL changes(I'm talking to you, Windows blogs!)
                         // When that happens, or when the users pastes in an invalid URL or a 
                         // URL is valid but the content is malformed somehow, an exception is 
                         // thrown in the task chain before the feed is added to the Feeds 
                         // collection. The only recourse is to stop trying to read the feed.
                         // That means deleting it from the feeds.txt file in local settings.
                         SyndicationErrorStatus status = SyndicationError::GetStatus(e->HResult);
                         String^ msgString;
    
                         // Define the action that will occur when the user presses the popup button.
                         auto handler = ref new Windows::UI::Popups::UICommandInvokedHandler(
                               [this, url](Windows::UI::Popups::IUICommand^ command)
                         {
                               auto app = safe_cast<App^>(App::Current);
                               app->DeleteUrlFromFeedFile(url);
                         });
    
                         // Display a message that hopefully is helpful.
                         if (status == SyndicationErrorStatus::InvalidXml)
                         {
                               msgString = "There seems to be a problem with the formatting in this feed: ";
                         }
    
                         if (status == SyndicationErrorStatus::Unknown)
                         {
                               msgString = "I can't load this feed (is the URL correct?): ";
                         }
    
                         // Show the popup.
                         auto msg = ref new Windows::UI::Popups::MessageDialog(
                               msgString + url);
                         auto cmd = ref new Windows::UI::Popups::UICommand(
                               ref new String(L"Forget this feed."), handler, 1);
                         msg->Commands->Append(cmd);
                         msg->ShowAsync();
                  }
           }); //end task chain
    }
    
    ///<summary>
    /// Retrieve the data for each atom or rss feed and put it into our custom data structures.
    ///</summary>
    void FeedDataSource::InitDataSource()
    {
           // Hard code some feeds for now. Later in the tutorial we'll improve this.
           auto urls = ref new Vector<String^>();
           urls->Append(L"http://sxp.microsoft.com/feeds/3.0/devblogs");
           urls->Append(L"https://blogs.windows.com/windows/b/bloggingwindows/rss.aspx");
           urls->Append(L"https://azure.microsoft.com/blog/feed");
    
           // Populate the list of feeds.
           SyndicationClient^ client = ref new SyndicationClient();
           for (auto url : urls)
           {
                  RetrieveFeedAndInitData(url, client);
           }
    }
    
    ///<summary>
    /// Creates our app-specific representation of a FeedData.
    ///</summary>
    FeedData^ FeedDataSource::GetFeedData(String^ feedUri, SyndicationFeed^ feed)
    {
           FeedData^ feedData = ref new FeedData();
    
           // Store the Uri now in order to map completion_events 
           // when resuming from termination.
           feedData->Uri = feedUri;
    
           // Get the title of the feed (not the individual posts).
           // auto app = safe_cast<App^>(App::Current);
           TextHelper^ helper = ref new TextHelper();
    
           feedData->Title = helper->UnescapeText(feed->Title->Text);
           if (feed->Subtitle != nullptr)
           {
                  feedData->Description = helper->UnescapeText(feed->Subtitle->Text);
           }
    
           // Occasionally a feed might have no posts, so we guard against that here.
           if (feed->Items->Size > 0)
           {
                  // Use the date of the latest post as the last updated date.
                  feedData->PubDate = feed->Items->GetAt(0)->PublishedDate;
    
                  for (auto item : feed->Items)
                  {
                         FeedItem^ feedItem;
                         feedItem = ref new FeedItem();
                         feedItem->Title = helper->UnescapeText(item->Title->Text);
                         feedItem->PubDate = item->PublishedDate;
    
                         //Only get first author in case of multiple entries.
                         item->Authors->Size > 0 ? feedItem->Author =
                               item->Authors->GetAt(0)->Name : feedItem->Author = L"";
    
                         if (feed->SourceFormat == SyndicationFormat::Atom10)
                         {
                               // Sometimes a post has only the link to the web page
                               if (item->Content != nullptr)
                               {
                                      feedItem->Content = helper->UnescapeText(item->Content->Text);
                               }
                               feedItem->Link = ref new Uri(item->Id);
                         }
                         else
                         {
                               feedItem->Content = item->Summary->Text;
                               feedItem->Link = item->Links->GetAt(0)->Uri;
                         }
                         feedData->Items->Append(feedItem);
                  };
           }
           else
           {
                  feedData->Description = "NO ITEMS AVAILABLE." + feedData->Description;
           }
    
           return feedData;
    
    } //end GetFeedData
    
  3. Now let's get a FeedDataSource instance into our app. In app.xaml.h, add an #include directive for FeedData.h to make the types visible.

        #include "FeedData.h"
    
    • In the Shared project, in App.xaml, add an Application.Resources node and in it put a reference to FeedDataSource so that the page now looks like this:

          <Application
              x:Class="SimpleBlogReader.App"
              xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="using:SimpleBlogReader">
      
              <Application.Resources>
                  <local:FeedDataSource x:Key="feedDataSource" />    
              </Application.Resources>
      </Application>
      

      This markup will cause a FeedDataSource object to be created when the app is started, and the object can be accessed from any page in the app. When the OnLaunched event is raised, the App object will call InitDataSource to cause the feedDataSource instance to start downloading all its data.

      The project won't build yet because we need to add some additional class definitions.

Part 4: Handle data synchronization on resume from termination

When the app first starts up, and while the user is navigating back and forth between pages, no data access synchronization is required. The feeds only appear in the first page after they are initialized, and the other pages never attempt to access the data until the user has clicked on a visible feed. And after that all access is read-only; we never modify our source data. However, there is one scenario that requires synchronization: when the app is terminated while a page based on a particular feed is active, then that page will need to re-bind to that feed data when the app is resumed. In this case it's possible for a page to attempt to access data that doesn't exist yet. Therefore we need a way to force the page to wait for the data to be ready.

The following functions enable the app to remember which feed it was looking at. The SetCurrentFeed method just persists the feed to the local settings where it can be retrieved even after the app goes out of memory. The GetCurrentFeedAsync method is the interesting one, because we have to ensure that when we come back and want to re-load the last feed, we don't try to do so before that feed has been reloaded. We'll talk about this code more later. We'll add the code to the App class because we'll be calling it from both the Windows app and the phone app.

  1. In app.xaml.h add these method signatures. The internal accessibility means they can be consumed only from other C++ code in the same namespace.

        internal:
        concurrency::task<FeedData^> GetCurrentFeedAsync();
        void SetCurrentFeed(FeedData^ feed); 
        FeedItem^ GetFeedItem(FeedData^ fd, Platform::String^ uri);
        void AddFeed(Platform::String^ feedUri);
        void RemoveFeeds(Platform::Collections::Vector<FeedData^>^ feedsToDelete);
        void DeleteUrlFromFeedFile(Platform::String^ s);
    
  2. Then in app.xaml.cpp, add the following using statements at the top:

        using namespace concurrency;
        using namespace Platform::Collections;
        using namespace Windows::Storage;
    

    You need the concurrency namespace for task, the Platform::Collections namespace for Vector, and the Windows::Storage namespace for ApplicationData.

    And add these lines to the bottom :

    ///<summary>
    /// Grabs the URI that the user entered, then inserts it into the in-memory list
    /// and retrieves the data. Then adds the new feed to the data file so it's 
    /// there the next time the app starts up.
    ///</summary>
    void App::AddFeed(String^ feedUri)
    {
        auto feedDataSource =
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        auto client = ref new Windows::Web::Syndication::SyndicationClient();
    
        // The UI is data-bound to the items collection and will update automatically
        // after we append to the collection.
        create_task(feedDataSource->RetrieveFeedAndInitData(feedUri, client))
            .then([this, feedUri] {
    
            // Add the uri to the roaming data. The API requires an IIterable so we have to 
            // put the uri in a Vector.
            Vector<String^>^ vec = ref new Vector<String^>();
            vec->Append(feedUri);
            concurrency::create_task(ApplicationData::Current->LocalFolder->
                CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
                .then([vec](StorageFile^ file)
            {
                FileIO::AppendLinesAsync(file, vec);
            });
        });
    }
    
    /// <summary>
    /// Called when the user chooses to remove some feeds which otherwise
    /// are valid Urls and currently are displaying in the UI, and are stored in 
    /// the Feeds collection as well as in the feeds.txt file.
    /// </summary>
    void App::RemoveFeeds(Vector<FeedData^>^ feedsToDelete)
    {
        // Create a new list of feeds, excluding the ones the user selected.
        auto feedDataSource =
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));  
    
        // If we delete the "last viewed feed" we need to also remove the reference to it
        // from local settings.
        ApplicationDataContainer^ localSettings = ApplicationData::Current->LocalSettings;
        String^ lastViewed;
    
        if (localSettings->Values->HasKey("LastViewedFeed"))
        {
            lastViewed =
                safe_cast<String^>(localSettings->Values->Lookup("LastViewedFeed"));
        }
    
        // When performance is an issue, consider using Vector::ReplaceAll
        for (const auto& item : feedsToDelete)
        {
            unsigned int index = -1;
            bool b = feedDataSource->Feeds->IndexOf(item, &index);
            if (index >= 0)
            {
                feedDataSource->Feeds->RemoveAt(index);           
            }
    
            // Prevent ourself from trying later to reference 
            // the page we just deleted.
            if (lastViewed != nullptr && lastViewed == item->Title)
            {
                localSettings->Values->Remove("LastViewedFeed");
            }
        }
    
        // Re-initialize feeds.txt with the new list of URLs.
        Vector<String^>^ newFeedList = ref new Vector<String^>();
        for (const auto& item : feedDataSource->Feeds)
        {
            newFeedList->Append(item->Uri);
        }
    
        // Overwrite the old data file with the new list.
        create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([newFeedList](StorageFile^ file)
        {
            FileIO::WriteLinesAsync(file, newFeedList);
        });
    }
    
    
    ///<summary>
    /// This function enables the user to back out after
    /// entering a bad url in the "Add Feed" text box, for example pasting in a 
    /// partial address. This function will also be called if a URL that was previously 
    /// formatted correctly one day starts returning malformed XML when we try to load it.
    /// In either case, the FeedData was not added to the Feeds collection, and so 
    /// we only need to delete the URL from the data file.
    /// </summary>
    void App::DeleteUrlFromFeedFile(Platform::String^ s)
    {
        // Overwrite the old data file with the new list.
        create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([this](StorageFile^ file)
        {
            return FileIO::ReadLinesAsync(file);
        }).then([this, s](IVector<String^>^ lines)
        {
            for (unsigned int i = 0; i < lines->Size; ++i)
            {
                if (lines->GetAt(i) == s)
                {
                    lines->RemoveAt(i);
                }
            }
            return lines;
        }).then([this](IVector<String^>^ lines)
        {
            create_task(ApplicationData::Current->LocalFolder->
                CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
                .then([this, lines](StorageFile^ file)
            {
                FileIO::WriteLinesAsync(file, lines);
            });
        });
    }
    
    ///<summary>
    /// Returns the feed that the user last selected from MainPage.
    ///<summary>
    task<FeedData^> App::GetCurrentFeedAsync()
    {
        FeedDataSource^ feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        return create_task(feedDataSource->m_LastViewedFeedEvent);
    }
    
    ///<summary>
    /// So that we can always get the current feed in the same way, we call this 
    // method from ItemsPage when we change the current feed. This way the caller 
    // doesn't care whether we're resuming from termination or new navigating.
    // The only other place we set the event is in InitDataSource in FeedData.cpp 
    // when resuming from termination.
    ///</summary>
    
    void App::SetCurrentFeed(FeedData^ feed)
    {
        // Enable any pages waiting on the FeedData to continue
        FeedDataSource^ feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        feedDataSource->m_LastViewedFeedEvent = task_completion_event<FeedData^>();
        feedDataSource->m_LastViewedFeedEvent.set(feed);
    
        // Store the current URI so that we can look up the correct feedData object on resume.
        ApplicationDataContainer^ localSettings = 
            ApplicationData::Current->LocalSettings;
        auto values = localSettings->Values;
        values->Insert("LastViewedFeed", 
            dynamic_cast<PropertyValue^>(PropertyValue::CreateString(feed->Uri)));
    }
    
    // We stored the string ID 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^ App::GetFeedItem(FeedData^ fd, String^ uri)
    {
        auto items = fd->Items;
        auto itEnd = end(items);
        auto it = std::find_if(begin(items), itEnd,
            [uri](FeedItem^ fi)
        {
            return fi->Link->AbsoluteUri == uri;
        });
    
        if (it != itEnd)
            return *it;
    
        return nullptr;
    }
    

Part 5: Convert the data into usable forms

Not all raw data is necessarily in usable form. An RSS or Atom feed expresses its publication date as an RFC 822 numerical value. We need a way to convert that into text that makes sense to the user. To do that, we’ll create a custom class that implements IValueConverter and accepts an RFC833 value as input and outputs strings for each component of the date. Later, in the XAML that displays the data, we'll bind to the output of our DateConverter class instead of to the raw data format.

Hh465045.wedge(en-us,WIN.10).gifAdd a date converter

  1. In the shared project, create a new .h file and add this code:

    //DateConverter.h
    
    #pragma once
    #include <string> //for wcscmp
    #include <regex>
    
    namespace SimpleBlogReader
    {
        namespace WGDTF = Windows::Globalization::DateTimeFormatting;
    
        /// <summary>
        /// Implements IValueConverter so that we can convert the numeric date
        /// representation to a set of strings.
        /// </summary>
        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 = WGDTF::DateTimeFormatter::ShortDate::get();
                    result = dtf->Format(dt);
                }
                else if (wcscmp(param->Data(), L"month") == 0)
                {
                    auto formatter =
                        ref new WGDTF::DateTimeFormatter("{month.abbreviated(3)}");
                    result = formatter->Format(dt);
                }
                else if (wcscmp(param->Data(), L"day") == 0)
                {
                    auto formatter =
                        ref new WGDTF::DateTimeFormatter("{day.integer(2)}");
                    result = formatter->Format(dt);
                }
                else if (wcscmp(param->Data(), L"year") == 0)
                {
                    auto formatter =
                        ref new WGDTF::DateTimeFormatter("{year.full}");
                    auto tempResult = formatter->Format(dt); //e.g. "2014"
    
                    // Insert a hard return after second digit to get the rendering 
                    // effect we want
                    std::wregex r(L"(\\d\\d)(\\d\\d)");
                    result = ref new Platform::String(
                        std::regex_replace(tempResult->Data(), r, L"$1\n$2").c_str());
                }
                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();
            }
        };
    }
    
  2. Now #include it in App.xaml.h:

    #include "DateConverter.h"
    
  3. And create an instance of it in App.xaml in the Application.Resources node:

    <local:DateConverter x:Key="dateConverter" />
    

Feed content comes over the wire as HTML, or in some cases XML formatted text. To display this content in a RichTextBlock we have to convert it into rich text. The following class uses the Windows HtmlUtilities function to parse the HTML, and then uses <regex> functions to split it into paragraphs so that we can build up rich text objects. We cannot use data-binding in this scenario so there is no need for the class to implement IValueConverter. We'll just create local instances of it in the pages where we need it.

Hh465045.wedge(en-us,WIN.10).gifAdd a text converter

  1. In the shared project, add a new .h file, call it TextHelper.h, and add this code:

    #pragma once
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WF = Windows::Foundation;
        namespace WUIXD = Windows::UI::Xaml::Documents;
    
        public ref class TextHelper sealed
        {
        public:
            TextHelper();
            WFC::IVector<WUIXD::Paragraph^>^ CreateRichText(
                Platform::String^ fi,
                WF::TypedEventHandler < WUIXD::Hyperlink^,
                WUIXD::HyperlinkClickEventArgs^ > ^ context);
    
            Platform::String^ UnescapeText(Platform::String^ inStr);
    
        private:
    
            std::vector<std::wstring> SplitContentIntoParagraphs(const std::wstring& s, 
                const std::wstring& rgx);
            std::wstring UnescapeText(const std::wstring& input);
    
            // Maps some HTML entities that we'll use to replace the escape sequences
            // in the call to UnescapeText when we create feed titles and render text. 
            std::map<std::wstring, std::wstring> entities;
        };
    }
    
  2. Now add TextHelper.cpp:

    #include "pch.h"
    #include "TextHelper.h"
    
    using namespace std;
    using namespace SimpleBlogReader;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    
    using namespace Windows::Data::Html;
    using namespace Windows::UI::Xaml::Documents;
    
    /// <summary>
    /// Note that in this example we don't map all the possible HTML entities. Feel free to improve this.
    /// Also note that we initialize the map like this because VS2013 Udpate 3 does not support list
    /// initializers in a member declaration.
    /// </summary>
    TextHelper::TextHelper() : entities(
        {
            { L"&#60;", L"<" }, { L"&#62;", L">" }, { L"&#38;", L"&" }, { L"&#162;", L"¢" }, 
            { L"&#163;", L"£" }, { L"&#165;", L"¥" }, { L"&#8364;", L"€" }, { L"&#8364;", L"©" },
            { L"&#174;", L"®" }, { L"&#8220;", L"“" }, { L"&#8221;", L"”" }, { L"&#8216;", L"‘" },
            { L"&#8217;", L"’" }, { L"&#187;", L"»" }, { L"&#171;", L"«" }, { L"&#8249;", L"‹" },
            { L"&#8250;", L"›" }, { L"&#8226;", L"•" }, { L"&#176;", L"°" }, { L"&#8230;", L"…" },
            { L"&#160;", L" " }, { L"&quot;", LR"(")" }, { L"&apos;", L"'" }, { L"&lt;", L"<" },
            { L"&gt;", L">" }, { L"&rsquo;", L"’" }, { L"&nbsp;", L" " }, { L"&amp;", L"&" }
        })
    {  
    }
    
    ///<summary>
    /// Accepts the Content property from a Feed and returns rich text
    /// paragraphs that can be passed to a RichTextBlock.
    ///</summary>
    String^ TextHelper::UnescapeText(String^ inStr)
    {
        wstring input(inStr->Data());
        wstring result = UnescapeText(input);
        return ref new Platform::String(result.c_str());
    }
    
    ///<summary>
    /// Create a RichText block from the text retrieved by the HtmlUtilies object. 
    /// For a more full-featured app, you could parse the content argument yourself and
    /// add the page's images to the inlines collection.
    ///</summary>
    IVector<Paragraph^>^ TextHelper::CreateRichText(String^ content,
        TypedEventHandler<Hyperlink^, HyperlinkClickEventArgs^>^ context)
    {
        std::vector<Paragraph^> blocks; 
    
        auto text = HtmlUtilities::ConvertToText(content);
        auto parts = SplitContentIntoParagraphs(wstring(text->Data()), LR"(\r\n)");
    
        // Add the link at the top. Don't set the NavigateUri property because 
        // that causes the link to open in IE even if the Click event is handled. 
        auto hlink = ref new Hyperlink();
        hlink->Click += context;
        auto linkText = ref new Run();
        linkText->Foreground = 
            ref new Windows::UI::Xaml::Media::SolidColorBrush(Windows::UI::Colors::DarkRed);
        linkText->Text = "Link";
        hlink->Inlines->Append(linkText);
        auto linkPara = ref new Paragraph();
        linkPara->Inlines->Append(hlink);
        blocks.push_back(linkPara);
    
        for (auto part : parts)
        {
            auto p = ref new Paragraph();
            p->TextIndent = 10;
            p->Margin = (10, 10, 10, 10);
            auto r = ref new Run();
            r->Text = ref new String(part.c_str());
            p->Inlines->Append(r);
            blocks.push_back(p);
        }
    
        return ref new Vector<Paragraph^>(blocks);
    }
    
    ///<summary>
    /// Split an input string which has been created by HtmlUtilities::ConvertToText
    /// into paragraphs. The rgx string we use here is LR("\r\n") . If we ever use
    /// other means to grab the raw text from a feed, then the rgx will have to recognize
    /// other possible new line formats. 
    ///</summary>
    vector<wstring> TextHelper::SplitContentIntoParagraphs(const wstring& s, const wstring& rgx)
    {    
        const wregex r(rgx);
        vector<wstring> result;
    
        // the -1 argument indicates that the text after this match until the next match
        // is the "capture group". In other words, this is how we match on what is between the tokens.
        for (wsregex_token_iterator rit(s.begin(), s.end(), r, -1), end; rit != end; ++rit)
        {
            if (rit->length() > 0)
            {
                result.push_back(*rit);
            }
        }
        return result;  
    }
    
    ///<summary>
    /// This is used to unescape html entities that occur in titles, subtitles, etc.
    //  entities is a map<wstring, wstring> with key-values like this: { L"&#60;", L"<" },
    /// CAUTION: we must not unescape any content that gets sent to the webView.
    ///</summary>
    wstring TextHelper::UnescapeText(const wstring& input)
    {
        wsmatch match;
    
        // match L"&#60;" as well as "&nbsp;"
        const wregex rgx(LR"(&#?\w*?;)");
        wstring result;
    
        // itrEnd needs to be visible outside the loop
        wsregex_iterator itrEnd, itrRemainingText;
    
        // Iterate over input and build up result as we go along
        // by first appending what comes before the match, then the 
        // unescaped replacement for the HTML entity which is the match,
        // then once at the end appending what comes after the last match.
    
        for (wsregex_iterator itr(input.cbegin(), input.cend(), rgx); itr != itrEnd; ++itr)    
        {
            wstring entity = itr->str();
            map<wstring, wstring>::const_iterator mit = entities.find(entity);
            if (mit != end(entities))
            {
                result.append(itr->prefix());
                result.append(mit->second); // mit->second is the replacement text
                itrRemainingText = itr;
            }
            else 
            {
                // we found an entity that we don't explitly map yet so just 
                // render it in raw form. Exercise for the user: add
                // all legal entities to the entities map.   
                result.append(entity);
                continue; 
            }        
        }
    
        // If we didn't find any entities to escape
        // then (a) don't try to dereference itrRemainingText
        // and (b) return input because result is empty!
        if (itrRemainingText == itrEnd)
        {
            return input;
        }
        else
        {
            // Add any text between the last match and end of input string.
            result.append(itrRemainingText->suffix());
            return result;
        }
    }
    

    Note that our custom TextHelper class demonstrates some of the ways you can use ISO C++ (std::map, std::regex, std::wstring) internally within a C++/CX app. We'll create instances of this class locally in the pages that use it. We just have to include it once, in App.xaml.h:

    #include "TextHelper.h"
    
  3. You should be able to build and run the app now. Just don't expect it to do very much.

Part 6: Launch, suspend and resume the app

The App::OnLaunched event fires when the user starts the app by pressing or clicking on its app tile, and also after the user navigates back to the app after the system had terminated it to free up memory for other apps. In either case, we always go to the internet and reload the data in response to this event. However, there are other actions that only need to be invoked in one case or the other. We can deduce these states by looking at the rootFrame in combination with the LaunchActivatedEventArgs argument that is passed to the function, and then do the right thing. Fortunately, the SuspensionManager class that was added automatically with MainPage does most of the work to save and restore the app state when the app is suspended and relaunched. We just have to call its methods.

  1. Add the SuspensionManager code files to the project in the Common folder. Add SuspensionManager.h and copy the following code into it:

    //
    // SuspensionManager.h
    // Declaration of the SuspensionManager class
    //
    
    #pragma once
    
    namespace SimpleBlogReader
    {
        namespace Common
        {
            /// <summary>
            /// SuspensionManager captures global session state to simplify process lifetime management
            /// for an application.  Note that session state will be automatically cleared under a variety
            /// of conditions and should only be used to store information that would be convenient to
            /// carry across sessions, but that should be disacarded when an application crashes or is
            /// upgraded.
            /// </summary>
            class SuspensionManager sealed
            {
            public:
                static void RegisterFrame(Windows::UI::Xaml::Controls::Frame^ frame, Platform::String^ sessionStateKey, Platform::String^ sessionBaseKey = nullptr);
                static void UnregisterFrame(Windows::UI::Xaml::Controls::Frame^ frame);
                static concurrency::task<void> SaveAsync();
                static concurrency::task<void> RestoreAsync(Platform::String^ sessionBaseKey = nullptr);
                static Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ SessionState();
                static Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ SessionStateForFrame(
                    Windows::UI::Xaml::Controls::Frame^ frame);
    
            private:
                static void RestoreFrameNavigationState(Windows::UI::Xaml::Controls::Frame^ frame);
                static void SaveFrameNavigationState(Windows::UI::Xaml::Controls::Frame^ frame);
    
                static Platform::Collections::Map<Platform::String^, Platform::Object^>^ _sessionState;
                static const wchar_t* sessionStateFilename;
    
                static std::vector<Platform::WeakReference> _registeredFrames;
                static Windows::UI::Xaml::DependencyProperty^ FrameSessionStateKeyProperty;
                static Windows::UI::Xaml::DependencyProperty^ FrameSessionBaseKeyProperty;
                static Windows::UI::Xaml::DependencyProperty^ FrameSessionStateProperty;
            };
        }
    }
    
  2. Add the SuspensionManager.cpp code file and copy the following code into it:

    //
    // SuspensionManager.cpp
    // Implementation of the SuspensionManager class
    //
    
    #include "pch.h"
    #include "SuspensionManager.h"
    
    #include <algorithm>
    
    using namespace SimpleBlogReader::Common;
    
    using namespace concurrency;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Storage;
    using namespace Windows::Storage::FileProperties;
    using namespace Windows::Storage::Streams;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Interop;
    
    Map<String^, Object^>^ SuspensionManager::_sessionState = ref new Map<String^, Object^>();
    
    const wchar_t* SuspensionManager::sessionStateFilename = L"_sessionState.dat";
    
    std::vector<WeakReference> SuspensionManager::_registeredFrames;
    
    DependencyProperty^ SuspensionManager::FrameSessionStateKeyProperty =
        DependencyProperty::RegisterAttached("_FrameSessionStateKeyProperty",
        TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr);
    
    DependencyProperty^ SuspensionManager::FrameSessionBaseKeyProperty =
        DependencyProperty::RegisterAttached("_FrameSessionBaseKeyProperty",
        TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr);
    
    DependencyProperty^ SuspensionManager::FrameSessionStateProperty =
        DependencyProperty::RegisterAttached("_FrameSessionStateProperty",
        TypeName(IMap<String^, Object^>::typeid), TypeName(SuspensionManager::typeid), nullptr);
    
    class ObjectSerializeHelper
    {
    public:
        // Codes used for identifying serialized types
        enum StreamTypes {
            NullPtrType = 0,
    
            // Supported IPropertyValue types
            UInt8Type, UInt16Type, UInt32Type, UInt64Type, Int16Type, Int32Type, Int64Type,
            SingleType, DoubleType, BooleanType, Char16Type, GuidType, StringType,
    
            // Additional supported types
            StringToObjectMapType,
    
            // Marker values used to ensure stream integrity
            MapEndMarker
        };
        static String^ ReadString(DataReader^ reader);
        static IMap<String^, Object^>^ ReadStringToObjectMap(DataReader^ reader);
        static Object^ ReadObject(DataReader^ reader);
        static void WriteString(DataWriter^ writer, String^ string);
        static void WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue);
        static void WriteStringToObjectMap(DataWriter^ writer, IMap<String^, Object^>^ map);
        static void WriteObject(DataWriter^ writer, Object^ object);
    };
    
    /// <summary>
    /// Provides access to global session state for the current session.  This state is serialized by
    /// <see cref="SaveAsync"/> and restored by <see cref="RestoreAsync"/> which require values to be
    /// one of the following: boxed values including integers, floating-point singles and doubles,
    /// wide characters, boolean, Strings and Guids, or Map<String^, Object^> where map values are
    /// subject to the same constraints.  Session state should be as compact as possible.
    /// </summary>
    IMap<String^, Object^>^ SuspensionManager::SessionState()
    {
        return _sessionState;
    }
    
    /// <summary>
    /// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to
    /// and restored from <see cref="SessionState"/>.  Frames should be registered once
    /// immediately after creation if they will participate in session state management.  Upon
    /// registration if state has already been restored for the specified key
    /// the navigation history will immediately be restored.  Subsequent invocations of
    /// <see cref="RestoreAsync(String)"/> will also restore navigation history.
    /// </summary>
    /// <param name="frame">An instance whose navigation history should be managed by
    /// <see cref="SuspensionManager"/></param>
    /// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to
    /// store navigation-related information.</param>
    /// <param name="sessionBaseKey">An optional key that identifies the type of session.
    /// This can be used to distinguish between multiple application launch scenarios.</param>
    void SuspensionManager::RegisterFrame(Frame^ frame, String^ sessionStateKey, String^ sessionBaseKey)
    {
        if (frame->GetValue(FrameSessionStateKeyProperty) != nullptr)
        {
            throw ref new FailureException("Frames can only be registered to one session state key");
        }
    
        if (frame->GetValue(FrameSessionStateProperty) != nullptr)
        {
            throw ref new FailureException("Frames must be either be registered before accessing frame session state, or not registered at all");
        }
    
        if (sessionBaseKey != nullptr)
        {
            frame->SetValue(FrameSessionBaseKeyProperty, sessionBaseKey);
            sessionStateKey = sessionBaseKey + "_" + sessionStateKey;
        }
    
        // Use a dependency property to associate the session key with a frame, and keep a list of frames whose
        // navigation state should be managed
        frame->SetValue(FrameSessionStateKeyProperty, sessionStateKey);
        _registeredFrames.insert(_registeredFrames.begin(), WeakReference(frame));
    
        // Check to see if navigation state can be restored
        RestoreFrameNavigationState(frame);
    }
    
    /// <summary>
    /// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/>
    /// from <see cref="SessionState"/>.  Any navigation state previously captured will be
    /// removed.
    /// </summary>
    /// <param name="frame">An instance whose navigation history should no longer be
    /// managed.</param>
    void SuspensionManager::UnregisterFrame(Frame^ frame)
    {
        // Remove session state and remove the frame from the list of frames whose navigation
        // state will be saved (along with any weak references that are no longer reachable)
        auto key = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
        if (SessionState()->HasKey(key))
        {
            SessionState()->Remove(key);
        }
        _registeredFrames.erase(
            std::remove_if(_registeredFrames.begin(), _registeredFrames.end(), [=](WeakReference& e)
        {
            auto testFrame = e.Resolve<Frame>();
            return testFrame == nullptr || testFrame == frame;
        }),
            _registeredFrames.end()
            );
    }
    
    /// <summary>
    /// Provides storage for session state associated with the specified <see cref="Frame"/>.
    /// Frames that have been previously registered with <see cref="RegisterFrame"/> have
    /// their session state saved and restored automatically as a part of the global
    /// <see cref="SessionState"/>.  Frames that are not registered have transient state
    /// that can still be useful when restoring pages that have been discarded from the
    /// navigation cache.
    /// </summary>
    /// <remarks>Apps may choose to rely on <see cref="NavigationHelper"/> to manage
    /// page-specific state instead of working with frame session state directly.</remarks>
    /// <param name="frame">The instance for which session state is desired.</param>
    /// <returns>A collection of state subject to the same serialization mechanism as
    /// <see cref="SessionState"/>.</returns>
    IMap<String^, Object^>^ SuspensionManager::SessionStateForFrame(Frame^ frame)
    {
        auto frameState = safe_cast<IMap<String^, Object^>^>(frame->GetValue(FrameSessionStateProperty));
    
        if (frameState == nullptr)
        {
            auto frameSessionKey = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
            if (frameSessionKey != nullptr)
            {
                // Registered frames reflect the corresponding session state
                if (!_sessionState->HasKey(frameSessionKey))
                {
                    _sessionState->Insert(frameSessionKey, ref new Map<String^, Object^>());
                }
                frameState = safe_cast<IMap<String^, Object^>^>(_sessionState->Lookup(frameSessionKey));
            }
            else
            {
                // Frames that aren't registered have transient state
                frameState = ref new Map<String^, Object^>();
            }
            frame->SetValue(FrameSessionStateProperty, frameState);
        }
        return frameState;
    }
    
    void SuspensionManager::RestoreFrameNavigationState(Frame^ frame)
    {
        auto frameState = SessionStateForFrame(frame);
        if (frameState->HasKey("Navigation"))
        {
            frame->SetNavigationState(safe_cast<String^>(frameState->Lookup("Navigation")));
        }
    }
    
    void SuspensionManager::SaveFrameNavigationState(Frame^ frame)
    {
        auto frameState = SessionStateForFrame(frame);
        frameState->Insert("Navigation", frame->GetNavigationState());
    }
    
    /// <summary>
    /// Save the current <see cref="SessionState"/>.  Any <see cref="Frame"/> instances
    /// registered with <see cref="RegisterFrame"/> will also preserve their current
    /// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity
    /// to save its state.
    /// </summary>
    /// <returns>An asynchronous task that reflects when session state has been saved.</returns>
    task<void> SuspensionManager::SaveAsync(void)
    {
        // Save the navigation state for all registered frames
        for (auto && weakFrame : _registeredFrames)
        {
            auto frame = weakFrame.Resolve<Frame>();
            if (frame != nullptr) SaveFrameNavigationState(frame);
        }
    
        // Serialize the session state synchronously to avoid asynchronous access to shared
        // state
        auto sessionData = ref new InMemoryRandomAccessStream();
        auto sessionDataWriter = ref new DataWriter(sessionData->GetOutputStreamAt(0));
        ObjectSerializeHelper::WriteObject(sessionDataWriter, _sessionState);
    
        // Once session state has been captured synchronously, begin the asynchronous process
        // of writing the result to disk
        return task<unsigned int>(sessionDataWriter->StoreAsync()).then([=](unsigned int)
        {
            return ApplicationData::Current->LocalFolder->CreateFileAsync(StringReference(sessionStateFilename),
                CreationCollisionOption::ReplaceExisting);
        })
            .then([=](StorageFile^ createdFile)
        {
            return createdFile->OpenAsync(FileAccessMode::ReadWrite);
        })
            .then([=](IRandomAccessStream^ newStream)
        {
            return RandomAccessStream::CopyAsync(
                sessionData->GetInputStreamAt(0), newStream->GetOutputStreamAt(0));
        })
            .then([=](UINT64 copiedBytes)
        {
            (void) copiedBytes; // Unused parameter
            return;
        });
    }
    
    /// <summary>
    /// Restores previously saved <see cref="SessionState"/>.  Any <see cref="Frame"/> instances
    /// registered with <see cref="RegisterFrame"/> will also restore their prior navigation
    /// state, which in turn gives their active <see cref="Page"/> an opportunity restore its
    /// state.
    /// </summary>
    /// <param name="sessionBaseKey">An optional key that identifies the type of session.
    /// This can be used to distinguish between multiple application launch scenarios.</param>
    /// <returns>An asynchronous task that reflects when session state has been read.  The
    /// content of <see cref="SessionState"/> should not be relied upon until this task
    /// completes.</returns>
    task<void> SuspensionManager::RestoreAsync(String^ sessionBaseKey)
    {
        _sessionState->Clear();
    
        task<StorageFile^> getFileTask(ApplicationData::Current->LocalFolder->GetFileAsync(StringReference(sessionStateFilename)));
        return getFileTask.then([=](StorageFile^ stateFile)
        {
            task<BasicProperties^> getBasicPropertiesTask(stateFile->GetBasicPropertiesAsync());
            return getBasicPropertiesTask.then([=](BasicProperties^ stateFileProperties)
            {
                auto size = unsigned int(stateFileProperties->Size);
                if (size != stateFileProperties->Size) throw ref new FailureException("Session state larger than 4GB");
                task<IRandomAccessStreamWithContentType^> openReadTask(stateFile->OpenReadAsync());
                return openReadTask.then([=](IRandomAccessStreamWithContentType^ stateFileStream)
                {
                    auto stateReader = ref new DataReader(stateFileStream);
                    return task<unsigned int>(stateReader->LoadAsync(size)).then([=](unsigned int bytesRead)
                    {
                        (void) bytesRead; // Unused parameter
                        // Deserialize the Session State
                        Object^ content = ObjectSerializeHelper::ReadObject(stateReader);
                        _sessionState = (Map<String^, Object^>^)content;
    
                        // Restore any registered frames to their saved state
                        for (auto && weakFrame : _registeredFrames)
                        {
                            auto frame = weakFrame.Resolve<Frame>();
                            if (frame != nullptr && safe_cast<String^>(frame->GetValue(FrameSessionBaseKeyProperty)) == sessionBaseKey)
                            {
                                frame->ClearValue(FrameSessionStateProperty);
                                RestoreFrameNavigationState(frame);
                            }
                        }
                    }, task_continuation_context::use_current());
                });
            });
        });
    }
    
    #pragma region Object serialization for a known set of types
    
    void ObjectSerializeHelper::WriteString(DataWriter^ writer, String^ string)
    {
        writer->WriteByte(StringType);
        writer->WriteUInt32(writer->MeasureString(string));
        writer->WriteString(string);
    }
    
    void ObjectSerializeHelper::WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue)
    {
        switch (propertyValue->Type)
        {
        case PropertyType::UInt8:
            writer->WriteByte(StreamTypes::UInt8Type);
            writer->WriteByte(propertyValue->GetUInt8());
            return;
        case PropertyType::UInt16:
            writer->WriteByte(StreamTypes::UInt16Type);
            writer->WriteUInt16(propertyValue->GetUInt16());
            return;
        case PropertyType::UInt32:
            writer->WriteByte(StreamTypes::UInt32Type);
            writer->WriteUInt32(propertyValue->GetUInt32());
            return;
        case PropertyType::UInt64:
            writer->WriteByte(StreamTypes::UInt64Type);
            writer->WriteUInt64(propertyValue->GetUInt64());
            return;
        case PropertyType::Int16:
            writer->WriteByte(StreamTypes::Int16Type);
            writer->WriteUInt16(propertyValue->GetInt16());
            return;
        case PropertyType::Int32:
            writer->WriteByte(StreamTypes::Int32Type);
            writer->WriteUInt32(propertyValue->GetInt32());
            return;
        case PropertyType::Int64:
            writer->WriteByte(StreamTypes::Int64Type);
            writer->WriteUInt64(propertyValue->GetInt64());
            return;
        case PropertyType::Single:
            writer->WriteByte(StreamTypes::SingleType);
            writer->WriteSingle(propertyValue->GetSingle());
            return;
        case PropertyType::Double:
            writer->WriteByte(StreamTypes::DoubleType);
            writer->WriteDouble(propertyValue->GetDouble());
            return;
        case PropertyType::Boolean:
            writer->WriteByte(StreamTypes::BooleanType);
            writer->WriteBoolean(propertyValue->GetBoolean());
            return;
        case PropertyType::Char16:
            writer->WriteByte(StreamTypes::Char16Type);
            writer->WriteUInt16(propertyValue->GetChar16());
            return;
        case PropertyType::Guid:
            writer->WriteByte(StreamTypes::GuidType);
            writer->WriteGuid(propertyValue->GetGuid());
            return;
        case PropertyType::String:
            WriteString(writer, propertyValue->GetString());
            return;
        default:
            throw ref new InvalidArgumentException("Unsupported property type");
        }
    }
    
    void ObjectSerializeHelper::WriteStringToObjectMap(DataWriter^ writer, IMap<String^, Object^>^ map)
    {
        writer->WriteByte(StringToObjectMapType);
        writer->WriteUInt32(map->Size);
        for (auto && pair : map)
        {
            WriteObject(writer, pair->Key);
            WriteObject(writer, pair->Value);
        }
        writer->WriteByte(MapEndMarker);
    }
    
    void ObjectSerializeHelper::WriteObject(DataWriter^ writer, Object^ object)
    {
        if (object == nullptr)
        {
            writer->WriteByte(NullPtrType);
            return;
        }
    
        auto propertyObject = dynamic_cast<IPropertyValue^>(object);
        if (propertyObject != nullptr)
        {
            WriteProperty(writer, propertyObject);
            return;
        }
    
        auto mapObject = dynamic_cast<IMap<String^, Object^>^>(object);
        if (mapObject != nullptr)
        {
            WriteStringToObjectMap(writer, mapObject);
            return;
        }
    
        throw ref new InvalidArgumentException("Unsupported data type");
    }
    
    String^ ObjectSerializeHelper::ReadString(DataReader^ reader)
    {
        int length = reader->ReadUInt32();
        String^ string = reader->ReadString(length);
        return string;
    }
    
    IMap<String^, Object^>^ ObjectSerializeHelper::ReadStringToObjectMap(DataReader^ reader)
    {
        auto map = ref new Map<String^, Object^>();
        auto size = reader->ReadUInt32();
        for (unsigned int index = 0; index < size; index++)
        {
            auto key = safe_cast<String^>(ReadObject(reader));
            auto value = ReadObject(reader);
            map->Insert(key, value);
        }
        if (reader->ReadByte() != StreamTypes::MapEndMarker)
        {
            throw ref new InvalidArgumentException("Invalid stream");
        }
        return map;
    }
    
    Object^ ObjectSerializeHelper::ReadObject(DataReader^ reader)
    {
        auto type = reader->ReadByte();
        switch (type)
        {
        case StreamTypes::NullPtrType:
            return nullptr;
        case StreamTypes::UInt8Type:
            return reader->ReadByte();
        case StreamTypes::UInt16Type:
            return reader->ReadUInt16();
        case StreamTypes::UInt32Type:
            return reader->ReadUInt32();
        case StreamTypes::UInt64Type:
            return reader->ReadUInt64();
        case StreamTypes::Int16Type:
            return reader->ReadInt16();
        case StreamTypes::Int32Type:
            return reader->ReadInt32();
        case StreamTypes::Int64Type:
            return reader->ReadInt64();
        case StreamTypes::SingleType:
            return reader->ReadSingle();
        case StreamTypes::DoubleType:
            return reader->ReadDouble();
        case StreamTypes::BooleanType:
            return reader->ReadBoolean();
        case StreamTypes::Char16Type:
            return (char16_t) reader->ReadUInt16();
        case StreamTypes::GuidType:
            return reader->ReadGuid();
        case StreamTypes::StringType:
            return ReadString(reader);
        case StreamTypes::StringToObjectMapType:
            return ReadStringToObjectMap(reader);
        default:
            throw ref new InvalidArgumentException("Unsupported property type");
        }
    }
    
    #pragma endregion
    
  3. In app.xaml.cpp add this include directive:

    #include "Common\SuspensionManager.h"
    
  4. Add the namespace directive:

    using namespace SimpleBlogReader::Common;
    
  5. Now replace the existing function with this code:

    void App::OnLaunched(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();
            SuspensionManager::RegisterFrame(rootFrame, "AppFrame");
    
            // Initialize the Atom and RSS feed objects with data from the web
            FeedDataSource^ feedDataSource = 
                safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
            if (feedDataSource->Feeds->Size == 0)
            {
                if (e->PreviousExecutionState == ApplicationExecutionState::Terminated)
                {
                    // On resume FeedDataSource needs to know whether the app was on a
                    // specific FeedData, which will be the unless it was on MainPage
                    // when it was terminated.
                    ApplicationDataContainer^ localSettings = ApplicationData::Current->LocalSettings;
                    auto values = localSettings->Values;
                    if (localSettings->Values->HasKey("LastViewedFeed"))
                    {
                        feedDataSource->CurrentFeedUri = 
                            safe_cast<String^>(localSettings->Values->Lookup("LastViewedFeed"));
                    }
                }
    
                feedDataSource->InitDataSource();
            }
    
            // We have 4 pages in the app
            rootFrame->CacheSize = 4;
            auto prerequisite = task<void>([](){});
            if (e->PreviousExecutionState == ApplicationExecutionState::Terminated)
            {
                // Now restore the pages if we are resuming
                prerequisite = Common::SuspensionManager::RestoreAsync();
            }
    
            // if we're starting fresh, prerequisite will execute immediately.
            // if resuming from termination, prerequisite will wait until RestoreAsync() completes.
            prerequisite.then([=]()
            {
                if (rootFrame->Content == nullptr)
                {
                    if (!rootFrame->Navigate(MainPage::typeid, e->Arguments))
                    {
                        throw ref new FailureException("Failed to create initial page");
                    }
                }
                // Place the frame in the current Window
                Window::Current->Content = rootFrame;
                Window::Current->Activate();
            }, task_continuation_context::use_current());
        }
    
        // There is a frame, but is has no content, so navigate to main page
        // and activate the window.
        else if (rootFrame->Content == nullptr)
        {
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
            // Removes the turnstile navigation for startup.
            if (rootFrame->ContentTransitions != nullptr)
            {
                _transitions = ref new TransitionCollection();
                for (auto transition : rootFrame->ContentTransitions)
                {
                    _transitions->Append(transition);
                }
            }
    
            rootFrame->ContentTransitions = nullptr;
            _firstNavigatedToken = rootFrame->Navigated += 
                ref new NavigatedEventHandler(this, &App::RootFrame_FirstNavigated);
    
    
    #endif
            // 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(MainPage::typeid, e->Arguments))
            {
                throw ref new FailureException("Failed to create initial page");
            }
    
            // Ensure the current window is active in this code path.
            // we also called this inside the task for the other path.
            Window::Current->Activate();
        }
    }
    

    Note that the App class is in the shared project, so the code we write here will run on both the windows and the phone apps, except where the WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP macro is defined.

  6. The OnSuspending handler is simpler. It is called when the system shuts down the app, not when the user closes it. We just let SuspensionManager do the work here. It will call the SaveState event handler on each page in the app, and will serialize whatever objects we have stored in each page’s PageState object and then restore the values back into the pages when the app resumes. Look at SuspensionManager.cpp if you want to see the code.

    Replace the existing OnSuspending function body with this code:

    void App::OnSuspending(Object^ sender, SuspendingEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        (void)e;        // Unused parameter
    
        // Save application state and stop any background activity
        auto deferral = e->SuspendingOperation->GetDeferral();
        create_task(Common::SuspensionManager::SaveAsync())
            .then([deferral]()
        {
            deferral->Complete();
        });
    }
    

At this point we could launch the app and download the feed data, but we have no way to display it to the user. Let’s do something about that!

Part 7: Add the first UI page, a list of feeds

When the app opens up, we want to show the user a top-level collection of all the feeds that have been downloaded. They can click or press on an item in the collection to navigate to a particular feed which will contain a collection of feed items, or posts. We already added the pages. In the Windows app it’s an items page, which shows a GridView when the device is horizontal, and a ListView when the device is vertical. Phone projects don’t have an Items Page, so we have a basic page to which we’ll add a ListView manually. The list view will automatically adjust itself when the device orientation changes.

On this and every page there are usually the same basic tasks to accomplish:

  • Add the XAML markup that describes the UI and binds to data
  • Add custom code to the LoadState and SaveState member functions.
  • Handle events, at least one of which usually has code that navigates to the next page

We’ll take these in order, first in the Windows project:

Add the XAML markup (MainPage)

The main page renders each FeedData object in a GridView control. To describe how the data should look, we create a DataTemplate, which is a XAML tree that will be used to render each item. The possibilities for DataTemplates in terms of layouts, fonts, colors and so on, are limited only by your own imagination and sense of style. On this page, we’ll use a simple template that, when rendered, will look like this:

Feed item

  1. A XAML style is like a style in Microsoft Word; it is a convenient way to group a set of property values on a XAML element, the "TargetType". A Style can be based on another style. The "x:Key" attribute specifies the name we use to refer to the style when we consume it.

    Put this template, and its supporting styles, in the Page.Resources node of MainPage.xaml (Windows 8.1). They are only used in MainPage.

    <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 Margin="0,22,16,0">
                <TextBlock Text="{Binding Title}" 
                            Style="{StaticResource GridTitleTextStyle}" 
                            Margin="10,10,10,10"/>
                <TextBlock Text="{Binding Description}" 
                            Style="{StaticResource GridDescriptionTextStyle}"
                            Margin="10,10,10,10" />
            </StackPanel>
            <Border BorderBrush="DarkRed" BorderThickness="4" VerticalAlignment="Bottom">
                <StackPanel VerticalAlignment="Bottom" Orientation="Horizontal" 
                            Background="{StaticResource GreenBlockBackgroundBrush}">
                    <TextBlock Text="Last Updated" FontWeight="Bold" Margin="12,4,0,8" 
                                Height="42"/>
                    <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}" 
                                FontWeight="ExtraBold" Margin="4,4,12,8" Height="42" Width="88"/>
                </StackPanel>
            </Border>
        </Grid>
    </DataTemplate>
    

    You will see a red squiggly under GreenBlockBackgroundBrush, which we will take care of in a few steps.

  2. Still in MainPage.xaml (Windows 8.1), delete the page-local AppName element so that it doesn’t hide the global element we are going to add at App scope.

  3. Add a CollectionViewSource to the Page.Resources node. This object connects our ListView to the data model:

    <!-- Collection of items displayed by this page -->
            <CollectionViewSource
            x:Name="itemsViewSource"
            Source="{Binding Items}"/>
    

    Note that the Page element already has a DataContext attribute set to the DefaultViewModel property for the MainPage class. We set that property to be a FeedDataSource, and therefore the CollectionViewSource looks there for an Items collection, which it finds.

  4. In App.xaml, let’s add a global resource string for the app name, along with some additional resources that will be referenced from multiple pages in the app. By putting resources here, we don't have to define them separately on each page. Add these elements to the Resources node in App.xaml:

            <x:String x:Key="AppName">Simple Blog Reader</x:String>        
    
            <SolidColorBrush x:Key="WindowsBlogBackgroundBrush" Color="#FF0A2562"/>
            <SolidColorBrush x:Key="GreenBlockBackgroundBrush" Color="#FF6BBD46"/>
            <Style x:Key="WindowsBlogLayoutRootStyle" TargetType="Panel">
                <Setter Property="Background" 
                        Value="{StaticResource WindowsBlogBackgroundBrush}"/>
            </Style>
    
            <!-- Green square in all ListViews that displays the date -->
            <ControlTemplate x:Key="DateBlockTemplate">
                <Viewbox Stretch="Fill">
                    <Canvas Height="86" Width="86"  Margin="4,0,4,4" 
                     HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <TextBlock TextTrimming="WordEllipsis" 
                                   Padding="0,0,0,0"
                                   TextWrapping="NoWrap" 
                                   Width="Auto"
                                   Height="Auto" 
                                   FontSize="32" 
                                   FontWeight="Bold">
                            <TextBlock.Text>
                                <Binding Path="PubDate" 
                                         Converter="{StaticResource dateConverter}"
                                         ConverterParameter="month"/>
                            </TextBlock.Text>
                        </TextBlock>
    
                        <TextBlock TextTrimming="WordEllipsis" 
                                   TextWrapping="Wrap" 
                                   Width="Auto" 
                                   Height="Auto" 
                                   FontSize="32" 
                                   FontWeight="Bold" 
                                   Canvas.Top="36">
                            <TextBlock.Text>
                                <Binding Path="PubDate"  
                                         Converter="{StaticResource dateConverter}"
                                         ConverterParameter="day"/>
                            </TextBlock.Text>
                        </TextBlock>
    
                        <Line Stroke="White" 
                              StrokeThickness="2" X1="50" Y1="46" X2="50" Y2="80" />
    
                        <TextBlock TextWrapping="Wrap"  
                                   Height="Auto"  
                                   FontSize="18" 
                                   FontWeight="Bold"
                             FontStretch="Condensed"
                                   LineHeight="18"
                                   LineStackingStrategy="BaselineToBaseline"
                                   Canvas.Top="38" 
                                   Canvas.Left="56">
                            <TextBlock.Text>
                                <Binding Path="PubDate" 
                                         Converter="{StaticResource dateConverter}"
                                         ConverterParameter="year"  />
                            </TextBlock.Text>
                        </TextBlock>
                    </Canvas>
                </Viewbox>
            </ControlTemplate>
    
            <!-- Describes the layout for items in all ListViews -->
            <DataTemplate x:Name="ListItemTemplate">
                <Grid Margin="5,0,0,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="72"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition MaxHeight="54"></RowDefinition>
                    </Grid.RowDefinitions>
                    <!-- Green date block -->
                    <Border Background="{StaticResource GreenBlockBackgroundBrush}"
                            VerticalAlignment="Top">
                        <ContentControl Template="{StaticResource DateBlockTemplate}" />
                    </Border>
                    <TextBlock Grid.Column="1"
                               Text="{Binding Title}"
                               Margin="10,0,0,0" FontSize="20" 
                               TextWrapping="Wrap"
                               MaxHeight="72" 
                               Foreground="#FFFE5815" />
                </Grid>
            </DataTemplate>
    

MainPage displays a list of feeds. When the device is in landscape orientation, we'll use a GridView, which supports horizontal scrolling. In landscape orientation, we'll use a ListView, which supports vertical scrolling. We’d like the user to be able to use the app in either orientation. It is relatively straightforward to implement support for orientation changes:

  • Add both controls to the page, and set the ItemSource to the same collectionViewSource. Set the Visibility property on the ListView to Collapsed so that by default it is not visible.
  • Create a set of two VisualState objects, one that describes the UI behavior for landscape orientation and one that describes the behavior for portrait orientation.
  • Handle the Window::SizeChanged event, which is fired when the orientation changes or the user narrows or widens the window. Examine the height and width of the new size. If the height is greater than the width, then invoke the VisualState for portrait orientation. Otherwise invoke the state for landscape.

Hh465045.wedge(en-us,WIN.10).gifAdd the GridView and ListView

  1. In MainPage.xaml, add this GridView and ListView and the grid that contains the back button and page title:

     <Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
            <Grid.ChildrenTransitions>
                <TransitionCollection>
                    <EntranceThemeTransition/>
                </TransitionCollection>
            </Grid.ChildrenTransitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="140"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!-- 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"  Margin="0,-10,0,10">
            </GridView>
    
            <!-- Vertical scrolling list -->
            <ListView
                x:Name="ItemListView"
                Visibility="Collapsed"            
                AutomationProperties.AutomationId="ItemsListView"
                AutomationProperties.Name="Items"
                TabIndex="1" Grid.Row="1" Margin="-10,-10,0,0"      
                IsItemClickEnabled="True"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"            
                ItemClick="ItemGridView_ItemClick"
                ItemTemplate="{StaticResource ListItemTemplate}">
    
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="2,0,0,2"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
            <!-- Back button and page title -->
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button x:Name="backButton" Margin="39,59,39,0" 
                        Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
                        Style="{StaticResource NavigationBackButtonNormalStyle}"
                        VerticalAlignment="Top"
                        AutomationProperties.Name="Back"
                        AutomationProperties.AutomationId="BackButton"
                        AutomationProperties.ItemType="Navigation Button"/>
                <TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" 
                        Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" 
                        IsHitTestVisible="false" TextWrapping="NoWrap" 
                        VerticalAlignment="Bottom" Margin="0,0,30,40"/>
            </Grid>
    
  2. Note that both controls use the same member function for the ItemClick event. Place the insertion point on one of those and press F12 to auto-generate the event handler stub. We'll add the code for it later.

  3. Paste in the VisualStateGroups definition so that this is the last element inside the root grid (don’t put it outside the Grid or it won't work). Note that there are two states, but only one is explicitly defined. That’s because the DefaultLayout state is already described in the XAML for this page).

    <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="DefaultLayout"/>
            <VisualState x:Name="Portrait">
                <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" 
                           Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" 
                           Storyboard.TargetProperty="Visibility">
                    <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                </ObjectAnimationUsingKeyFrames>
               </Storyboard>
            </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    
  4. Now the UI is all defined. We just need to tell the page what to do when it loads.

LoadState and SaveState (Windows app MainPage)

The two primary member functions that we need to pay attention to on any XAML page are LoadState and (sometimes) SaveState. In LoadState we populate the data for the page, and in SaveState we save any data that will be necessary to repopulate the page in case we get suspended and then started again.

  • Replace the LoadState implementation with this code, which inserts the feed data that was loaded (or is still being loaded) by the feedDataSource that we created on startup, and puts the data into our ViewModel for this page.

    void MainPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {     
        auto feedDataSource = safe_cast<FeedDataSource^>  
        (App::Current->Resources->Lookup("feedDataSource"));
        this->DefaultViewModel->Insert("Items", feedDataSource->Feeds);
    }
    

    We don't have to call SaveState for MainPage because there is nothing for this page to remember. It always displays all the feeds.

Event handlers (Windows app MainPage)

All pages reside conceptually inside a Frame. It's the Frame that we use to navigate back and forth between the pages. The second parameter in a Navigate function call is used to pass data from one page to another. Any objects we pass here are automatically stored and serialized by the SuspensionManager whenever the app is suspended so that the values can be restored when the app is resumed. The default SuspensionManager only supports the built-in types, String, and Guid. If you need more sophisticated serialization, you can make a custom SuspensionManager. Here we pass a String, which the SplitPage will use to look up the current feed.

Hh465045.wedge(en-us,WIN.10).gifTo navigate on item clicks

  1. When the user clicks on an item in the grid, the event handler gets the item that was clicked, sets it as the "current feed" in case the app gets suspended at some later point, and then navigates to the next page. It passes the feed's title to the next page so that that page can look up the data for that feed. Here is the code to paste in:

    void MainPage::ItemGridView_ItemClick(Object^ sender, ItemClickEventArgs^ e)
    {
        // We must manually cast from Object^ to FeedData^.
        auto feedData = safe_cast<FeedData^>(e->ClickedItem);
    
        // Store the feed and tell other pages it's loaded and ready to go.
        auto app = safe_cast<App^>(App::Current);
        app->SetCurrentFeed(feedData);
    
        // Only navigate if there are items in the feed
        if (feedData->Items->Size > 0)
        {
            // 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(SplitPage::typeid, feedData->Title);
        }
    }
    
  2. For the previous code to compile, we need to #include SplitPage.xaml.h at the top of the current file, MainPage.xaml.cpp (Windows 8.1):

    #include "SplitPage.xaml.h"
    

Hh465045.wedge(en-us,WIN.10).gifTo handle the Page_SizeChanged event

  • In MainPage.xaml, add a name to the root element by adding x:Name="pageRoot" to the root Page element's attributes, then add an attribute SizeChanged="pageRoot_SizeChanged" to create an event handler. Replace the handler implementation in the cpp file with this code:

    void MainPage::pageRoot_SizeChanged(Platform::Object^ sender, SizeChangedEventArgs^ e)
    {
        if (e->NewSize.Height / e->NewSize.Width >= 1)
        {
            VisualStateManager::GoToState(this, "Portrait", false);
        }
        else
        {
            VisualStateManager::GoToState(this, "DefaultLayout", false);
        }
    } 
    

    Then add the declaration of this function to the MainPage class in MainPage.xaml.h.

    private:
        void pageRoot_SizeChanged(Platform::Object^ sender, SizeChangedEventArgs^ e);
    

    The code is straightforward. If you now run the app in the simulator, and rotate the device, you will see the UI change between the GridView and the ListView.

Add XAML (Phone app MainPage)

Now let's get the phone app main page working. This is going to be a lot less code because we’ll be using all the code we put into the shared project. Also, phone apps don’t support GridView controls because the screens are too small for it to work well. So we’ll use a ListView that will adjust to landscape orientation automatically and won’t need any VisualState changes. We'll start by adding the DataContext attribute to the Page element. This isn't auto-generated in a phone basic page like it is in an ItemsPage or SplitPage.

  1. In order to implement page navigation, your pages need NavigationHelper, which in turn depends on RelayCommand. Add a new item, RelayCommand.h, and copy this code into it:

    //
    // RelayCommand.h
    // Declaration of the RelayCommand and associated classes
    //
    
    #pragma once
    
    // <summary>
    // A command whose sole purpose is to relay its functionality 
    // to other objects by invoking delegates. 
    // The default return value for the CanExecute method is 'true'.
    // <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
    // <see cref="CanExecute"/> is expected to return a different value.
    // </summary>
    
    
    namespace SimpleBlogReader
    {
        namespace Common
        {
            [Windows::Foundation::Metadata::WebHostHidden]
            public ref class RelayCommand sealed :[Windows::Foundation::Metadata::Default] Windows::UI::Xaml::Input::ICommand
            {
            public:
                virtual event Windows::Foundation::EventHandler<Object^>^ CanExecuteChanged;
                virtual bool CanExecute(Object^ parameter);
                virtual void Execute(Object^ parameter);
                virtual ~RelayCommand();
    
            internal:
                RelayCommand(std::function<bool(Platform::Object^)> canExecuteCallback,
                    std::function<void(Platform::Object^)> executeCallback);
                void RaiseCanExecuteChanged();
    
            private:
                std::function<bool(Platform::Object^)> _canExecuteCallback;
                std::function<void(Platform::Object^)> _executeCallback;
            };
        }
    }
    
  2. In the Common folder, add RelayCommand.cpp and copy this code into it:

    //
    // RelayCommand.cpp
    // Implementation of the RelayCommand and associated classes
    //
    
    #include "pch.h"
    #include "RelayCommand.h"
    #include "NavigationHelper.h"
    
    using namespace SimpleBlogReader::Common;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::System;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::ViewManagement;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Navigation;
    
    /// <summary>
    /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
    /// </summary>
    /// <param name="parameter">
    /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
    /// </param>
    /// <returns>true if this command can be executed; otherwise, false.</returns>
    bool RelayCommand::CanExecute(Object^ parameter)
    {
        return (_canExecuteCallback) (parameter);
    }
    
    /// <summary>
    /// Executes the <see cref="RelayCommand"/> on the current command target.
    /// </summary>
    /// <param name="parameter">
    /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
    /// </param>
    void RelayCommand::Execute(Object^ parameter)
    {
        (_executeCallback) (parameter);
    }
    
    /// <summary>
    /// Method used to raise the <see cref="CanExecuteChanged"/> event
    /// to indicate that the return value of the <see cref="CanExecute"/>
    /// method has changed.
    /// </summary>
    void RelayCommand::RaiseCanExecuteChanged()
    {
        CanExecuteChanged(this, nullptr);
    }
    
    /// <summary>
    /// RelayCommand Class Destructor.
    /// </summary>
    RelayCommand::~RelayCommand()
    {
        _canExecuteCallback = nullptr;
        _executeCallback = nullptr;
    };
    
    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="canExecuteCallback">The execution status logic.</param>
    /// <param name="executeCallback">The execution logic.</param>
    RelayCommand::RelayCommand(std::function<bool(Platform::Object^)> canExecuteCallback,
        std::function<void(Platform::Object^)> executeCallback) :
        _canExecuteCallback(canExecuteCallback),
        _executeCallback(executeCallback)
        {
        }
    
  3. In the Common folder, add a file NavigationHelper.h and copy this code into it:

    //
    // NavigationHelper.h
    // Declaration of the NavigationHelper and associated classes
    //
    
    #pragma once
    
    #include "RelayCommand.h"
    
    namespace SimpleBlogReader
    {
        namespace Common
        {
            /// <summary>
            /// Class used to hold the event data required when a page attempts to load state.
            /// </summary>
            public ref class LoadStateEventArgs sealed
            {
            public:
    
                /// <summary>
                /// The parameter value passed to <see cref="Frame->Navigate(Type, Object)"/> 
                /// when this page was initially requested.
                /// </summary>
                property Platform::Object^ NavigationParameter
                {
                    Platform::Object^ get();
                }
    
                /// <summary>
                /// A dictionary of state preserved by this page during an earlier
                /// session.  This will be null the first time a page is visited.
                /// </summary>
                property Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ PageState
                {
                    Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ get();
                }
    
            internal:
                LoadStateEventArgs(Platform::Object^ navigationParameter, Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ pageState);
    
            private:
                Platform::Object^ _navigationParameter;
                Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ _pageState;
            };
    
            /// <summary>
            /// Represents the method that will handle the <see cref="NavigationHelper->LoadState"/>event
            /// </summary>
            public delegate void LoadStateEventHandler(Platform::Object^ sender, LoadStateEventArgs^ e);
    
            /// <summary>
            /// Class used to hold the event data required when a page attempts to save state.
            /// </summary>
            public ref class SaveStateEventArgs sealed
            {
            public:
    
                /// <summary>
                /// An empty dictionary to be populated with serializable state.
                /// </summary>
                property Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ PageState
                {
                    Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ get();
                }
    
            internal:
                SaveStateEventArgs(Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ pageState);
    
            private:
                Windows::Foundation::Collections::IMap<Platform::String^, Platform::Object^>^ _pageState;
            };
    
            /// <summary>
            /// Represents the method that will handle the <see cref="NavigationHelper->SaveState"/>event
            /// </summary>
            public delegate void SaveStateEventHandler(Platform::Object^ sender, SaveStateEventArgs^ e);
    
            /// <summary>
            /// NavigationHelper aids in navigation between pages.  It provides commands used to 
            /// navigate back and forward as well as registers for standard mouse and keyboard 
            /// shortcuts used to go back and forward in Windows and the hardware back button in
            /// Windows Phone.  In addition it integrates SuspensionManger to handle process lifetime
            /// management and state management when navigating between pages.
            /// </summary>
            /// <example>
            /// To make use of NavigationHelper, follow these two steps or
            /// start with a BasicPage or any other Page item template other than BlankPage.
            /// 
            /// 1) Create an instance of the NavigationHelper somewhere such as in the 
            ///     constructor for the page and register a callback for the LoadState and 
            ///     SaveState events.
            /// <code>
            ///     MyPage::MyPage()
            ///     {
            ///         InitializeComponent();
            ///         auto navigationHelper = ref new Common::NavigationHelper(this);
            ///         navigationHelper->LoadState += ref new Common::LoadStateEventHandler(this, &MyPage::LoadState);
            ///         navigationHelper->SaveState += ref new Common::SaveStateEventHandler(this, &MyPage::SaveState);
            ///     }
            ///     
            ///     void MyPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
            ///     { }
            ///     void MyPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
            ///     { }
            /// </code>
            /// 
            /// 2) Register the page to call into the NavigationHelper whenever the page participates 
            ///     in navigation by overriding the <see cref="Windows::UI::Xaml::Controls::Page::OnNavigatedTo"/> 
            ///     and <see cref="Windows::UI::Xaml::Controls::Page::OnNavigatedFrom"/> events.
            /// <code>
            ///     void MyPage::OnNavigatedTo(NavigationEventArgs^ e)
            ///     {
            ///         NavigationHelper->OnNavigatedTo(e);
            ///     }
            ///
            ///     void MyPage::OnNavigatedFrom(NavigationEventArgs^ e)
            ///     {
            ///         NavigationHelper->OnNavigatedFrom(e);
            ///     }
            /// </code>
            /// </example>
            [Windows::Foundation::Metadata::WebHostHidden]
            [Windows::UI::Xaml::Data::Bindable]
            public ref class NavigationHelper sealed
            {
            public:
                /// <summary>
                /// <see cref="RelayCommand"/> used to bind to the back Button's Command property
                /// for navigating to the most recent item in back navigation history, if a Frame
                /// manages its own navigation history.
                /// 
                /// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoBack"/>
                /// as the Execute Action and <see cref="CanGoBack"/> for CanExecute.
                /// </summary>
                property RelayCommand^ GoBackCommand
                {
                    RelayCommand^ get();
                }
    
                /// <summary>
                /// <see cref="RelayCommand"/> used for navigating to the most recent item in 
                /// the forward navigation history, if a Frame manages its own navigation history.
                /// 
                /// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoForward"/>
                /// as the Execute Action and <see cref="CanGoForward"/> for CanExecute.
                /// </summary>
                property RelayCommand^ GoForwardCommand
                {
                    RelayCommand^ get();
                }
    
            internal:
                NavigationHelper(Windows::UI::Xaml::Controls::Page^ page,
                    RelayCommand^ goBack = nullptr,
                    RelayCommand^ goForward = nullptr);
    
                bool CanGoBack();
                void GoBack();
                bool CanGoForward();
                void GoForward();
    
                void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e);
                void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e);
    
                event LoadStateEventHandler^ LoadState;
                event SaveStateEventHandler^ SaveState;
    
            private:
                Platform::WeakReference _page;
    
                RelayCommand^ _goBackCommand;
                RelayCommand^ _goForwardCommand;
    
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
                Windows::Foundation::EventRegistrationToken _backPressedEventToken;
    
                void HardwareButton_BackPressed(Platform::Object^ sender,
                    Windows::Phone::UI::Input::BackPressedEventArgs^ e);
    #else
                bool _navigationShortcutsRegistered;
                Windows::Foundation::EventRegistrationToken _acceleratorKeyEventToken;
                Windows::Foundation::EventRegistrationToken _pointerPressedEventToken;
    
                void CoreDispatcher_AcceleratorKeyActivated(Windows::UI::Core::CoreDispatcher^ sender,
                    Windows::UI::Core::AcceleratorKeyEventArgs^ e);
                void CoreWindow_PointerPressed(Windows::UI::Core::CoreWindow^ sender,
                    Windows::UI::Core::PointerEventArgs^ e);
    #endif
    
                Platform::String^ _pageKey;
                Windows::Foundation::EventRegistrationToken _loadedEventToken;
                Windows::Foundation::EventRegistrationToken _unloadedEventToken;
                void OnLoaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
                void OnUnloaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
    
                ~NavigationHelper();
            };
        }
    }
    
  4. Now add the implementation file, NavigationHelper.cpp, in the same folder, with the following code:

    //
    // NavigationHelper.cpp
    // Implementation of the NavigationHelper and associated classes
    //
    
    #include "pch.h"
    #include "NavigationHelper.h"
    #include "RelayCommand.h"
    #include "SuspensionManager.h"
    
    using namespace SimpleBlogReader::Common;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::System;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::ViewManagement;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Navigation;
    
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
    using namespace Windows::Phone::UI::Input;
    #endif
    
    /// <summary>
    /// Initializes a new instance of the <see cref="LoadStateEventArgs"/> class.
    /// </summary>
    /// <param name="navigationParameter">
    /// The parameter value passed to <see cref="Frame->Navigate(Type, Object)"/> 
    /// when this page was initially requested.
    /// </param>
    /// <param name="pageState">
    /// A dictionary of state preserved by this page during an earlier
    /// session.  This will be null the first time a page is visited.
    /// </param>
    LoadStateEventArgs::LoadStateEventArgs(Object^ navigationParameter, IMap<String^, Object^>^ pageState)
    {
        _navigationParameter = navigationParameter;
        _pageState = pageState;
    }
    
    /// <summary>
    /// Gets the <see cref="NavigationParameter"/> property of <see cref"LoadStateEventArgs"/> class.
    /// </summary>
    Object^ LoadStateEventArgs::NavigationParameter::get()
    {
        return _navigationParameter;
    }
    
    /// <summary>
    /// Gets the <see cref="PageState"/> property of <see cref"LoadStateEventArgs"/> class.
    /// </summary>
    IMap<String^, Object^>^ LoadStateEventArgs::PageState::get()
    {
        return _pageState;
    }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="SaveStateEventArgs"/> class.
    /// </summary>
    /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
    SaveStateEventArgs::SaveStateEventArgs(IMap<String^, Object^>^ pageState)
    {
        _pageState = pageState;
    }
    
    /// <summary>
    /// Gets the <see cref="PageState"/> property of <see cref"SaveStateEventArgs"/> class.
    /// </summary>
    IMap<String^, Object^>^ SaveStateEventArgs::PageState::get()
    {
        return _pageState;
    }
    
    /// <summary>
    /// Initializes a new instance of the <see cref="NavigationHelper"/> class.
    /// </summary>
    /// <param name="page">A reference to the current page used for navigation.  
    /// This reference allows for frame manipulation and to ensure that keyboard 
    /// navigation requests only occur when the page is occupying the entire window.</param>
    NavigationHelper::NavigationHelper(Page^ page, RelayCommand^ goBack, RelayCommand^ goForward) :
    _page(page),
    _goBackCommand(goBack),
    _goForwardCommand(goForward)
    {
        // When this page is part of the visual tree make two changes:
        // 1) Map application view state to visual state for the page
        // 2) Handle hardware navigation requests
        _loadedEventToken = page->Loaded += ref new RoutedEventHandler(this, &NavigationHelper::OnLoaded);
    
        //// Undo the same changes when the page is no longer visible
        _unloadedEventToken = page->Unloaded += ref new RoutedEventHandler(this, &NavigationHelper::OnUnloaded);
    }
    
    NavigationHelper::~NavigationHelper()
    {
        delete _goBackCommand;
        delete _goForwardCommand;
    
        _page = nullptr;
    }
    
    /// <summary>
    /// Invoked when the page is part of the visual tree
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::OnLoaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
        _backPressedEventToken = HardwareButtons::BackPressed +=
            ref new EventHandler<BackPressedEventArgs^>(this,
            &NavigationHelper::HardwareButton_BackPressed);
    #else
        Page ^page = _page.Resolve<Page>();
    
        // Keyboard and mouse navigation only apply when occupying the entire window
        if (page != nullptr &&
            page->ActualHeight == Window::Current->Bounds.Height &&
            page->ActualWidth == Window::Current->Bounds.Width)
        {
            // Listen to the window directly so focus isn't required
            _acceleratorKeyEventToken = Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated +=
                ref new TypedEventHandler<CoreDispatcher^, AcceleratorKeyEventArgs^>(this,
                &NavigationHelper::CoreDispatcher_AcceleratorKeyActivated);
    
            _pointerPressedEventToken = Window::Current->CoreWindow->PointerPressed +=
                ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this,
                &NavigationHelper::CoreWindow_PointerPressed);
    
            _navigationShortcutsRegistered = true;
        }
    #endif
    }
    
    /// <summary>
    /// Invoked when the page is removed from visual tree
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::OnUnloaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
        HardwareButtons::BackPressed -= _backPressedEventToken;
    #else
        if (_navigationShortcutsRegistered)
        {
            Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated -= _acceleratorKeyEventToken;
            Window::Current->CoreWindow->PointerPressed -= _pointerPressedEventToken;
            _navigationShortcutsRegistered = false;
        }
    #endif
    
        // Remove handler and release the reference to page
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            page->Loaded -= _loadedEventToken;
            page->Unloaded -= _unloadedEventToken;
            delete _goBackCommand;
            delete _goForwardCommand;
            _goForwardCommand = nullptr;
            _goBackCommand = nullptr;
        }
    }
    
    #pragma region Navigation support
    
    /// <summary>
    /// Method used by the <see cref="GoBackCommand"/> property
    /// to determine if the <see cref="Frame"/> can go back.
    /// </summary>
    /// <returns>
    /// true if the <see cref="Frame"/> has at least one entry 
    /// in the back navigation history.
    /// </returns>
    bool NavigationHelper::CanGoBack()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            return (frame != nullptr && frame->CanGoBack);
        }
    
        return false;
    }
    
    /// <summary>
    /// Method used by the <see cref="GoBackCommand"/> property
    /// to invoke the <see cref="Windows::UI::Xaml::Controls::Frame::GoBack"/> method.
    /// </summary>
    void NavigationHelper::GoBack()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            if (frame != nullptr && frame->CanGoBack)
            {
                frame->GoBack();
            }
        }
    }
    
    /// <summary>
    /// Method used by the <see cref="GoForwardCommand"/> property
    /// to determine if the <see cref="Frame"/> can go forward.
    /// </summary>
    /// <returns>
    /// true if the <see cref="Frame"/> has at least one entry 
    /// in the forward navigation history.
    /// </returns>
    bool NavigationHelper::CanGoForward()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            return (frame != nullptr && frame->CanGoForward);
        }
    
        return false;
    }
    
    /// <summary>
    /// Method used by the <see cref="GoForwardCommand"/> property
    /// to invoke the <see cref="Windows::UI::Xaml::Controls::Frame::GoBack"/> method.
    /// </summary>
    void NavigationHelper::GoForward()
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frame = page->Frame;
            if (frame != nullptr && frame->CanGoForward)
            {
                frame->GoForward();
            }
        }
    }
    
    /// <summary>
    /// Gets the <see cref="GoBackCommand"/> property of <see cref"NavigationHelper"/> class.
    /// </summary>
    RelayCommand^ NavigationHelper::GoBackCommand::get()
    {
        if (_goBackCommand == nullptr)
        {
            _goBackCommand = ref new RelayCommand(
                [this](Object^) -> bool
            {
                return CanGoBack();
            },
                [this](Object^) -> void
            {
                GoBack();
            }
            );
        }
        return _goBackCommand;
    }
    
    /// <summary>
    /// Gets the <see cref="GoForwardCommand"/> property of <see cref"NavigationHelper"/> class.
    /// </summary>
    RelayCommand^ NavigationHelper::GoForwardCommand::get()
    {
        if (_goForwardCommand == nullptr)
        {
            _goForwardCommand = ref new RelayCommand(
                [this](Object^) -> bool
            {
                return CanGoForward();
            },
                [this](Object^) -> void
            {
                GoForward();
            }
            );
        }
        return _goForwardCommand;
    }
    
    #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
    /// <summary>
    /// Handles the back button press and navigates through the history of the root frame.
    /// </summary>
    void NavigationHelper::HardwareButton_BackPressed(Object^ sender, BackPressedEventArgs^ e)
    {
        if (this->GoBackCommand->CanExecute(nullptr))
        {
            e->Handled = true;
            this->GoBackCommand->Execute(nullptr);
        }
    }
    #else
    /// <summary>
    /// Invoked on every keystroke, including system keys such as Alt key combinations, when
    /// this page is active and occupies the entire window.  Used to detect keyboard navigation
    /// between pages even when the page itself doesn't have focus.
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher^ sender,
        AcceleratorKeyEventArgs^ e)
    {
        sender; // Unused parameter
        auto virtualKey = e->VirtualKey;
    
        // Only investigate further when Left, Right, or the dedicated Previous or Next keys
        // are pressed
        if ((e->EventType == CoreAcceleratorKeyEventType::SystemKeyDown ||
            e->EventType == CoreAcceleratorKeyEventType::KeyDown) &&
            (virtualKey == VirtualKey::Left || virtualKey == VirtualKey::Right ||
            virtualKey == VirtualKey::GoBack || virtualKey == VirtualKey::GoForward))
        {
            auto coreWindow = Window::Current->CoreWindow;
            auto downState = Windows::UI::Core::CoreVirtualKeyStates::Down;
            bool menuKey = (coreWindow->GetKeyState(VirtualKey::Menu) & downState) == downState;
            bool controlKey = (coreWindow->GetKeyState(VirtualKey::Control) & downState) == downState;
            bool shiftKey = (coreWindow->GetKeyState(VirtualKey::Shift) & downState) == downState;
            bool noModifiers = !menuKey && !controlKey && !shiftKey;
            bool onlyAlt = menuKey && !controlKey && !shiftKey;
    
            if ((virtualKey == VirtualKey::GoBack && noModifiers) ||
                (virtualKey == VirtualKey::Left && onlyAlt))
            {
                // When the previous key or Alt+Left are pressed navigate back
                e->Handled = true;
                GoBackCommand->Execute(this);
            }
            else if ((virtualKey == VirtualKey::GoForward && noModifiers) ||
                (virtualKey == VirtualKey::Right && onlyAlt))
            {
                // When the next key or Alt+Right are pressed navigate forward
                e->Handled = true;
                GoForwardCommand->Execute(this);
            }
        }
    }
    
    /// <summary>
    /// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
    /// page is active and occupies the entire window.  Used to detect browser-style next and
    /// previous mouse button clicks to navigate between pages.
    /// </summary>
    /// <param name="sender">Instance that triggered the event.</param>
    /// <param name="e">Event data describing the conditions that led to the event.</param>
    void NavigationHelper::CoreWindow_PointerPressed(CoreWindow^ sender, PointerEventArgs^ e)
    {
        auto properties = e->CurrentPoint->Properties;
    
        // Ignore button chords with the left, right, and middle buttons
        if (properties->IsLeftButtonPressed ||
            properties->IsRightButtonPressed ||
            properties->IsMiddleButtonPressed)
        {
            return;
        }
    
        // If back or foward are pressed (but not both) navigate appropriately
        bool backPressed = properties->IsXButton1Pressed;
        bool forwardPressed = properties->IsXButton2Pressed;
        if (backPressed ^ forwardPressed)
        {
            e->Handled = true;
            if (backPressed)
            {
                if (GoBackCommand->CanExecute(this))
                {
                    GoBackCommand->Execute(this);
                }
            }
            else
            {
                if (GoForwardCommand->CanExecute(this))
                {
                    GoForwardCommand->Execute(this);
                }
            }
        }
    }
    #endif
    
    #pragma endregion
    
    #pragma region Process lifetime management
    
    /// <summary>
    /// Invoked when this page is about to be displayed in a Frame.
    /// </summary>
    /// <param name="e">Event data that describes how this page was reached.  The Parameter
    /// property provides the group to be displayed.</param>
    void NavigationHelper::OnNavigatedTo(NavigationEventArgs^ e)
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frameState = SuspensionManager::SessionStateForFrame(page->Frame);
            _pageKey = "Page-" + page->Frame->BackStackDepth;
    
            if (e->NavigationMode == NavigationMode::New)
            {
                // Clear existing state for forward navigation when adding a new page to the
                // navigation stack
                auto nextPageKey = _pageKey;
                int nextPageIndex = page->Frame->BackStackDepth;
                while (frameState->HasKey(nextPageKey))
                {
                    frameState->Remove(nextPageKey);
                    nextPageIndex++;
                    nextPageKey = "Page-" + nextPageIndex;
                }
    
                // Pass the navigation parameter to the new page
                LoadState(this, ref new LoadStateEventArgs(e->Parameter, nullptr));
            }
            else
            {
                // Pass the navigation parameter and preserved page state to the page, using
                // the same strategy for loading suspended state and recreating pages discarded
                // from cache
                LoadState(this, ref new LoadStateEventArgs(e->Parameter, safe_cast<IMap<String^, Object^>^>(frameState->Lookup(_pageKey))));
            }
        }
    }
    
    /// <summary>
    /// Invoked when this page will no longer be displayed in a Frame.
    /// </summary>
    /// <param name="e">Event data that describes how this page was reached.  The Parameter
    /// property provides the group to be displayed.</param>
    void NavigationHelper::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        Page ^page = _page.Resolve<Page>();
        if (page != nullptr)
        {
            auto frameState = SuspensionManager::SessionStateForFrame(page->Frame);
            auto pageState = ref new Map<String^, Object^>();
            SaveState(this, ref new SaveStateEventArgs(pageState));
            frameState->Insert(_pageKey, pageState);
        }
    }
    #pragma endregion
    
  5. Now add code to include the NavigationHelper to the MainPage.xaml.h header file, as well as the DefaultViewModel property that we'll need later.

    //
    // MainPage.xaml.h
    // Declaration of the MainPage class
    //
    
    #pragma once
    
    #include "MainPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
    
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
        /// <summary>
        /// A basic page that provides characteristics common to most applications.
        /// </summary>
        [Windows::Foundation::Metadata::WebHostHidden]
        public ref class MainPage sealed
        {
        public:
            MainPage();
    
            /// <summary>
            /// Gets the view model for this <see cref="Page"/>. 
            /// This can be changed to a strongly typed view model.
            /// </summary>
            property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
            {
                WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
            }
    
            /// <summary>
            /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
            /// </summary>
            property Common::NavigationHelper^ NavigationHelper
            {
                Common::NavigationHelper^ get();
            }
    
        protected:
            virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
            virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
        private:
            void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
            void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
            static WUIX::DependencyProperty^ _defaultViewModelProperty;
            static WUIX::DependencyProperty^ _navigationHelperProperty;
    
        };
    
    }
    
  6. In MainPage.xaml.cpp, add the implementation of the NavigationHelper and stubs for loading and saving state, and DefaultViewModel properties. You will also add the required using namespace directives, so the final code looks like this:

    //
    // MainPage.xaml.cpp
    // Implementation of the MainPage class
    //
    
    #include "pch.h"
    #include "MainPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    using namespace Windows::UI::Xaml::Interop;
    
    // The Basic Page item template is documented at https://go.microsoft.com/fwlink/?LinkID=390556
    
    MainPage::MainPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, 
            ref new Platform::Collections::Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &MainPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &MainPage::SaveState);
    }
    
    DependencyProperty^ MainPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(MainPage::typeid), nullptr);
    
    /// <summary>
    /// Used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ MainPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ MainPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(MainPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ MainPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void MainPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void MainPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
    /// <summary>
    /// Populates the page with content passed during navigation. Any saved state is also
    /// provided when recreating a page from a prior session.
    /// </summary>
    /// <param name="sender">
    /// The source of the event; typically <see cref="NavigationHelper"/>
    /// </param>
    /// <param name="e">Event data that provides both the navigation parameter passed to
    /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested and
    /// a dictionary of state preserved by this page during an earlier
    /// session. The state will be null the first time a page is visited.</param>
    void MainPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
     (void) sender; // Unused parameter
        (void) e; // Unused parameter
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void MainPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void) sender;  // Unused parameter
        (void) e; // Unused parameter
    }
    
  7. Still in MainPage.xaml (Windows Phone 8.1), moving down the page, find the "Title Panel " comment, and remove the entire StackPanel. On the phone, we need the screen real estate to list blog feeds.

  8. Further down the page you’ll see a Grid with this comment: "TODO: Content should be placed within the following grid". Put this ListView inside that Grid:

        <!-- Vertical scrolling item list -->
         <ListView
            x:Name="itemListView"           
            AutomationProperties.AutomationId="itemListView"
            AutomationProperties.Name="Items"
            TabIndex="1" 
            IsItemClickEnabled="True"
            ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
            IsSwipeEnabled="False"
            ItemClick="ItemListView_ItemClick"
            SelectionMode="Single"
            ItemTemplate="{StaticResource ListItemTemplate}">
    
            <ListView.ItemContainerStyle>
                <Style TargetType="FrameworkElement">
                    <Setter Property="Margin" Value="2,0,0,2"/>
                </Style>
            </ListView.ItemContainerStyle>
        </ListView>
    
  9. Now place the cursor over the ItemListView_ItemClick event and press F12 (Go To Definition). Visual Studio will generate an empty event handler function for us. We’ll add some code to that later. For now we just need the function to be generated so that the app will compile.

Part 8: List the posts and show the text view of a selected post

In this part we'll add two pages to the phone app: the page that lists the posts and the page that shows the text version of a selected post. In the Windows app, we just have to add a single page called a SplitPage that will show the list on one side and and the text of the selected post on the other side. First the phone pages.

Add the XAML markup (Phone app FeedPage)

Let’s stay in the phone project and work on the FeedPage, which lists the posts for the feed that the user selects.

Hh465045.wedge(en-us,WIN.10).gif

  1. In FeedPage.xaml (Windows Phone 8.1), add a data context to the Page element:

    DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
    
  2. Now add the CollectionViewSource after the opening Page element:

    <Page.Resources>
        <!-- Collection of items displayed by this page -->
        <CollectionViewSource
        x:Name="itemsViewSource"
        Source="{Binding Items}"/>
    </Page.Resources>
    
  3. In the Grid element, add this StackPanel:

            <!-- TitlePanel -->
            <StackPanel Grid.Row="0" Margin="24,17,0,28">
                <TextBlock Text="{StaticResource AppName}" 
                           Style="{ThemeResource TitleTextBlockStyle}" 
                           Typography.Capitals="SmallCaps"/>
            </StackPanel>
    
  4. Next, add the ListView inside the grid (just after the opening element):

                <!-- Vertical scrolling item list -->
                <ListView
                x:Name="itemListView"
                AutomationProperties.AutomationId="ItemsListView"
                AutomationProperties.Name="Items"
                TabIndex="1"
                Grid.Row="1"
                Margin="-10,-10,0,0" 
                IsItemClickEnabled="True"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"
                ItemClick="ItemListView_ItemClick"
                ItemTemplate="{StaticResource ListItemTemplate}">
    
                    <ListView.ItemContainerStyle>
                        <Style TargetType="FrameworkElement">
                            <Setter Property="Margin" Value="2,0,0,2"/>
                        </Style>
                    </ListView.ItemContainerStyle>
                </ListView>
    

    Note that the ListView ItemsSource property binds to the CollectionViewSource, which binds to our FeedData::Items property which we insert into the DefaultViewModel property in LoadState in the code behind (see below).

  5. There is an ItemClick event declared in the ListView. Put the cursor over it and press F12 to generate the event handler in the code-behind. We'll leave it empty for now.

LoadState and SaveState (Phone app FeedPage)

In MainPage, we didn't have to worry about storing state, because the page always does a full re-initialization from the internet whenever the app starts up for any reason. The other pages need to remember their state. For example, if the app is terminated (unloaded from memory) while FeedPage is showing, when the user navigates back to it, we want it to seem like the app never went away. So we need to remember what feed had been selected. The place to store this data is in local AppData storage, and a good time to store it is when the user clicks on it in MainPage.

There's just one complication here--does the data actually exist yet? If we are navigating to FeedPage from MainPage via a user click, then we know for sure that the selected FeedData object already exists, because it won't appear in the MainPage list otherwise. However, if the app is resuming, the last viewed FeedData object might not be loaded yet when FeedPage attempts to bind to it. So, FeedPage (and other pages) need a way to know when the FeedData is available. The concurrency::task_completion_event is designed for just such a situation. By using it, we can get the FeedData object safely in the same code path regardless of whether we are resuming or navigating fresh from MainPage. From FeedPage, we always get our feed by calling GetCurrentFeedAsync. If we are navigating from MainPage, the event was already set when the user clicked on a feed, and so the method will return the feed immediately. If we are resuming from suspension, the event is set in the FeedDataSource::InitDataSource function, and in that case FeedPage might have to wait a bit for the feed to be reloaded. Waiting is better than crashing in this case. This little complication is the reason for a lot of that complicated asynchronous code in FeedData.cpp and App.xaml.cpp, but if you look at that code closely, you'll see that it's not as complicated as it might appear.

  1. In FeedPage.xaml.cpp, add this namespace to bring the task objects into scope:

    using namespace concurrency;
    
  2. And an #include directive for TextViewerPage.xaml.h:

    #include "TextViewerPage.xaml.h"
    

    The TextViewerPage class definition is required in the call to Navigate, shown below.

  3. Replace the LoadState method with this code:

    void FeedPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to
                // initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
            }, task_continuation_context::use_current());
        }
    }
    

    If we are navigating back to FeedPage from a page further up the page stack, then the page will already be initialized (i.e. DefaultViewModel will have a value for "Feed") and the current feed will already be correctly set. But if we are navigating forward from MainPage, or are resuming, then we will need to get the current feed in order to populate the page with the correct data. GetCurrentFeedAsync will wait if necessary for the feed data to arrive after resuming. We specify the use_current() context to tell the task to get back onto the UI thread before attempting to access the DefaultViewModel dependency property. XAML-related objects in general can't be accessed directly from background threads.

    We don't do anything with SaveState in this page because we get our state from the GetCurrentFeedAsync method whenever the page loads.

  4. Add the declaration of LoadState in the header file FeedPage.xaml.h, add an include directive for "Common\NavigationHelper.h", and add the NavigationHelper and DefaultViewModel properties.

    //
    // FeedPage.xaml.h
    // Declaration of the FeedPage class
    //
    
    #pragma once
    
    #include "FeedPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
    
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
                    /// <summary>
                    /// A basic page that provides characteristics common to most applications.
                    /// </summary>
                    [Windows::Foundation::Metadata::WebHostHidden]
                    public ref class FeedPage sealed
                    {
                    public:
                                    FeedPage();
    
                                    /// <summary>
                                    /// Gets the view model for this <see cref="Page"/>. 
                                    /// This can be changed to a strongly typed view model.
                                    /// </summary>
                                    property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
                                    {
                                                    WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                                    }
    
                                    /// <summary>
                                    /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
                                    /// </summary>
                                    property Common::NavigationHelper^ NavigationHelper
                                    {
                                                    Common::NavigationHelper^ get();
                                    }
    
                    protected:
                                    virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                                    virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
                    private:
                                    void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                                    void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
                                    static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                                    static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
            void ItemListView_ItemClick(Platform::Object^ sender, WUIXControls::ItemClickEventArgs^ e);
        };
    
    }
    
  5. Add the implementation of these properties in FeedPage.xaml.cpp, which now looks like this:

    //
    // FeedPage.xaml.cpp
    // Implementation of the FeedPage class
    //
    
    #include "pch.h"
    #include "FeedPage.xaml.h"
    #include "TextViewerPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace concurrency;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Graphics::Display;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    
    // The Basic Page item template is documented at https://go.microsoft.com/fwlink/?LinkID=390556
    
    FeedPage::FeedPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, 
            ref new Platform::Collections::Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &FeedPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &FeedPage::SaveState);
    }
    
    DependencyProperty^ FeedPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(FeedPage::typeid), nullptr);
    
    /// <summary>
    /// Used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ FeedPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ FeedPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(FeedPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ FeedPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void FeedPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void FeedPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
    /// <summary>
    /// Populates the page with content passed during navigation. Any saved state is also
    /// provided when recreating a page from a prior session.
    /// </summary>
    /// <param name="sender">
    /// The source of the event; typically <see cref="NavigationHelper"/>
    /// </param>
    /// <param name="e">Event data that provides both the navigation parameter passed to
    /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested and
    /// a dictionary of state preserved by this page during an earlier
    /// session. The state will be null the first time a page is visited.</param>
    void FeedPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
    
        (void)sender; // Unused parameter
    
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to 
                // initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
            }, task_continuation_context::use_current());
        }
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void FeedPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender; // Unused parameter
    }
    
    

EventHandlers (Phone app FeedPage)

We handle one event on FeedPage, the ItemClick event that navigates forward to the page where the user can read the post. You already created a stub handler when you pressed F12 on the event name in the XAML.

  1. Let's replace the implementation now with this code.

    void FeedPage::ItemListView_ItemClick(Platform::Object^ sender, ItemClickEventArgs^ e)
    {
        FeedItem^ clickedItem = dynamic_cast<FeedItem^>(e->ClickedItem);
        this->Frame->Navigate(TextViewerPage::typeid, clickedItem->Link->AbsoluteUri);
    }
    
  2. Press F5 to build and run the phone app in the emulator. Now, when you select an item from MainPage, the app should navigate to FeedPage and show a list of feeds. The next step is to display the text for a selected feed.

Hh465045.wedge(en-us,WIN.10).gifAdd the XAML markup (Phone app TextViewerPage)

  1. In the Phone project, in TextViewerPage.xaml, replace the title panel and content grid with this markup that will display the app name (unobtrusively) and the title of the current post, along with a simple text rendering of the contents:

     <!-- TitlePanel -->
            <StackPanel Grid.Row="0" Margin="24,17,0,28">
                <TextBlock Text="{StaticResource AppName}" 
                           Style="{ThemeResource TitleTextBlockStyle}" 
                           Typography.Capitals="SmallCaps"/>
                <TextBlock x:Name="FeedItemTitle" Margin="0,12,0,0" 
                           Style="{StaticResource SubheaderTextBlockStyle}" 
                           TextWrapping="Wrap"/>
            </StackPanel>
    
            <!--TODO: Content should be placed within the following grid-->
            <Grid Grid.Row="1" x:Name="ContentRoot">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
    
                <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Row="1"
                Padding="20,20,20,20"
                HorizontalScrollBarVisibility="Disabled" 
                    VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Disabled" 
                    ScrollViewer.VerticalScrollMode="Enabled"
                ScrollViewer.ZoomMode="Disabled" Margin="4,0,-4,0">
                    <!--Border enables background color for rich text block-->
                    <Border x:Name="contentViewBorder" BorderBrush="#FFFE5815"  
                            Background="AntiqueWhite" BorderThickness="6" Grid.Row="1">
                        <RichTextBlock x:Name="BlogTextBlock" Foreground="Black" 
                                       FontFamily="Segoe WP" FontSize="24" 
                                       Padding="10,10,10,10" 
                                       VerticalAlignment="Bottom" >
                        </RichTextBlock>
                    </Border>
                </ScrollViewer>
            </Grid>
    
  2. In TextViewerPage.xaml.h, add the NavigationHelper and DefaultViewItems properties, and also add a private member m_FeedItem to store a reference to the current feed item after we look it up the first time using the GetFeedItem function that we added to the App class in the previous step.

    Also, add a function RichTextHyperlinkClicked. TextViewerPage.xaml.h should now look like this:

    //
    // TextViewerPage.xaml.h
    // Declaration of the TextViewerPage class
    //
    
    #pragma once
    
    #include "TextViewerPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXDoc = Windows::UI::Xaml::Documents;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
                    /// <summary>
                    /// A basic page that provides characteristics common to most applications.
                    /// </summary>
                    [Windows::Foundation::Metadata::WebHostHidden]
                    public ref class TextViewerPage sealed
                    {
                    public:
                                    TextViewerPage();
    
                                    /// <summary>
                                    /// Gets the view model for this <see cref="Page"/>. 
                                    /// This can be changed to a strongly typed view model.
                                    /// </summary>
                                    property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
                                    {
                                                    WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                                    }
    
                                    /// <summary>
                                    /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
                                    /// </summary>
                                    property Common::NavigationHelper^ NavigationHelper
                                    {
                                                    Common::NavigationHelper^ get();
                                    }
    
    
    
                    protected:
                                    virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                                    virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
                                    void RichTextHyperlinkClicked(WUIXDoc::Hyperlink^ link, 
                                         WUIXDoc::HyperlinkClickEventArgs^ args);
    
                    private:
                                    void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                                    void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
                                    static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                                    static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
    
            FeedItem^ m_feedItem;
        };
    
    }
    

Hh465045.wedge(en-us,WIN.10).gifLoadState and SaveState (Phone app TextViewerPage)

  1. In TextViewerPage.xaml.cpp, add this include directive:

    #include "WebViewerPage.xaml.h"
    
  2. Add these two namespace directives:

    using namespace concurrency;
    using namespace Windows::UI::Xaml::Documents;
    
  3. Add the code for NavigationHelper and DefaultViewModel.

    TextViewerPage::TextViewerPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, 
            ref new Platform::Collections::Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &TextViewerPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &TextViewerPage::SaveState);
    
      //  this->DataContext = DefaultViewModel;
    
    }
    
    DependencyProperty^ TextViewerPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(TextViewerPage::typeid), nullptr);
    
    /// <summary>
    /// Used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ TextViewerPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ TextViewerPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(TextViewerPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ TextViewerPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void TextViewerPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void TextViewerPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
  4. Now replace the implementations of LoadState and SaveState with this code:

    void TextViewerPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        // (void)e; // Unused parameter
    
        auto app = safe_cast<App^>(App::Current);
        app->GetCurrentFeedAsync().then([this, app, e](FeedData^ fd)
        {        
            m_feedItem = app->GetFeedItem(fd, safe_cast<String^>(e->NavigationParameter));
            FeedItemTitle->Text = m_feedItem->Title;
            BlogTextBlock->Blocks->Clear();
            TextHelper^ helper = ref new TextHelper();
    
            auto blocks = helper->
                CreateRichText(m_feedItem->Content, 
                    ref new TypedEventHandler<Hyperlink^, HyperlinkClickEventArgs^>
                    (this, &TextViewerPage::RichTextHyperlinkClicked));
            for (auto b : blocks)
            {
                BlogTextBlock->Blocks->Append(b);
            }
        }, task_continuation_context::use_current());    
    }
    
    void TextViewerPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        e->PageState->Insert("Uri", m_feedItem->Link->AbsoluteUri);
    }
    

    We can't bind to a RichTextBlock so we construct it's contents manually using the TextHelper class. For the sake of simplicity we use the HtmlUtilities::ConvertToText function, which only extracts the text from the feed. As an excercise, you can try parsing the html or xml yourself and appending the image links as well as text to the Blocks collection. The SyndicationClient has a function for parsing XML feeds. Some feeds are well-formed XML and some are not.

Hh465045.wedge(en-us,WIN.10).gifEvent Handlers (Phone app TextViewerPage)

  1. In TextViewerPage, we navigate to the WebViewerPage by means of a Hyperlink in the RichText. This is normally not the way to navigate between pages, but it seems appropriate in this case and it allows us to explore how hyperlinks work. We already added the function signature to TextViewerPage.xaml.h. Now add the implementation in TextViewerPage.xaml.cpp:

    ///<summary>
    /// Invoked when the user clicks on the "Link" text at the top of the rich text 
    /// view of the feed. This navigates to the web page. Identical action to using
    /// the App bar "forward" button.
    ///</summary>
    void TextViewerPage::RichTextHyperlinkClicked(Hyperlink^ hyperLink, 
        HyperlinkClickEventArgs^ args)
    {
        this->Frame->Navigate(WebViewerPage::typeid, m_feedItem->Link->AbsoluteUri);
    }
    
  2. Now set the phone project as the startup project and press F5. You should be able to click on an item in the feed page and navigate to the TextViewerPage where you can read the blog post. There's some interesting stuff in those blogs!

Add the XAML (Windows App SplitPage)

The Windows app behaves differently from the phone app in a few ways. We've already seen how the MainPage.xaml in the Windows project uses an ItemsPage template, which isn't available in phone apps. Now we're going to add a SplitPage, also not available on phone. When a device is in landscape orientation, the SplitPage in the Windows app has a right and a left pane. When the user navigates to the page in our app, they will see the list of feed items in the left pane, and a text rendering of the currently selected feed in the right pane. When the device is in portrait orientation, or the window is not at full width, the Split Page uses VisualStates to behave as if it were two separate pages. This is called "logical page navigation" in the code.

  1. Start off with the following code, which is the xaml for a basic split page that was the default template in Windows 8 projects.

    <Page
        x:Name="pageRoot"
        x:Class="SimpleBlogReader.SplitPage"
        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:SimpleBlogReader"
        xmlns:common="using:SimpleBlogReader.Common"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
    
        <Page.Resources>
            <!-- Collection of items displayed by this page -->
            <CollectionViewSource
            x:Name="itemsViewSource"
            Source="{Binding Items}"/>
        </Page.Resources>
    
        <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"
                              Click="fwdButton_Click"/>
                </Grid>
            </AppBar>
        </Page.TopAppBar>
        <!--
            This grid acts as a root panel for the page that defines two rows:
            * Row 0 contains the back button and page title
            * Row 1 contains the rest of the page layout
        -->
        <Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
            <Grid.ChildrenTransitions>
                <TransitionCollection>
                    <EntranceThemeTransition/>
                </TransitionCollection>
            </Grid.ChildrenTransitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="140"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="primaryColumn" Width="420"/>
                <ColumnDefinition x:Name="secondaryColumn" Width="*"/>
            </Grid.ColumnDefinitions>
    
            <!-- Back button and page title -->
            <Grid x:Name="titlePanel" Grid.ColumnSpan="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button x:Name="backButton" Margin="39,59,39,0" 
                        Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
                            Style="{StaticResource NavigationBackButtonNormalStyle}"
                            VerticalAlignment="Top"
                            AutomationProperties.Name="Back"
                            AutomationProperties.AutomationId="BackButton"
                            AutomationProperties.ItemType="Navigation Button"/>
    
                <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Title}" 
                           Style="{StaticResource HeaderTextBlockStyle}"
                           IsHitTestVisible="false" TextWrapping="NoWrap" 
                           VerticalAlignment="Bottom" Padding="10,10,10,10" Margin="0,0,30,40">
                    <TextBlock.Transitions>
                        <TransitionCollection>
                            <ContentThemeTransition/>
                        </TransitionCollection>
                    </TextBlock.Transitions>
                </TextBlock>
            </Grid>
    
            <!-- 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">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="6">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="60" Height="60">
                                <Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                            </Border>
                            <StackPanel Grid.Column="1" Margin="10,0,0,0">
                                <TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" MaxHeight="40"/>
                                <TextBlock Text="{Binding Subtitle}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="0,0,0,10"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
    
            <!-- Details for selected item -->
            <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Column="1"
                Grid.RowSpan="2"
                Padding="60,0,66,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">
                        <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>
                    <TextBlock Grid.Row="2" Grid.ColumnSpan="2" Margin="0,20,0,0" Text="{Binding Content}" Style="{StaticResource BodyTextBlockStyle}"/>
                </Grid>
            </ScrollViewer>
    
    
            <VisualStateManager.VisualStateGroups>
    
                <!-- Visual states reflect the application's view state -->
                <VisualStateGroup x:Name="ViewStates">
                    <VisualState x:Name="PrimaryView" />
                    <VisualState x:Name="SinglePane">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="primaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="secondaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="itemDetail" 
                                Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="Padding">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="120,0,90,60"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                    <!--
                        When an item is selected and only one pane is shown the details display requires more extensive changes:
                         * Hide the master list and the column it was in
                         * Move item details down a row to make room for the title
                         * Move the title directly above the details
                         * Adjust padding for details
                     -->
                    <VisualState x:Name="SinglePane_Detail">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="primaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="secondaryColumn" 
                                Storyboard.TargetProperty="Width">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="Visibility">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="titlePanel" 
                                Storyboard.TargetProperty="(Grid.Column)">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames 
                                Storyboard.TargetName="itemDetail" 
                                Storyboard.TargetProperty="Padding">
                                <DiscreteObjectKeyFrame KeyTime="0" Value="10,0,10,0"/>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        </Grid>
    </Page>
    
  2. The default page already has its data context and CollectionViewSource set.

    Let's tweak the titlePanel grid so that it spans two columns. This will allow the feed title to display across the full width of the screen:

    <Grid x:Name="titlePanel" Grid.ColumnSpan="2">
    
  3. Now look for the pageTitle TextBlock in this same Grid and change the Binding from Title to Feed.Title.

    Text="{Binding Feed.Title}"
    
  4. Now look for the "Vertical scrolling item list" comment and replace the default ListView 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="10,0,0,60"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                IsSwipeEnabled="False"
                SelectionChanged="ItemListView_SelectionChanged"
                ItemTemplate="{StaticResource ListItemTemplate}">
    
                <ListView.ItemContainerStyle>
                    <Style TargetType="FrameworkElement">
                        <Setter Property="Margin" Value="0,0,0,10"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
    
  5. The details pane of a SplitPage can hold whatever you want. In this app, we'll put a RichTextBlock in it and display a simple text version of the blog post. We can use a utility function provided by the Windows API to parse the HTML from the FeedItem and return a Platform::String, and then we'll use our own utility class to split the returned string into paragraphs and build up rich text elements. This view will not show any images but it loads fast and, if you want to extend this app, you could later add an option to let the user adjust the font and font size.

    Find the ScrollViewer element below the "Details for selected item" comment and delete it. Then paste in this markup:

            <!-- Details for selected item -->
            <Grid x:Name="itemDetailGrid" 
                  Grid.Row="1"
                  Grid.Column="1"
                  Margin="10,10,10,10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <TextBlock x:Name="itemTitle" Margin="10,10,10,10" 
                           DataContext="{Binding SelectedItem, ElementName=itemListView}" 
                           Text="{Binding Title}" 
                           Style="{StaticResource SubheaderTextBlockStyle}"/>
                <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Row="1"
                Padding="20,20,20,20"
                DataContext="{Binding SelectedItem, ElementName=itemListView}"
                HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Enabled"
                ScrollViewer.ZoomMode="Disabled" Margin="4,0,-4,0">
                    <Border x:Name="contentViewBorder" BorderBrush="#FFFE5815" 
                            Background="Honeydew" BorderThickness="5" Grid.Row="1">
                        <RichTextBlock x:Name="BlogTextBlock" Foreground="Black" 
                                       FontFamily="Lucida Sans" 
                                       FontSize="32"
                                       Margin="20,20,20,20">                        
                        </RichTextBlock>
                    </Border>
                </ScrollViewer>
            </Grid>
    

LoadState and SaveState (Windows app SplitPage)

  1. Replace the SplitPage page you created the following code.

    SplitPage.xaml.h should look like this:

    //
    // SplitPage.xaml.h
    // Declaration of the SplitPage class
    //
    
    #pragma once
    
    #include "SplitPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXDoc = Windows::UI::Xaml::Documents;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
           /// <summary>
           /// A page that displays a group title, a list of items within the group, and details for the
           /// currently selected item.
           /// </summary>
           [Windows::Foundation::Metadata::WebHostHidden]
           public ref class SplitPage sealed
           {
           public:
                  SplitPage();
    
                  /// <summary>
                  /// This can be changed to a strongly typed view model.
                  /// </summary>
                  property WFC::IObservableMap<Platform::String^, 
                Platform::Object^>^ DefaultViewModel
                  {
                         WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                  }
    
                  /// <summary>
                  /// NavigationHelper is used on each page to aid in navigation and 
                  /// process lifetime management
                  /// </summary>
                  property Common::NavigationHelper^ NavigationHelper
                  {
                         Common::NavigationHelper^ get();
                  }
           protected:
                  virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                  virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
           private:
                  void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                  void SaveState(Object^ sender, Common::SaveStateEventArgs^ e);
                  bool CanGoBack();
                  void GoBack();
    
    #pragma region Logical page navigation
    
                  // The split page isdesigned so that when the Window does have enough space to show
                  // both the list and the dteails, only one pane will be shown at at time.
                  //
                  // This is all implemented with a single physical page that can represent two logical
                  // pages.  The code below achieves this goal without making the user aware of the
                  // distinction.
    
                  void Window_SizeChanged(Platform::Object^ sender, 
                Windows::UI::Core::WindowSizeChangedEventArgs^ e);
                  void ItemListView_SelectionChanged(Platform::Object^ sender, 
                WUIXControls::SelectionChangedEventArgs^ e);
                  bool UsingLogicalPageNavigation();
                  void InvalidateVisualState();
                  Platform::String^ DetermineVisualState();
    
    #pragma endregion
    
                  static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                  static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
                  static const int MinimumWidthForSupportingTwoPanes = 768;
    
            void fwdButton_Click(Platform::Object^ sender, WUIX::RoutedEventArgs^ e);
            void pageRoot_SizeChanged(Platform::Object^ sender, WUIX::SizeChangedEventArgs^ e);
           };
    }
    

    For SplitPage.xaml.cpp, use the following code as the starting point. This implements a basic split page, and adds the NavigationHelper and SuspensionManager support the same as you added to the other pages, and the SizeChanged event handler the same as a previous page.

    //
    // SplitPage.xaml.cpp
    // Implementation of the SplitPage class
    //
    
    #include "pch.h"
    #include "SplitPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace SimpleBlogReader::Common;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace concurrency;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::ViewManagement;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Documents;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Navigation;
    
    // The Split Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234234
    
    SplitPage::SplitPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, ref new Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this,
            ref new Common::RelayCommand(
            [this](Object^) -> bool
        {
            return CanGoBack();
        },
            [this](Object^) -> void
        {
            GoBack();
        }
            )
            );
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += 
            ref new Common::LoadStateEventHandler(this, &SplitPage::LoadState);
        navigationHelper->SaveState += 
            ref new Common::SaveStateEventHandler(this, &SplitPage::SaveState);
    
        ItemListView->SelectionChanged += 
            ref new SelectionChangedEventHandler(this, &SplitPage::ItemListView_SelectionChanged);
        Window::Current->SizeChanged += 
            ref new WindowSizeChangedEventHandler(this, &SplitPage::Window_SizeChanged);
        InvalidateVisualState();
    
    }
    
    DependencyProperty^ SplitPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(SplitPage::typeid), nullptr);
    
    /// <summary>
    /// used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ SplitPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ SplitPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(SplitPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ SplitPage::NavigationHelper::get()
    {
        //        return _navigationHelper;
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Page state management
    
    /// <summary>
    /// Populates the page with content passed during navigation.  Any saved state is also
    /// provided when recreating a page from a prior session.
    /// </summary>
    /// <param name="navigationParameter">The parameter value passed to
    /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested.
    /// </param>
    /// <param name="pageState">A map of state preserved by this page during an earlier
    /// session.  This will be null the first time a page is visited.</param>
    void SplitPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, app, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
    
                if (e->PageState == nullptr)
                {
                    // 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);
                    }
                }
                else
                {
                    auto itemUri = safe_cast<String^>(e->PageState->Lookup("SelectedItemUri"));
                    auto app = safe_cast<App^>(App::Current);
                    auto selectedItem = app->GetFeedItem(fd, itemUri);
    
                    if (selectedItem != nullptr)
                    {
                        this->itemsViewSource->View->MoveCurrentTo(selectedItem);
                    }
                }
            }, task_continuation_context::use_current());
        }
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void SplitPage::SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e)
    {
        if (itemsViewSource->View != nullptr)
        {
            auto selectedItem = itemsViewSource->View->CurrentItem;
            if (selectedItem != nullptr)
            {
                auto feedItem = safe_cast<FeedItem^>(selectedItem);
                e->PageState->Insert("SelectedItemUri", feedItem->Link->AbsoluteUri);
            }
        }
    }
    
    #pragma endregion
    
    #pragma region Logical page navigation
    
    // Visual state management typically reflects the four application view states directly (full
    // screen landscape and portrait plus snapped and filled views.)  The split page is designed so
    // that the snapped and portrait view states each have two distinct sub-states: either the item
    // list or the details are displayed, but not both at the same time.
    //
    // This is all implemented with a single physical page that can represent two logical pages.
    // The code below achieves this goal without making the user aware of the distinction.
    
    /// <summary>
    /// Invoked to determine whether the page should act as one logical page or two.
    /// </summary>
    /// <returns>True when the current view state is portrait or snapped, false
    /// otherwise.</returns>
    bool SplitPage::CanGoBack()
    {
        if (UsingLogicalPageNavigation() && ItemListView->SelectedItem != nullptr)
        {
            return true;
        }
        else
        {
            return NavigationHelper->CanGoBack();
        }
    }
    
    void SplitPage::GoBack()
    {
        if (UsingLogicalPageNavigation() && ItemListView->SelectedItem != nullptr)
        {
            // When logical page navigation is in effect and there's a selected item that
            // item's details are currently displayed.  Clearing the selection will return to
            // the item list.  From the user's point of view this is a logical backward
            // navigation.
            ItemListView->SelectedItem = nullptr;
        }
        else
       {
            NavigationHelper->GoBack();
        }
    }
    
    /// <summary>
    /// Invoked with the Window changes size
    /// </summary>
    /// <param name="sender">The current Window</param>
    /// <param name="e">Event data that describes the new size of the Window</param>
    void SplitPage::Window_SizeChanged(Platform::Object^ sender, WindowSizeChangedEventArgs^ e)
    {
        InvalidateVisualState();
    }
    
    /// <summary>
    /// Invoked when an item within the list is selected.
    /// </summary>
    /// <param name="sender">The GridView displaying the selected item.</param>
    /// <param name="e">Event data that describes how the selection was changed.</param>
    void SplitPage::ItemListView_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e)
    {
           if (UsingLogicalPageNavigation())
           {
                  InvalidateVisualState();
           }
    }
    
    /// <summary>
    /// Invoked to determine whether the page should act as one logical page or two.
    /// </summary>
    /// <returns>True if the window should show act as one logical page, false
    /// otherwise.</returns>
    bool SplitPage::UsingLogicalPageNavigation()
    {
        return Window::Current->Bounds.Width <= MinimumWidthForSupportingTwoPanes;
    }
    
    void SplitPage::InvalidateVisualState()
    {
        auto visualState = DetermineVisualState();
        VisualStateManager::GoToState(this, visualState, false);
        NavigationHelper->GoBackCommand->RaiseCanExecuteChanged();
    }
    
    /// <summary>
    /// Invoked to determine the name of the visual state that corresponds to an application
    /// view state.
    /// </summary>
    /// <returns>The name of the desired visual state.  This is the same as the name of the
    /// view state except when there is a selected item in portrait and snapped views where
    /// this additional logical page is represented by adding a suffix of _Detail.</returns>
    Platform::String^ SplitPage::DetermineVisualState()
    {
        if (!UsingLogicalPageNavigation())
            return "PrimaryView";
    
        // Update the back button's enabled state when the view state changes
        auto logicalPageBack = UsingLogicalPageNavigation() 
            && ItemListView->SelectedItem != nullptr;
    
        return logicalPageBack ? "SinglePane_Detail" : "SinglePane";
    }
    
    #pragma endregion
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void SplitPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void SplitPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    
    }
    #pragma endregion
    
    void SimpleBlogReader::SplitPage::fwdButton_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        // 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 itemUri = safe_cast<String^>(selectedItem->Link->AbsoluteUri);
            this->Frame->Navigate(WebViewerPage::typeid, itemUri);
        }
    }
    
    /// <summary>
    /// 
    /// 
    /// </summary>
    void SimpleBlogReader::SplitPage::pageRoot_SizeChanged(
        Platform::Object^ sender,
        SizeChangedEventArgs^ e)
    {
        if (e->NewSize.Height / e->NewSize.Width >= 1)
        {
            VisualStateManager::GoToState(this, "SinglePane", true);
        }
        else
        {
            VisualStateManager::GoToState(this, "PrimaryView", true);
        }
    }
    
  2. In SplitPage.xaml.cpp, add this using directive:

    using namespace Windows::UI::Xaml::Documents;
    
  3. Now replace LoadState and SaveState with this code:

    void SplitPage::LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e)
    {
        if (!this->DefaultViewModel->HasKey("Feed"))
        {
            auto app = safe_cast<App^>(App::Current);
            app->GetCurrentFeedAsync().then([this, app, e](FeedData^ fd)
            {
                // Insert into the ViewModel for this page to initialize itemsViewSource->View
                this->DefaultViewModel->Insert("Feed", fd);
                this->DefaultViewModel->Insert("Items", fd->Items);
    
                if (e->PageState == nullptr)
                {
                    // 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);
                    }
                }
                else
                {
                    auto itemUri = safe_cast<String^>(e->PageState->Lookup("SelectedItemUri"));
                    auto app = safe_cast<App^>(App::Current);
                    auto selectedItem = GetFeedItem(fd, itemUri);
    
                    if (selectedItem != nullptr)
                    {
                        this->itemsViewSource->View->MoveCurrentTo(selectedItem);
                    }
                }
            }, task_continuation_context::use_current());
        }
    }
    
    /// <summary>
    /// Preserves state associated with this page in case the application is suspended or the
    /// page is discarded from the navigation cache.  Values must conform to the serialization
    /// requirements of <see cref="SuspensionManager::SessionState"/>.
    /// </summary>
    /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
    /// <param name="e">Event data that provides an empty dictionary to be populated with
    /// serializable state.</param>
    void SplitPage::SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e)
    {
        if (itemsViewSource->View != nullptr)
        {
            auto selectedItem = itemsViewSource->View->CurrentItem;
            if (selectedItem != nullptr)
            {
                auto feedItem = safe_cast<FeedItem^>(selectedItem);
                e->PageState->Insert("SelectedItemUri", feedItem->Link->AbsoluteUri);
            }
        }
    }
    

    Note that we're using the GetCurrentFeedAsync method that we added to the shared project earlier. One difference between this page and the phone page is that now we keep track of the selected item. In SaveState, we insert the current selected item into the PageState object, so that SuspensionManager will persist it as necessary and it will be available to us in the PageState object again when LoadState is called. We'll need that string to look up the current FeedItem in the current Feed.

Event Handlers (Windows app SplitPage)

When the selected item changes, the details pane will use the TextHelper class to render the text.

  1. In SplitPage.xaml.cpp, add these #include directives:

    #include "TextHelper.h"
    #include "WebViewerPage.xaml.h"
    
  2. Replace the default SelectionChanged event handler stub with this:

    void SimpleBlogReader::SplitPage::ItemListView_SelectionChanged(
        Platform::Object^ sender,
        SelectionChangedEventArgs^ e)
    {
        if (UsingLogicalPageNavigation())
        {
            InvalidateVisualState();
        }
    
        // 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)
        {
            BlogTextBlock->Blocks->Clear();
            TextHelper^ helper = ref new TextHelper();
            auto blocks = helper->CreateRichText(fi->Content, 
                ref new TypedEventHandler<Hyperlink^, 
                HyperlinkClickEventArgs^>(this, &SplitPage::RichTextHyperlinkClicked));
            for (auto b : blocks)
            {
                BlogTextBlock->Blocks->Append(b);
            }
        }
    }
    

    This function specifies a callback that will be passed to a Hyperlink that we create in the rich text.

  3. Add this private member function in SplitPage.xaml.h:

    void RichTextHyperlinkClicked(Windows::UI::Xaml::Documents::Hyperlink^ link, 
        Windows::UI::Xaml::Documents::HyperlinkClickEventArgs^ args);
    
  4. And this implementation in SplitPage.xaml.cpp:

    /// <summary>
    ///  Navigate to the appropriate destination page, and configure the new page
    ///  by passing required information as a navigation parameter.
    /// </summary>
    void SplitPage::RichTextHyperlinkClicked(
        Hyperlink^ hyperLink,
        HyperlinkClickEventArgs^ args)
    {
    
        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 itemUri = safe_cast<String^>(selectedItem->Link->AbsoluteUri);
            this->Frame->Navigate(WebViewerPage::typeid, itemUri);
        }
    }
    

    This function in turn references the next page in the navigation stack. Now you can press F5 and see the text update as the selection changes. Run in the simulator and rotate the virtual device to see that the default VisualState objects handle both portrait and landscape orientations exactly as expected. Click on the Link text in the blog text and navigate to the WebViewerPage. Of course there's no content there yet; we'll get to that after we catch up on the phone project.

About back navigation

You may have noticed that in the Windows app the SplitPage provides a back navigation button that takes you back to the MainPage without any additional coding required on your part. On the phone, the back button functionality is provided by the hardware back button, not software buttons. The phone back button navigation is handled by the NavigationHelper class in the Common folder. Search for "BackPressed" (Ctrl + Shift + F) in your solution to see the relevant code. Again, there is nothing extra that you have to do here. It just works!

Part 9: Add a web view of the selected post.

The last page we'll add is one that will show the blog post in its original web page. Sometimes a reader might want to see the pictures, too! The disadvantage of viewing web pages is that text can be difficult to read on a phone screen, and not all web pages are formatted well for mobile devices. Sometimes the margins extend off the side of the screen and require a lot of horizontal scrolling. Our WebViewerPage page is relatively simple. We'll just add a WebView control in the page and let it do all the work. We'll start with the phone project:

Hh465045.wedge(en-us,WIN.10).gifAdd the XAML (Phone app WebViewerPage)

  • In WebViewerPage.xaml, add the title panel and contentRoot Grid:

            <!-- TitlePanel -->
            <StackPanel Grid.Row="0" Margin="10,10,10,10">
                <TextBlock Text="{StaticResource AppName}" 
                           Style="{ThemeResource TitleTextBlockStyle}" 
                           Typography.Capitals="SmallCaps"/>
            </StackPanel>
    
            <!--TODO: Content should be placed within the following grid-->
            <Grid Grid.Row="1" x:Name="ContentRoot">
                <!-- Back button and page title -->
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
    
                <!--This will render while web page is still downloading, 
                indicating that something is happening-->
                <TextBlock x:Name="pageTitle" Text="{Binding Title}" Grid.Column="1" 
                           IsHitTestVisible="false" 
                           TextWrapping="WrapWholeWords"  
                           VerticalAlignment="Center"  
                           HorizontalAlignment="Center"  
                           Margin="40,20,40,20"/>
    
            </Grid>
    

Hh465045.wedge(en-us,WIN.10).gifLoadState and SaveState ( Phone app WebViewerPage)

  1. Start WebViewerPage just like all the other pages, by providing NavigationHelper and DefaultItems support in the WebViewerPage.xaml.h file and the implementation in WebViewerPage.xaml.cpp.

    WebViewerPage.xaml.h should start like this:

    //
    // WebViewerPage.xaml.h
    // Declaration of the WebViewerPage class
    //
    
    #pragma once
    
    #include "WebViewerPage.g.h"
    #include "Common\NavigationHelper.h"
    
    namespace SimpleBlogReader
    {
        namespace WFC = Windows::Foundation::Collections;
        namespace WUIX = Windows::UI::Xaml;
        namespace WUIXNav = Windows::UI::Xaml::Navigation;
        namespace WUIXControls = Windows::UI::Xaml::Controls;
    
                    /// <summary>
                    /// A basic page that provides characteristics common to most applications.
                    /// </summary>
                    [Windows::Foundation::Metadata::WebHostHidden]
                    public ref class WebViewerPage sealed
                    {
                    public:
                                    WebViewerPage();
    
                                    /// <summary>
                                    /// This can be changed to a strongly typed view model.
                                    /// </summary>
                                    property WFC::IObservableMap<Platform::String^, Platform::Object^>^ DefaultViewModel
                                    {
                                                    WFC::IObservableMap<Platform::String^, Platform::Object^>^  get();
                                    }
    
                                    /// <summary>
                                    /// NavigationHelper is used on each page to aid in navigation and 
                                    /// process lifetime management
                                    /// </summary>
                                    property Common::NavigationHelper^ NavigationHelper
                                    {
                                                    Common::NavigationHelper^ get();
                                    }
    
                    protected:
                                    virtual void OnNavigatedTo(WUIXNav::NavigationEventArgs^ e) override;
                                    virtual void OnNavigatedFrom(WUIXNav::NavigationEventArgs^ e) override;
    
                    private:
                                    void LoadState(Platform::Object^ sender, Common::LoadStateEventArgs^ e);
                                    void SaveState(Platform::Object^ sender, Common::SaveStateEventArgs^ e);
    
                                    static Windows::UI::Xaml::DependencyProperty^ _defaultViewModelProperty;
                                    static Windows::UI::Xaml::DependencyProperty^ _navigationHelperProperty;
    
    
                    };
    }
    

    WebViewerPage.xaml.cpp should start like this:

    //
    // WebViewerPage.xaml.cpp
    // Implementation of the WebViewerPage class
    //
    
    #include "pch.h"
    #include "WebViewerPage.xaml.h"
    
    using namespace SimpleBlogReader;
    using namespace concurrency;
    
    using namespace Platform;
    using namespace Platform::Collections;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Collections;
    
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Controls;
    using namespace Windows::UI::Xaml::Controls::Primitives;
    using namespace Windows::UI::Xaml::Data;
    using namespace Windows::UI::Xaml::Input;
    using namespace Windows::UI::Xaml::Interop;
    using namespace Windows::UI::Xaml::Media;
    using namespace Windows::UI::Xaml::Media::Animation;
    using namespace Windows::UI::Xaml::Navigation;
    
    // The Basic Page item template is documented at 
    // https://go.microsoft.com/fwlink/?LinkId=234237
    
    WebViewerPage::WebViewerPage()
    {
        InitializeComponent();
        SetValue(_defaultViewModelProperty, ref new Map<String^, Object^>(std::less<String^>()));
        auto navigationHelper = ref new Common::NavigationHelper(this);
        SetValue(_navigationHelperProperty, navigationHelper);
        navigationHelper->LoadState += ref new Common::LoadStateEventHandler(this, &WebViewerPage::LoadState);
        navigationHelper->SaveState += ref new Common::SaveStateEventHandler(this, &WebViewerPage::SaveState);
    }
    
    DependencyProperty^ WebViewerPage::_defaultViewModelProperty =
    DependencyProperty::Register("DefaultViewModel",
    TypeName(IObservableMap<String^, Object^>::typeid), TypeName(WebViewerPage::typeid), nullptr);
    
    /// <summary>
    /// used as a trivial view model.
    /// </summary>
    IObservableMap<String^, Object^>^ WebViewerPage::DefaultViewModel::get()
    {
        return safe_cast<IObservableMap<String^, Object^>^>(GetValue(_defaultViewModelProperty));
    }
    
    DependencyProperty^ WebViewerPage::_navigationHelperProperty =
    DependencyProperty::Register("NavigationHelper",
    TypeName(Common::NavigationHelper::typeid), TypeName(WebViewerPage::typeid), nullptr);
    
    /// <summary>
    /// Gets an implementation of <see cref="NavigationHelper"/> designed to be
    /// used as a trivial view model.
    /// </summary>
    Common::NavigationHelper^ WebViewerPage::NavigationHelper::get()
    {
        return safe_cast<Common::NavigationHelper^>(GetValue(_navigationHelperProperty));
    }
    
    #pragma region Navigation support
    
    /// The methods provided in this section are simply used to allow
    /// NavigationHelper to respond to the page's navigation methods.
    /// 
    /// Page specific logic should be placed in event handlers for the  
    /// <see cref="NavigationHelper::LoadState"/>
    /// and <see cref="NavigationHelper::SaveState"/>.
    /// The navigation parameter is available in the LoadState method 
    /// in addition to page state preserved during an earlier session.
    
    void WebViewerPage::OnNavigatedTo(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedTo(e);
    }
    
    void WebViewerPage::OnNavigatedFrom(NavigationEventArgs^ e)
    {
        NavigationHelper->OnNavigatedFrom(e);
    }
    
    #pragma endregion
    
    
  2. In WebViewerPage.xaml.h, add this private member variable:

    Windows::Foundation::Uri^ m_feedItemUri;
    
  3. In WebViewerPage.xaml.cpp, replace LoadState and SaveState with this code:

    void WebViewerPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        // Run the PopInThemeAnimation. 
        Storyboard^ sb = dynamic_cast<Storyboard^>(this->FindName("PopInStoryboard"));
        if (sb != nullptr)
        {
            sb->Begin();
        }
    
        if (e->PageState == nullptr)
        {
            m_feedItemUri = safe_cast<String^>(e->NavigationParameter);
            contentView->Navigate(ref new Uri(m_feedItemUri));
        }
        // We are resuming from suspension:
        else
        {
            m_feedItemUri = safe_cast<String^>(e->PageState->Lookup("FeedItemUri"));
            contentView->Navigate(ref new Uri(m_feedItemUri));
        }
    }
    
    void WebViewerPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
        (void)e; // Unused parameter
        e->PageState->Insert("FeedItemUri", m_feedItemUri);
    }
    

    Note the gratuitious animation at the start of the function. You can read more about animations on the Windows Developer Center. Note that here again we have to deal with the two possible ways that we might be arriving at this page. If we're waking up, then we have to go look up our state.

That's it! Press F5 and now you can navigate from the TextViewerPage to the WebViewerPage!

Now go back to the Windows project. This is going to be very similar to what we just did for the phone.

Hh465045.wedge(en-us,WIN.10).gifAdd the XAML (Windows app WebViewerPage)

  1. In WebViewerPage.xaml, add a SizeChanged event to the Page element, and call it pageRoot_SizeChanged. Put the insertion point on it and press F12 to generate the code-behind.

  2. Find the "Back button and page title " grid and delete the TextBlock. The page title will show on the web page so we don't need it taking up space here.

  3. Now, immediately after that back button grid, add the Border with the WebView :

    <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" 
                    Grid.Row="1" Margin="20,20,20,20">
                <WebView x:Name="contentView" ScrollViewer.HorizontalScrollMode="Enabled"
                         ScrollViewer.VerticalScrollMode="Enabled"/>
            </Border> 
    

    A WebView control does a lot of work for free, but it does have its quirks that make it different in some ways from other XAML controls. You should definitely read up on it if you are going to be using it extensively in an app.

Hh465045.wedge(en-us,WIN.10).gifAdd member variable

  1. Add the following private declaration in WebViewerPage.xaml.h:

    Platform::String^ m_feedItemUri;
    

Hh465045.wedge(en-us,WIN.10).gifLoadState and SaveState (Windows app WebViewerPage)

  1. Replace the LoadState and SaveState functions with this code, which is very similar to the phone page:

    void WebViewerPage::LoadState(Object^ sender, Common::LoadStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        // Run the PopInThemeAnimation. 
        auto sb = dynamic_cast<Storyboard^>(this->FindName("PopInStoryboard"));
        if (sb != nullptr)
        {
            sb->Begin();
        }
    
        // We are navigating forward from SplitPage
        if (e->PageState == nullptr)
        {
            m_feedItemUri = safe_cast<String^>(e->NavigationParameter);
            contentView->Navigate(ref new Uri(m_feedItemUri));
        }
    
        // We are resuming from suspension:
        else
        {
            contentView->Navigate(
                ref new Uri(safe_cast<String^>(e->PageState->Lookup("FeedItemUri")))
                );
        }
    }
    
    void WebViewerPage::SaveState(Object^ sender, Common::SaveStateEventArgs^ e)
    {
        (void)sender;   // Unused parameter
    
        // Store the info needed to reconstruct the page on back navigation,
        // or in case we are terminated.
        e->PageState->Insert("FeedItemUri", m_feedItemUri);
    }
    

    \

  2. Set the Windows project as startup project and press F5. When you click the link on TextViewerPage, you should be taken to the WebViewerPage, and when you click on the WebViewerPage back button, you should go back to TextViewerPage.

Part 10: Add and remove feeds

The app works great now on both Windows and Phone, assuming that a user never wants to read anything other than the three feeds we have hard-coded into it. But as a final step, let’s get real and enable the user to add and delete feeds of their own choosing. We'll show them some default feeds so that the screen is not blank when they first start the app. Then we'll add some buttons to enable them to add and delete feeds. Of course, we'll have to store the list of user feeds so that the persist from session to session. This is a good time to learn about app local data.

As a first step, we'll still need to store some default feeds for the first time the app starts up. But instead of hard coding those, we can put them in a string resource file where the ResourceLoader can find them. We need those resources to get compiled into both the Windows and the phone app, so we’ll create the .resw file in the shared project.

Hh465045.wedge(en-us,WIN.10).gifAdd string resources:

  1. In Solution Explorer, select the shared project, then right click and add a new item. In the left pane choose Resource and then in the middle pane choose Resources File (.resw). (Don’t choose the .rc file because that’s for desktop apps.) Leave the default name or give it any name. Then click on Add.

  2. Add the following name-value pairs:

    The resource editor should look like this when you are done.

    String resources

Hh465045.wedge(en-us,WIN.10).gifAdd the shared code for adding and removing feeds

  1. We'll add the code for loading the URLs to the FeedDataSource class. In feeddata.h, add this private member function to FeedDataSource:

    concurrency::task<Windows::Foundation::Collections::IVector<Platform::String^>^> GetUserURLsAsync();
    
  2. Add these statements to FeedData.cpp

    using namespace Windows::Storage;
    using namespace Windows::Storage::Streams;
    
  3. And then add the implementation:

    /// <summary>
    /// The first time the app runs, the default feed URLs are loaded from the local resources
    /// into a text file that is stored in the app folder. All subsequent additions and lookups 
    /// are against that file. The method has to return a task because the file access is an 
    /// async operation, and the call site needs to be able to continue from it with a .then method.
    /// </summary>
    
    task<IVector<String^>^> FeedDataSource::GetUserURLsAsync()
    {
    
        return create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("Feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([](StorageFile^ file)
        {
            return FileIO::ReadLinesAsync(file);
        }).then([](IVector<String^>^ t)
        {
            if (t->Size == 0)
            {
                // The data file is new, so we'll populate it with the 
                // default URLs that are stored in the apps resources.
                auto loader = ref new Resources::ResourceLoader();
    
                t->Append(loader->GetString("URL_1\n"));
                t->Append(loader->GetString("URL_2"));
                t->Append(loader->GetString("URL_3"));
    
                // Before we return the URLs, let's create the new file asynchronously 
                //  for use next time. We don't need the result of the operation now 
                // because we already have vec, so we can just kick off the task to
                // run whenever it gets scheduled.
                create_task(ApplicationData::Current->LocalFolder->
                    CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
                    .then([t](StorageFile^ file)
                {
                    OutputDebugString(L"append lines async\n");
                    FileIO::AppendLinesAsync(file, t);
                });
            }
    
            // Return the URLs
            return create_task([t]()
            {
                OutputDebugString(L"returning t\n");
                return safe_cast<IVector<String^>^>(t);
            });
        });
    }
    

    GetUserURLsAsync will look to see if the feeds.txt file exists. If not, it creates it and adds the URLs from the string resources. Any files the user adds will go into the feeds.txt file. Since all file writing operations are asynchronous, we use a task and a .then continuation to ensure that the async work is done before we try to access the file data.

  4. Now replace the old InitDataSource implementation with this code that calls GetUerURLsAsync:

    ///<summary>
    /// Retrieve the data for each atom or rss feed and put it into our custom data structures.
    ///</summary>
    void FeedDataSource::InitDataSource()
    {
        auto urls = GetUserURLsAsync()
            .then([this](IVector<String^>^ urls)
        {
            // Populate the list of feeds.
            SyndicationClient^ client = ref new SyndicationClient();
            for (auto url : urls)
            {
                RetrieveFeedAndInitData(url, client);
            }
        });
    }
    
  5. The functions to add and remove feeds are the same on Windows and on Phone, so we'll put them in the App class. In App.xaml.h,

  6. Add these internal members:

    void AddFeed(Platform::String^ feedUri);
    void RemoveFeed(Platform::String^ feedUri);
    
  7. In App.xaml.cpp, add this namespace:

    using namespace Platform::Collections;
    
  8. In App.xaml.cpp:

    void App::AddFeed(String^ feedUri)
    {
        auto feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        auto client = ref new Windows::Web::Syndication::SyndicationClient();
    
        // The UI is data-bound to the items collection and will update automatically
        // after we append to the collection.
        feedDataSource->RetrieveFeedAndInitData(feedUri, client);
    
        // Add the uri to the roaming data. The API requires an IIterable so we have to 
        // put the uri in a Vector.
        Vector<String^>^ vec = ref new Vector<String^>();
        vec->Append(feedUri);
        concurrency::create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([vec](StorageFile^ file)
        {
            FileIO::AppendLinesAsync(file, vec);
        });
    }
    void App::RemoveFeed(Platform::String^ feedTitle)
    {
        // Create a new list of feeds, excluding the one the user selected.
        auto feedDataSource = 
            safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource"));
        int feedListIndex = -1;
        Vector<String^>^  newFeeds = ref new Vector<String^>();
        for (unsigned int i = 0; i < feedDataSource->Feeds->Size; ++i)
        {
            if (feedDataSource->Feeds->GetAt(i)->Title == feedTitle)
            {
                feedListIndex = i;
            }
            else
            {
                newFeeds->Append(feedDataSource->Feeds->GetAt(i)->Uri);
            }
        }
    
        // Delete the selected item from the list view and the Feeds collection.
        feedDataSource->Feeds->RemoveAt(feedListIndex);
    
        // Overwrite the old data file with the new list.
        create_task(ApplicationData::Current->LocalFolder->
            CreateFileAsync("feeds.txt", CreationCollisionOption::OpenIfExists))
            .then([newFeeds](StorageFile^ file)
        {
            FileIO::WriteLinesAsync(file, newFeeds);
        });
    }
    

Hh465045.wedge(en-us,WIN.10).gifAdd the XAML Markup for add and remove buttons (Windows 8.1)

  1. The buttons for adding and removing feeds belong on the MainPage. We'll put the buttons in a TopAppBar in the Windows app and a BottomAppBar in the phone app (phone apps don't have top app bars). In the Windows project, in MainPage.xaml: add the TopAppBar right after the Page.Resources node:

    <Page.TopAppBar>
            <CommandBar x:Name="cmdBar" IsSticky="False" Padding="10,0,10,0">
    
                <AppBarButton x:Name="addButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Add">
                    <Button.Flyout>
                        <Flyout Placement="Top">
                            <Grid>
                                <StackPanel>
                                    <TextBox x:Name="tbNewFeed" Width="400"/>
                                    <Button Click="AddFeed_Click">Add feed</Button>
                                </StackPanel>
                            </Grid>
                        </Flyout>
                    </Button.Flyout>
                </AppBarButton>
    
                <AppBarButton x:Name="removeButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Remove"
                              Click="removeFeed_Click"/>
    
                <!--These buttons appear when the user clicks the remove button to 
                signal that they want to remove a feed. Delete removes the feed(s)  
                and returns to the normal visual state and cancel just returns 
                to the normal state. -->
                <AppBarButton x:Name="deleteButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Delete" Click="deleteButton_Click"/>
    
                <AppBarButton x:Name="cancelButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Cancel"
                              Click="cancelButton_Click"/>
            </CommandBar>
        </Page.TopAppBar>
    
  2. In each of the four Click event handler names (add, remove, delete, cancel), put the cursor on the handler name and press F12 to generate the functions in the code-behind.

  3. Add this second VisualStateGroup inside the <VisualStateManager.VisualStateGroups> element:

    <VisualStateGroup x:Name="SelectionStates">
        <VisualState x:Name="Normal"/>
            <VisualState x:Name="Checkboxes">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" 
                            Storyboard.TargetProperty="SelectionMode">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="Multiple"/>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" 
                            Storyboard.TargetProperty="IsItemClickEnabled">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
                    </ObjectAnimationUsingKeyFrames>                  
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="cmdBar" 
                             Storyboard.TargetProperty="IsSticky">
                         <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
                    </ObjectAnimationUsingKeyFrames>
                 </Storyboard>
          </VisualState>
    </VisualStateGroup>
    

Hh465045.wedge(en-us,WIN.10).gifAdd the event handlers for adding and removing feeds (Windows 8.1):

  • In MainPage.xaml.cpp, replace four the event handler stubs with this code:

    /// <summary>
    /// Invoked when the user clicks the "add" button to add a new feed.  
    /// Retrieves the feed data, updates the UI, adds the feed to the ListView
    /// and appends it to the data file.
    /// </summary>
    void MainPage::AddFeed_Click(Object^ sender, RoutedEventArgs^ e)
    {
        auto app = safe_cast<App^>(App::Current);
        app->AddFeed(tbNewFeed->Text);
    }
    
    /// <summary>
    /// Invoked when the user clicks the remove button. This changes the grid or list
    ///  to multi-select so that clicking on an item adds a check mark to it without 
    /// any navigation action. This method also makes the "delete" and  "cancel" buttons
    /// visible so that the user can delete selected items, or cancel the operation.
    /// </summary>
    void MainPage::removeFeed_Click(Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Checkboxes", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
    }
    
    ///<summary>
    /// Invoked when the user presses the "trash can" delete button on the app bar.
    ///</summary>
    void SimpleBlogReader::MainPage::deleteButton_Click(Object^ sender, RoutedEventArgs^ e)
    {
    
        // Determine whether listview or gridview is active
        IVector<Object^>^ itemsToDelete;
        if (itemListView->ActualHeight > 0)
        {
            itemsToDelete = itemListView->SelectedItems;
        }
        else
        {
            itemsToDelete = itemGridView->SelectedItems;
        }
    
        for (auto item : itemsToDelete)
        {       
            // Get the feed the user selected.
            Object^ proxy = safe_cast<Object^>(item);
            FeedData^ item = safe_cast<FeedData^>(proxy);
    
            // Remove it from the data file and app-wide feed collection
            auto app = safe_cast<App^>(App::Current);
            app->RemoveFeed(item->Title);
        }
    
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    }
    
    ///<summary>
    /// Invoked when the user presses the "X" cancel button on the app bar. Returns the app 
    /// to the state where clicking on an item causes navigation to the feed.
    ///</summary>
    void MainPage::cancelButton_Click(Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    }
    

    Press F5 with the Windows project as startup project. You can see that each of these member functions sets the visibility property on the buttons to the appropriate value, and then goes to the Normal Visual State.

Hh465045.wedge(en-us,WIN.10).gifAdd the XAML markup for add and remove buttons (Windows Phone 8.1)

  1. Add the bottom app bar with the buttons after the Page.Resources node:

     <Page.BottomAppBar>
    
            <CommandBar x:Name="cmdBar" Padding="10,0,10,0">
    
                <AppBarButton x:Name="addButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Add"
                              >
                    <Button.Flyout>
                        <Flyout Placement="Top">
                            <Grid Background="Black">
                                <StackPanel>
                                    <TextBox x:Name="tbNewFeed" Width="400"/>
                                    <Button Click="AddFeed_Click">Add feed</Button>
                                </StackPanel>
                            </Grid>
                        </Flyout>
                    </Button.Flyout>
    
                </AppBarButton>
                <AppBarButton x:Name="removeButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Icon="Remove"
                              Click="removeFeed_Click"/>
    
    
                <!--These buttons appear when the user clicks the remove button to 
                signal that they want to remove a feed. Delete removes the feed(s)  
                and returns to the normal visual state. Cancel just returns to the normal state. -->
                <AppBarButton x:Name="deleteButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Delete" Click="deleteButton_Click"/>
    
    
                <AppBarButton x:Name="cancelButton" Height="95" Margin="20,0,20,0"
                              HorizontalAlignment="Right"
                              Visibility="Collapsed"
                              Icon="Cancel"
                              Click="cancelButton_Click"/>
            </CommandBar>
        </Page.BottomAppBar>
    
  2. Press F12 on each of the Click event names to generate the code-behind.

  3. Add the "Checkboxes" VisualStateGroup so that the entire VisualStateGroups node looks like this:

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="SelectionStates">
            <VisualState x:Name="Normal"/>
            <VisualState x:Name="Checkboxes">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="SelectionMode">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="Multiple"/>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemListView" 
                                Storyboard.TargetProperty="IsItemClickEnabled">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
                    </ObjectAnimationUsingKeyFrames>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    

Hh465045.wedge(en-us,WIN.10).gifAdd the event handlers for the add and remove feed buttons (Windows Phone 8.1)

  • In MainPage.xaml.cpp (WIndows Phone 8.1) replaced the stub event handlers that you just created with this code:

    void MainPage::AddFeed_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        if (tbNewFeed->Text->Length() > 9)
        {
            auto app = static_cast<App^>(App::Current);
            app->AddFeed(tbNewFeed->Text);
        }
    }
    
    
    void MainPage::removeFeed_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Checkboxes", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
    }
    
    
    void MainPage::deleteButton_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        for (auto item : ItemListView->SelectedItems)
        {
            // Get the feed the user selected.
            Object^ proxy = safe_cast<Object^>(item);
            FeedData^ item = safe_cast<FeedData^>(proxy);
    
            // Remove it from the data file and app-wide feed collection
            auto app = safe_cast<App^>(App::Current);
            app->RemoveFeed(item->Title);
        }
    
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    
    }
    
    void MainPage::cancelButton_Click(Platform::Object^ sender, RoutedEventArgs^ e)
    {
        VisualStateManager::GoToState(this, "Normal", false);
        removeButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        addButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
        deleteButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
        cancelButton->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
    }
    

    Press F5 and try using the new buttons to add or remove feeds! To add a feed on the phone, click on an RSS link on a web page, then choose Save. Then press on the edit box that has the name of the URL and then press the copy icon. Navigate back to the app and put the insertion point in the edit box and press the copy icon again to paste in the url. You should see the feed appear in the feed list almost immediately.

    The SimpleBlogReader app is now is a good usable state. It is ready to deploy to your Windows device.

To deploy the app to your own phone, it must first be registered as described in Register your Windows Phone.

Hh465045.wedge(en-us,WIN.10).gifTo deploy to an unlocked Windows Phone

  1. Create a Release Build.

    VS 2013 Release Build C++

  2. From the main menu, select Project | Store | Create App Packages. You do NOT want to deploy to the store in this exercise. Accept the defaults in the next screen unless you have a reason to change them.

  3. If the packages were created successfully, you will be prompted to run the Windows App Certification Kit (WACK). You might want to do this just to make sure the app doesn't have any hidden defects that would prevent its acceptance by the store. But since we are not deploying to the store, this step is optional.

  4. From the main menu, select Tools | Windows Phone 8.1 | Application Deployment. The Application Deployment wizrd appears and in the first screen, Target should say "Device". Click on the Browse button to navigate to the AppPackages folder in your project tree, at the same level as the Debug and Release folders. Find the latest package in that folder (if there are more than one) and double click it and then click on the appx or appxbundle file inside it.

  5. Make sure your phone is plugged into your computer and that it isn't locked by the lock screen. Press the Deploy button in the wizard and wait for deployment to complete. It should only take a few seconds until you see a "Deployment successful" message. ind the app in the Applications list in the phone and tap it to run the app.

    Note: Adding new URLs can be a bit non-intuitive at first. Search for a URL you want to add, then tap the link. At the prompt, say you want to open it. Copy the RSS url, for example http://feeds.bbci.co.uk/news/world/rss.xml, NOT the temporary xml file name that appears after IE opens the file. If the XML page opens up in IE, you'll need to navigate back to the previous IE screen to grab the URL you want from the address bar. Once you have copied it, then navigate back to Simple Blog Reader and paste it into the Add Feed text block and then press the "Add Feed" button. You'll see the fully initialized feed appear very quickly in your main page. Exercise for the reader: implement a share contract or other means to simplify the addition of new URLs to SimpleBlogReader. Happy reading!

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:

Roadmap for Windows Runtime apps using C++