Create a blog reader Store app (C++)

Applies to Windows only

Start to finish, here's how to use C++ and XAML to develop a Store app that you can deploy to Windows 8.1 or to Windows Phone 8.1. 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 Lessons 1-4 under Create your first Windows Store app using C++.

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

For this tutorial we’ll use Visual Studio Express 2013 with Update 2 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.

This tutorial does not show how to use Expression Blend to create the UI. Instead it shows the XAML markup directly.

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 Apps) 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++ > Store Apps > Universal Apps. , In the middle pane, choose and then select the Blank App (Universal Apps) template. Name the solution “SimpleBlogReader.” For more complete instructions, see Hello World in C++.

    After the project is created, you’ll have a solution with three projects: the Windows 8.1 project, the Windows Phone 8.1 project and a Shared project. The latter is really just a folder that contains files that are included in the other projects compilation process; it doesn't produce any binaries. We’ll work in all three projects more or less in parallel as we go along in order to highlight the differences and similarities between the two project types. If you don't like that approach, you can skip through and do all the steps for one or the other projects first.

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. In the Windows 8.1 project, 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 http://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 http://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:

    
        /// <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);
            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();
            void RetrieveFeedAndInitData(Platform::String^ url, WWS::SyndicationClient^ client);
    
        internal:
            // This is used to prevent SplitPage from prematurely loading the last viewed page on resume.
            concurrency::task_completion_event<FeedData^> m_LastViewedFeedEvent;
        };
    
    
    
  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 initialize 
    /// the app's data structures.
    ///</summary>
    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.
        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!)
                // This logic is not completely robust because it doesn't address
                // the case of one of our default URLs in the resources going stale.
                SyndicationErrorStatus status = SyndicationError::GetStatus(e->HResult);
                if (status == SyndicationErrorStatus::InvalidXml) {
    
                    auto handler = ref new Windows::UI::Popups::UICommandInvokedHandler(
                        [this, url](Windows::UI::Popups::IUICommand^ command)
                    {
                        auto app = safe_cast<App^>(App::Current);
                        auto title = app->GetTitleFromUri(url);
                        app->RemoveFeed(title);
                    });
                    
                    auto msg = ref new Windows::UI::Popups::MessageDialog(
                        "An invalid XML exception was thrown in " + url + 
                        ". Please make sure to use a URI that points to a RSS or Atom feed.");
                    auto cmd = ref new Windows::UI::Popups::UICommand(
                        ref new String(L"Delete Invalid Feed"), handler, 1);
                    msg->Commands->Append(cmd);
                    msg->ShowAsync();
                }
    
                if (status == SyndicationErrorStatus::Unknown) 
                {
                    Windows::Web::WebErrorStatus webError = 
                        Windows::Web::WebError::GetStatus(e->HResult);
                    // TODO: Provide a user friendly message, if there is something 
                    // they can do to fix the problem.               
                }
            }
        }); //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"http://blogs.windows.com/windows/b/bloggingwindows/rss.aspx");
        urls->Append(L"http://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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="using:SimpleBlogReader">
      
              <Application.Resources>
                  <local:FeedDataSource x:Key="feedDataSource" />    
              </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 methods 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);
    
    
    
  2. Then in app.xaml.cpp:

    
    
    ///<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:
            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 both the Windows app and the phone app now. Just don't expect them to do very much. To select the project that runs when you press F5, right click on the project node and choose Set as Startup project.

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. In app.xaml.cpp add this include directive:

    
    #include "Common\SuspensionManager.h"
    
    
  2. Add these namespace directives:

    
    
    using namespace concurrency;
    using namespace SimpleBlogReader::Common;
    using namespace Windows::Storage;
    
    
  3. 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.

  4. 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 databinds 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 (Windows app MainPage)

The main page in the Windows app 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 (Windows)

  1. In MainPage.xaml (Windows 8.1), add this GridView and ListView just after the grid that contains the back button and page title:

    
     <!-- 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>
    
    
    
  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 just after the ListView, 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 (Windows 8.1)

  • In the Document Outline window (Ctrl + Alt + D), select pageRoot, then press Alt + Enter to show the Properties pane. Click on the event button in the upper right, then scroll down until you find the SizeChanged event. Put the cursor in the text box and press Enter to create an event handler with the default name pageRoot_SizeChanged. 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);
        }
    } 
    
    
    

    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 MainPage.xaml (Windows Phone 8.1), in the opening Page element, just after the x:Name attribute, add the DataContext attribute:

    
    DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
    
    
  2. Still in MainPage.xaml (Windows Phone 8.1), just after the opening Page element, add a Page.Resources node that contains a CollectionViewSource that will bind to the FeedDataSource Items collection:

    
    <Page.Resources>
        <!-- Collection of items displayed by this page -->
        <CollectionViewSource x:Name="itemsViewSource" Source="{Binding Items}"/>
    </Page.Resources>
    
    
    
  3. 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.

  4. 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>
    
    
    
  5. 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.

LoadState and SaveState (Phone app MainPage)

  1. First, at the top of MainPage.xaml.cpp (Windows Phone 8.1), add an #include statement:
    
    #include "FeedPage.xaml.h"
    
    
  2. Still in MainPage.xamp.cpp, replace the LoadState function implementation with this code (which should look familiar from the Windows MainPage.xaml.cpp file):


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


As in the Windows app, we don't do anything with SaveState here.

Event handlers (Phone app MainPage)

When the user clicks on a feed in MainPage, the app navigates to FeedPage, which lists all the posts in that feed. When we navigate, we pass the selected feed to FeedPage, which receives it in its LoadState function. Also we save the feed so we can look it later if necessary; we'll discuss that more in the next step.

  • Replace the empty event handle stub with this code:

    
    void MainPage::ItemListView_ItemClick(Platform::Object^ sender, ItemClickEventArgs^ e)
    {
        // We must manually cast from Object^ to FeedData^.
        auto feedData = safe_cast<FeedData^>(e->ClickedItem);
    
        auto app = safe_cast<App^>(App::Current);
        app->SetCurrentFeed(feedData);
    
        // Navigate to FeedPage and pass the title of the selected feed.
        // FeedPage will receive this in its LoadState method in the navigationParamter.
        this->Frame->Navigate(FeedPage::typeid, feedData->Uri);
    }
    
    

That’s it for the first page. Right click on the WindowsPhone 8.1 project node in Solution Explorer and choose Set as startup project. Now press F5 to start up the app in the phone emulator and you should see something like this (the order might be different):

Phone main page

Obviously the phone app is using the same startup code in FeedDataSource that is referenced in app.xaml. The list items get their appearance from the ListItemTemplate that we added in a previous step to app.xaml. That template embeds the DateBlockTemplate to produce the distinctive green square that displays the feed date. If you look at that template code, you’ll see how it binds to the text values produced by the DateConverter class. That template also demonstrates how to perform precise positioning of text elements within a constrained area. Please play with those position values to make those squares look better!

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. Replace the title panel with 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 ContentRoot 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 possibly scary-looking asynchronous code in FeedData.cpp and App.xaml.cpp, but if you look at that code close, you'll see that it's not rocket science, and anyway, that's life in an asynchronous world.

  1. In FeedPage.xaml.cpp (Windows Phone 8.1), 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.

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 a private member. We'll use this 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.

    
    FeedItem^ m_feedItem;
    
    
  3. Add this private function that will be invoked when the user clicks on the link in the rich text:

    
    void RichTextHyperlinkClicked(Windows::UI::Xaml::Documents::Hyperlink^ link, 
             Windows::UI::Xaml::Documents::HyperlinkClickEventArgs^ args);
    
    

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. 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. Now let's move to SplitPage.xaml (Windows 8.1). 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">
    
    
  2. Now look for the pageTitle TextBlock in this same Grid and change the Binding from Title to Feed.Title.

    
    Text="{Binding Feed.Title}"
    
    
  3. 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>
    
    
    
  4. 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. In SplitPage.xaml.cpp, add this using directive:

    
    using namespace Windows::UI::Xaml::Documents;
    
    
    
  2. 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. In WebViewerPage.xaml.h, add this private member variable:

    
    Windows::Foundation::Uri^ m_feedItemUri;
    
    
  2. 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 poitn 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);
    }
    
    
    

    The WebViewerPage in both projects is so similar that we could probably use the same XAML for both pages and put it in the Shared project. That exercise is left for you, the reader.

  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:

    • URL_1 http://sxp.microsoft.com/feeds/3.0/devblogs
    • URL_2 http://blogs.windows.com/windows/b/bloggingwindows/rss.aspx
    • URL_3 http://azure.microsoft.com/blog/feed

    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 device for development.

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:

Related topics

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

 

 

Show:
© 2014 Microsoft