Tutorial: Create your first Windows Store app using C++
This tutorial demonstrates some essential code and concepts to help you use C++ to develop a Windows Store app that has a UI that's defined in Extensible Application Markup Language (XAML), and guide you through the steps to create the app step by step. This tutorial assumes that you are already somewhat familiar with the content in the previous tutorials in this section. Our sample app is a basic blog reader that downloads and displays data from an RSS 2.0 or Atom 1.0 feed. You can also download the finished app from the MSDN samples web site.
Important This tutorial does not reflect changes in Windows 8.1 Preview and Microsoft Visual Studio 2013 Preview.
After following this tutorial, consider reading Developing an end-to-end Windows Store app using C++ and XAML: Hilo to learn more about how to use modern C++, the Windows Runtime, asynchronous programming, XAML, and development patterns such as Model-View-ViewModel (MVVM) in your Windows Store app apps using C++.
If you'd rather use another programming language, see:
Objectives
After completing this tutorial, you will have a good basic understanding of how to create a multi-page Windows Store apps using XAML and C++, and how and when to use the Visual C++ component extensions (C++/CX) to simplify the work of coding against the Windows Runtime. You will also learn how to use the concurrency::task class to consume asynchronous Windows Runtime APIs.
Our SimpleBlogReader will have the following features:
- It will support multiple blog feeds.
- It will support Process Lifetime Management (PLM) and correctly save and reload its state if the system shuts it down while another task was in the foreground.
- It will adapt to different window sizes and device orientations (landscape or portrait).
- It will use simple animations and transitions to add life to the user interface.
- It will use styles to enhance the visual appeal of the app.
This article is designed so that you can follow the steps to create the app yourself. By the time you complete this tutorial, you'll be prepared to build your own Windows Store app by using XAML and C++. You can download the finished app from the MSDN samples web site
Creating the project
In this project, we'll start with a Blank App and add pages to it manually. Create a new C++ Windows Store app Blank App in Visual Studio and call it "SimpleBlogReader". (You can refer to Hello World in C++ for the specific steps.)
Specifying app capabilities
A Windows Store app runs in a security container that has limited access to the file system, network resources, and hardware. When a user installs an app from the Windows Store, Windows looks at the metadata in the Package.appxmanifest file to determine what capabilities the app requires. For example, an app might have to access data from the Internet, documents from the user's Document Library, or the user's webcam and microphone. When the app is installed, it displays to the user the capabilities it needs, and the user must grant permission before it can access those resources. If the app doesn't request and receive permission for a resource it needs, it won't be allowed access to that resource at run time. When you create a Windows Store app
To verify that basic Internet capability has already been requested
-
In Solution Explorer, open Package.appxmanifest. The file opens in the Application Manifest Designer.
-
Select the Capabilities tab.
-
You will notice that the Internet (Client) check box is already selected. If we needed some other capability, we'd have to select it manually in this designer.
-
Close the manifest designer.
When you specify a capability, it's listed in the Package.appxmanifest file under the Capabilities element. You'd typically use the Application Manifest Designer to set capabilities, but if you open Package.appxmanifest.xml in the XML Text Editor, you can see the Capabilities element in the XML.
<Capabilities> <Capability Name="internetClient" /> </Capabilities>
For more information about app capabilities, see Manifest Designer.
Getting data into an app
Now we can write the code to get the blog feed into the app. The "Developing for Windows" blog exposes the full text of its posts in both RSS and Atom form. We want to display the title, author, date, and content from each of the latest blog posts. We can use the classes in the Windows::Web::Syndication namespace to download the feeds. We could also use those classes to display the data in the UI, but instead, we'll create our own data classes. This gives us additional flexibility and lets us treat RSS and Atom feeds in the same way. We create three classes:
-
FeedData holds info about the RSS or Atom feed.
-
FeedItem holds info about individual blog posts in the feed.
- FeedDataSource contains the methods to download the feeds and initialize our data classes.
We define these classes as public ref classes to enable data-binding with the XAML elements that display the Title, Author, and so on. We use the Bindable attribute to indicate to the XAML compiler that we are binding dynamically to instances of these types. In a public ref class, public data members are exposed as properties. Properties that have no special logic don't require a user-specified getter and setter; the compiler will supply them. In the FeedData class, notice how we use IVector to expose a public collection type to other Windows Runtime classes and components. We also use the Platform::Collections::Vector class internally as the concrete type that implements IVector. Later we'll learn how to consume that type.
To create custom data classes
-
In Solution Explorer, on the shortcut menu for the SimpleBlogReader project node, choose Add > New Item.
-
Select Header File (.h) from the list of options and name it FeedData.h.
-
Copy and paste the following code into the file. Take a moment to look at the code and familiarize yourself with the C++/CX constructs. Note the #include directive for "pch.h"; that header is the place to put all your system headers such as <string> and <vector>. By default pch.h includes <collection.h>, which is required for the Platform::Collections::Vector type. For more information about what you are seeing here, see Classes and structures (C++/CX).
//feeddata.h #pragma once #include "pch.h" #include <collection.h> namespace SimpleBlogReader { // To be bindable, a class must be defined within a namespace // and a bindable attribute needs to be applied. // A FeedItem represents a single blog post. [Windows::UI::Xaml::Data::Bindable] public ref class FeedItem sealed { public: FeedItem(void){} property Platform::String^ Title; property Platform::String^ Author; property Platform::String^ Content; property Windows::Foundation::DateTime PubDate; property Windows::Foundation::Uri^ Link; private: ~FeedItem(void){} }; // A FeedData object represents a feed that contains // one or more FeedItems. [Windows::UI::Xaml::Data::Bindable] public ref class FeedData sealed { public: FeedData(void) { m_items = ref new Platform::Collections::Vector<FeedItem^>(); } // The public members must be Windows Runtime types so that // the XAML controls can bind to them from a separate .winmd. property Platform::String^ Title; property Windows::Foundation::Collections::IVector<FeedItem^>^ Items { Windows::Foundation::Collections::IVector<FeedItem^>^ get() {return m_items; } } property Platform::String^ Description; property Windows::Foundation::DateTime PubDate; property Platform::String^ Uri; private: ~FeedData(void){} Platform::Collections::Vector<FeedItem^>^ m_items; }; // A FeedDataSource represents a collection of FeedData objects // and provides the methods to download the source data from which // FeedData and FeedItem objects are constructed. This class is // instantiated at startup by this declaration in the // ResourceDictionary in app.xaml: <local:FeedDataSource x:Key="feedDataSource" /> [Windows::UI::Xaml::Data::Bindable] public ref class FeedDataSource sealed { private: Platform::Collections::Vector<FeedData^>^ m_feeds; std::map<Platform::String^, concurrency::task_completion_event<FeedData^>> m_feedCompletionEvents; FeedData^ GetFeedData(Platform::String^ feedUri, Windows::Web::Syndication::SyndicationFeed^ feed); public: FeedDataSource(); property Windows::Foundation::Collections::IObservableVector<FeedData^>^ Feeds { Windows::Foundation::Collections::IObservableVector<FeedData^>^ get() { return this->m_feeds; } } void InitDataSource(); static Windows::Foundation::IAsyncOperation<FeedData^>^ GetFeedAsync(Platform::String^ title); static FeedItem^ GetFeedItem(FeedData^ fd, Platform::String^ uniqueiD); }; }
Create a new .cpp file as before by choosing Project > New Item and call it FeedData.cpp. Then copy and paste the following code into that file:
#include "pch.h" #include "FeedData.h" using namespace std; using namespace concurrency; using namespace SimpleBlogReader; using namespace Platform; using namespace Platform::Collections; using namespace Windows::Foundation; using namespace Windows::Web::Syndication; FeedDataSource::FeedDataSource() { m_feeds = ref new Vector<FeedData^>(); } // Retrieve the data for each atom or rss feed and put it // into our custom data structures. void FeedDataSource::InitDataSource() { // Left as an exercise: store the urls separately and let the user configure them. // It might be more convenient to use Platform::Strings here, but using wstring // serves to demonstrate how standard C++ types can be used here. std::vector<std::wstring> urls; urls.push_back(L"http://windowsteamblog.com/windows/b/developers/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows/b/windowsexperience/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows/b/extremewindows/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows/b/business/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows/b/windowssecurity/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows/b/springboard/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx"); // There is no Atom feed for this blog, so we use the RSS feed. urls.push_back(L"http://windowsteamblog.com/windows_live/b/windowslive/rss.aspx"); urls.push_back(L"http://windowsteamblog.com/windows_live/b/developer/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/ie/b/ie/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx"); urls.push_back(L"http://windowsteamblog.com/windows_phone/b/wmdev/atom.aspx"); // If we are resuming, we need to create a map of completion events so that // we don't attempt to restore page's state before it's backing data has been loaded. // First we create all the events in an "unset" state, mapped to the urls as keys. // we'll set the event after we asynchronously load the feed. for( wstring url : urls) { auto uri = ref new String(url.c_str()); task_completion_event<FeedData^> e; m_feedCompletionEvents.insert(make_pair(uri, e)); } SyndicationClient^ client = ref new SyndicationClient(); // Range-based for loop. Never write a regular for loop again! for(wstring url : urls) { // Create the async operation. feedOp is an // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^ auto uri = ref new String(url.c_str()); auto feedUri = ref new Uri(uri); auto feedOp = client->RetrieveFeedAsync(feedUri); // Create the task object and pass it the async operation. // SyndicationFeed^ is the type of the return value // that the feedOp operation will eventually produce. // Then, initialize a FeedData object with the feed info. Each // operation is independent and does not have to happen on the // UI thread. Therefore, we specify use_arbitrary. create_task(feedOp) .then([this, uri] (SyndicationFeed^ feed) -> FeedData^ { return GetFeedData(uri, feed); }, concurrency::task_continuation_context::use_arbitrary()) // Append the initialized FeedData object to the list // that is the data source for the items collection. // This has to happen on the UI thread. By default, a .then // continuation runs in the same apartment thread that it was called on. // Because the actions will be synchronized for us, we can append // safely to the Vector without taking an explicit lock. .then([this] (FeedData^ fd) { m_feeds->Append(fd); m_feedCompletionEvents[fd->Uri].set(fd); // Write to VS output window in debug mode only. Requires <windows.h>. OutputDebugString(fd->Title->Data()); OutputDebugString(L"\r\n"); }) // The last continuation serves as an error handler. The // call to get() will surface any exceptions that were raised // at any point in the task chain. .then( [] (task<void> t) { try { t.get(); } // SyndicationClient throws Platform::InvalidArgumentException // if a URL contains illegal characters. // We catch this exception for demonstration purposes only. // In the current design of this app, an illegal // character can only be introduced by a coding error // and should not be caught. If we modify the app to allow // the user to manually add a new url, then we need to catch // the exception. catch(Platform::InvalidArgumentException^ e) { // For example purposes we just output error to console. // In a real world app that allowed the user to enter // a url manually, you could prompt them to try again. OutputDebugString(e->Message->Data()); } }); //end task chain }; } FeedData^ FeedDataSource::GetFeedData(String^ feedUri, SyndicationFeed^ feed) { FeedData^ feedData = ref new FeedData(); // Knowing this makes it easier to map completion_events // when we resume from termination. feedData->Uri = feedUri; // Get the title of the feed (not the individual posts). feedData->Title = feed->Title->Text; if (feed->Subtitle->Text != nullptr) { feedData->Description = feed->Subtitle->Text; } // Use the date of the latest post as the last updated date. feedData->PubDate = feed->Items->GetAt(0)->PublishedDate; // Construct a FeedItem object for each post in the feed // using a range-based for loop. Preferable to a // C-style for loop, or std::for_each. for (auto item : feed->Items) { auto feedItem = ref new FeedItem(); feedItem->Title = item->Title->Text; feedItem->PubDate = item->PublishedDate; //We only get first author in case of multiple entries. feedItem->Author = item->Authors->GetAt(0)->Name; if (feed->SourceFormat == SyndicationFormat::Atom10) { feedItem->Content = item->Content->Text; String^ s(L"http://windowsteamblog.com"); feedItem->Link = ref new Uri(s + item->Id); } else if (feed->SourceFormat == SyndicationFormat::Rss20) { feedItem->Content = item->Summary->Text; feedItem->Link = item->Links->GetAt(0)->Uri; } feedData->Items->Append(feedItem); }; return feedData; } //end GetFeedData // We use this method to get the proper FeedData object when resuming // from shutdown. We need to wait for this data to be populated before // we attempt to restore page state. Note the use of task_completion_event // which doesn't block the UI thread. IAsyncOperation<FeedData^>^ FeedDataSource::GetFeedAsync(String^ uri) { return create_async([uri]() { auto feedDataSource = safe_cast<FeedDataSource^>( App::Current->Resources->Lookup("feedDataSource")); // Does not block the UI thread. auto f = feedDataSource->m_feedCompletionEvents[uri]; // In the callers we continue from this task after the event is // set in InitDataSource and we know we have a FeedData^. task<FeedData^> t = create_task(f); return t; }); } // We stored the stringID when the app was suspended // because storing the FeedItem itself would have required // more custom serialization code. Here is where we retrieve // the FeedItem based on its string ID. FeedItem^ FeedDataSource::GetFeedItem(FeedData^ feed, String^ uniqueId) { auto itEnd = end(feed->Items); auto it = std::find_if(begin(feed->Items), itEnd, [uniqueId] (FeedItem^ fi) { return fi->Title == uniqueId; }); if (it != itEnd) return safe_cast<FeedItem^>(*it); return nullptr; }
Tip
The
taskclass is visible because its header,<ppltasks.h>. is included by default in pch.h.The Windows.Web.Syndication.SyndicationClient class retrieves and parses RSS and Atom feeds. Because this operation involves network I/O, the method executes asynchronously. The asynchronous programming model is found throughout the Windows Runtime class libraries. An asynchronous method call returns control immediately to the UI thread so that the UI remains responsive while the operation is being performed on a background thread.
The Windows Runtime provides a way to invoke asynchronous operations and get their results when they complete; you can program directly against that API. However, the preferred approach is to use the task class class that is defined in ppltasks.h. The task class uses these same asynchronous Windows Runtime APIs, but you can use it to write more concise code, and it's easier to chain asynchronous operations and handle in one spot any exceptions that arise. We use task in two places in the file we just created. When you use the task class, the basic steps are always the same:
-
Create an asynchronous operation by calling a Windows Runtime *Async method, such as Windows::Web::Syndication::ISyndicationClient::RetrieveFeedAsync.
-
Create a task object by using the operation as input parameter.
-
Call task::then and specify a lambda that takes the return value of the operation as input.
-
Optionally, call then again one or more times. These clauses can accept the return value from the previous clause.
-
Optionally, provide a final then clause that handles any exceptions that were thrown anywhere in the chain of operations.
-
-
Add this #include directive to app.xaml.h:
#include "FeedData.h"
To implement IValueConverter for custom date formatting
Now is a good time to add one more custom class, an implementation of IValueConverter, an interface that enables you to convert between arbitrary types in arbitrary ways. Our class is a Date Converter that can return the day, month, and year as individual strings when given a DateTime argument. We will use this class in the style that we'll define for grid items and list view items later on.
To create the DateConverter
class
-
Add a new project item called DateConverter.h, and copy in this implementation:
//DateConverter.h #pragma once #include <string> //for wcscmp namespace SimpleBlogReader { public ref class DateConverter sealed : public Windows::UI::Xaml::Data::IValueConverter { public: virtual Platform::Object^ Convert(Platform::Object^ value, Windows::UI::Xaml::Interop::TypeName targetType, Platform::Object^ parameter, Platform::String^ language) { if(value == nullptr) { throw ref new Platform::InvalidArgumentException(); } auto dt = safe_cast<Windows::Foundation::DateTime>(value); auto param = safe_cast<Platform::String^>(parameter); Platform::String^ result; if(param == nullptr) { auto dtf = Windows::Globalization::DateTimeFormatting::DateTimeFormatter::ShortDate::get(); result = dtf->Format(dt); } else if(wcscmp(param->Data(), L"month") == 0) { auto month = ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{month.abbreviated(3)}"); result = month->Format(dt); } else if(wcscmp(param->Data(), L"day") == 0) { auto month = ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{day.integer(2)}"); result = month->Format(dt); } else if(wcscmp(param->Data(), L"year") == 0) { auto month = ref new Windows::Globalization::DateTimeFormatting::DateTimeFormatter("{year.full}"); result = month->Format(dt); } else { // We don't handle other format types currently. throw ref new Platform::InvalidArgumentException(); } return result; } virtual Platform::Object^ ConvertBack(Platform::Object^ value, Windows::UI::Xaml::Interop::TypeName targetType, Platform::Object^ parameter, Platform::String^ language) { // Not needed in SimpleBlogReader. Left as an exercise. throw ref new Platform::NotImplementedException(); } }; }
- To use the class, we add a reference to it in the Application.Resources node in App.xaml, just after the FeedDataSource tag. This causes DateConverter to be instantiated when the app is created.
<local:DateConverter x:Key="dateConverter" />
We also need to #include it somewhere so that it gets compiled. We'll do that in app.xaml.h:
#include "DateConverter.h"
Adding pages and navigation
Now we are ready to create the user interface. If we're going to support multiple blogs, we have to add some pages to the app, and handle the navigation between these pages. Fortunately, Visual Studio gives us several page templates that implement most of the page navigation and orientation functionality for us.
First, we want a page that lists all of the Windows Team Blogs; for this we can use an Items Page template. When a reader picks a blog from this page, we load the list of posts for that blog into another page. We'll use the SplitPage template for that. We'll also add a detail page so that the user can choose to read individual blog posts without the list view taking up space. Each of these templates has extensive navigation support built in. Our main task will be to write custom logic for the LoadState and SaveState methods that are stubbed out in each code-behind class, and add an app bar with a button to enable forward navigation from the split page to the details page.
To add new pages to the app
-
In Solution Explorer, right click on MainPage.xaml and choose Remove. We won't be using this page or its code-behind files in our project. You can either delete it or not delete it permanently.
-
In app.xaml.cpp, change the #include directive for "mainpage.xaml.h" to
#include itemspage.xaml.h. -
Still in app.xaml.cpp, press Ctrl+F to search the current document for "MainPage" and replace it with "ItemsPage." The red squiggly will go away after you add the new page.
-
On the menu bar, choose Project > Add New Item. The Add New Item dialog box opens.
-
In the Installed pane, expand Visual C++.
-
Select the template for a Windows Store app project.
-
In the center pane, select ItemsPage and accept the default name.
- Choose Yes when the dialog asks if you want to add necessary files automatically. These files are added to the Common folder and they contain code to support navigation as well as serialization and deserialization of simple types when your app is terminated and resumed.
-
Choose the Add button. The XAML and code-behind files for your page are added to the project.
-
Repeat steps 1 through 5, but select Split Page.
-
Repeat steps 1 through 5, but select Basic Page. Give this page the name "DetailPage".
The Items Page will show the list of Windows Team Blogs. The Split page will show the posts for each blog on the left side and the content of the selected post on the right side, like the MainPage we created previously. The Basic Page will show nothing but the content of a selected post, a back button, and the page title. On this page, instead of loading the post content into the WebView from a string of HTML as we do in the SplitView page, we navigate to the URL of the post and show the actual web page. After this work, the pages of the app will look something like this:

When we add the page templates to our project and look at the XAML and code-behind, it's apparent that these page templates do a lot of work for us. In fact, it's easy to get lost at first, but it helps to understand that each page template has three main sections:
|
Resources |
Styles and data templates for the page are defined in the Resources section. We talk more about this in the Creating a consistent look with styles section. |
|
Visual State Manager |
Animations and transitions that adapt the app to different layouts and orientations are defined in the Visual State Manager (VSM). We talk more about this in the Adapting to different layouts section. |
|
App Content |
The controls and content that make up the app UI are defined in the root layout panel. |
Navigating between pages
One of the main activities in a Windows Store app is navigating between pages. A user might be able to choose a forward or back button to move through the pages, or choose an item that opens another page—for example, to show details about the item. Here's the navigation design for SimpleBlogReader:
-
When the app starts, the items page shows a grid of blog feeds (DataFeed objects). Only the blog titles are displayed, along with the dates of the last posts and descriptions if they exist. When the user selects a feed, the app navigates to the SplitPage.
-
In landscape mode, the SplitPage shows the list of posts (FeedItem objects) on the left, and a preview of the currently selected post on the right. In portrait or snapped view, the SplitPage shows either the list or the preview, but not both at the same time. When the user selects another item in the list, the preview changes.
-
The SplitPage has an app bar that the user can invoke by swiping from the top of the screen or by right-clicking the mouse. The app bar has a button; when the user chooses it, the app navigates to the DetailPage that shows the blog post as a complete web page.
-
The user can choose the back button on the DetailPage to go back to the SplitPage, and can choose the back button on the SplitPage to go back to the ItemsPage.
To complicate things, every time we load a page we must consider whether the navigation is invoked by a user action (forward or back) or by the system resuming the app after terminating it at some point in the past.
The SuspensionManager class provides much of the code to save and restore page state when the app is suspended and terminated. However, it is up to us to implement the specific logic that's required to load or re-load the pages.
The XAMLUI framework provides a built-in navigation model that uses Frames and Pages and works much like the navigation in a web browser. The Frame control hosts Pages, and has a navigation history that you can use to go back and forward through pages that you've visited. You can pass data between pages as you navigate.
Navigation starts when the app is started. The infrastructure that supports navigation is in the App class. In the Visual Studio project templates, a Frame that's named rootFrame is set as the content of the app window. Let's look at the default code in App.xaml.cpp. Notice that after the rootFrame is set, the app checks its current state because it might starting from a closed state or resuming from suspension and already have its content in memory. If it's starting from a terminated state, it has to load the state that was saved when it was terminated. After handling these various cases, the app navigates to its first window.
// Default implementation. Not to be pasted into BlogReader. /// <summary> /// Invoked when the application is launched normally by the end user. Other entry points /// will be used when the application is launched to open a specific file, to display /// search results, and so forth. /// </summary> /// <param name="e">Details about the launch request and process.</param> void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ e) { #if _DEBUG if (IsDebuggerPresent()) { DebugSettings->EnableFrameRateCounter = true; } #endif auto rootFrame = dynamic_cast<Frame^>(Window::Current->Content); // Do not repeat app initialization when the Window already has content, // just ensure that the window is active if (rootFrame == nullptr) { // Create a Frame to act as the navigation context and associate it with // a SuspensionManager key rootFrame = ref new Frame(); if (e->PreviousExecutionState == ApplicationExecutionState::Terminated) { // TODO: Restore the saved session state only when appropriate, scheduling the // final launch steps after the restore is complete } if (rootFrame->Content == nullptr) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter if (!rootFrame->Navigate(TypeName(ItemsPage::typeid), e->Arguments)) { throw ref new FailureException("Failed to create initial page"); } } // Place the frame in the current Window Window::Current->Content = rootFrame; // Ensure the current window is active Window::Current->Activate(); } else { if (rootFrame->Content == nullptr) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter if (!rootFrame->Navigate(TypeName(ItemsPage::typeid), e->Arguments)) { throw ref new FailureException("Failed to create initial page"); } } // Ensure the current window is active Window::Current->Activate(); } }
To enable navigation between pages, use the Frame control's Navigate, GoForward, and GoBack methods. By using the Navigate(TypeName, Object) method, we can navigate to a new page and pass data to it at the same time. The first parameter is the TypeName of the page that we are navigating to. We obtain the TypeName of a type by using the static typeid operator. In this case, we want to navigate to the ItemsPage as the first page that users encounter when they start or resume the app.
The second parameter is the data object that we pass to the page that we're navigating to. In that page, it's passed to the LoadState method in the navigationParameter parameter. In this app, we support Process Lifetime Management (PLM). The PLM functionality in the generated code automatically tries to save the object that's passed in the navigationParameter, but without additional help from us, it can succeed only if the type is a String, Guid, or primitive type. Therefore, we won't pass an entire FeedData object, but we can pass a string and use it as a key to look up the specific FeedData or FeedItem object. That's the purpose of the GetFeedAsync and GetFeedItem methods in the FeedDataSource class. We'll look at those methods in more detail later.
To navigate from the App class to the items page
-
In App.xaml.cpp, add the following code to the App::OnLaunched method implementation. This code retrieves the FeedDataSource object that's stored in the app's data model dictionary and passes it to the Items page. Paste this code either before or after
rootFrame = ref new Frame();. IfrootFrame == nullptr, then we have to download all of the FeedData objects again because the app was either terminated by the system or closed by the user. Otherwise, they are still in memory.FeedDataSource^ feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource")); if (feedDataSource->Feeds->Size == 0) { feedDataSource->InitDataSource(); }When the Navigate method is invoked in App::OnLaunched, it eventually causes the ItemsPage::LoadState event handler to be called. As discussed earlier, we can't pass the feedDataSource object or even the Feeds property by using navigationParameter because we don't want to have to serialize it ourselves. Instead, we store a reference to the feedDataSource as a resource in App.xaml, and we just access it here. We still have to initialize the DefaultViewModel for ItemsPage by using the Feeds property. Here, the Feeds Vector is associated with a key that's named "Items" and is inserted into the DefaultViewModel member for ItemsPage. DefaultViewModel is a Windows::Foundation::Collections::IObservableMap. Each page has its own DefaultViewModel. After you intialize the DefaultViewModel with some data, the ItemsViewSource::View property will point to your data. If the elements in the collection are bindable, then the items will show up in the UI.
void ItemsPage::LoadState(Object^ navigationParameter, IMap<String^, Object^>^ pageState) { // This is the first page to load on startup. The feedDataSource was constructed when the app loaded // in response to this declaration in app.xaml: <local:FeedDataSource x:Key="feedDataSource" /> // and was initialized aynchronously in the OnLaunched event handler in app.xaml.cpp. // Initialization might still be happening, but that's ok. auto feedDataSource = safe_cast<FeedDataSource^>(App::Current->Resources->Lookup("feedDataSource")); // In ItemsPage.xaml (and every other page), the DefaultViewModel is set as DataContext: // DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}" // Because ItemsPage displays a collection of feeds, we set the Items element // to the FeedDataSource::Feeds collection. By comparison, the SplitPage sets this element to // the Items collection of one FeedData object. this->DefaultViewModel->Insert("Items", feedDataSource->Feeds); }
Now press F5 to run the app with this modification. Notice that without any changes yet to the template code, some of the data that we passed to the ItemsPage is already showing up in the grid squares. It looks something like this (the items might be arranged differently depending on your screen resolution):

The only thing left to do on ItemsPage is to tell it what to do when the user chooses one of its items.
To navigate from the items page to the split page
-
When the user picks a blog from the ItemsPage collection, we navigate from the Items page to the Split page. To enable this navigation, we want GridView items to behave like buttons instead of selected items. To make the GridView items respond like buttons, we set the SelectionMode and IsItemClickEnabled properties as shown in the next example. We then add a handler for the ItemClicked event of the GridView. In ItemsPage.xaml find the
itemGridViewelement and use the following markup to replace it:<GridView x:Name="itemGridView" AutomationProperties.AutomationId="ItemsGridView" AutomationProperties.Name="Items" TabIndex="1" Grid.RowSpan="2" Padding="116,136,116,46" ItemsSource="{Binding Source={StaticResource itemsViewSource}}" SelectionMode="None" IsItemClickEnabled="true" IsSwipeEnabled="false" ItemClick="ItemView_ItemClick"/>
-
Add these directives to ItemsPage.xaml.cpp:
#include "SplitPage.xaml.h" //... using namespace Windows::UI::Xaml::Interop;
-
The event handler prototype to ItemsPage.xaml.h and the implementation to ItemsPage.xaml.cpp:
//ItemsPage.xaml.h: protected: virtual void ItemView_ItemClick(Platform::Object^ sender, Windows::UI::Xaml::Controls::ItemClickEventArgs^ e); //ItemsPage.xaml.cpp: void ItemsPage::ItemView_ItemClick(Object^ sender, ItemClickEventArgs^ e) { // We must manually cast from Object^ to FeedData^. auto feedData = safe_cast<FeedData^>(e->ClickedItem); // Store the current URI so that we can look up the // correct feedData object on resume. App::Current->Resources->Insert("CurrentFeed", feedData); // Navigate to SplitPage and pass the title of the selected feed. // SplitPage will receive this in its LoadState method in the navigationParamter. this->Frame->Navigate(TypeName(SplitPage::typeid), feedData->Title); }
-
Add this using directive to SplitPage.xaml.cpp:
using namespace concurrency;
The header file for the concurrency namespace is <ppltasks.h> and it is already included in our local pch.h file.
-
When ItemsPage navigates to SplitView, it causes the SplitView::LoadState method to be called. As in ItemsPage, LoadState has to determine how it has been navigated to, and what the app's previous state was. The code comments in the next example provide more detail; you'll probably recognize much of it from looking at App::OnLaunched and ItemsPage::LoadState. Now inSplitPage.xaml.cpp and use this code to replace the entire method:
/// <summary> /// Populates the page with content that's passed during navigation. Any saved state is also /// provided when a page is recreated from a prior session. /// </summary> /// <param name="navigationParameter">The parameter value that was passed to /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested. /// </param> /// <param name="pageState">A map of state that was preserved by this page during an earlier /// session. This is null the first time that a page is visited.</param> void SplitPage::LoadState(Object^ navigationParameter, IMap<String^, Object^>^ pageState) { // If we are navigating forward from ItemsPage, there is no existing page state. if (pageState == nullptr) { // Current feed was set in the click event in ItemsPage. We don't pass it in // through navigationParameter because on suspension, the default serialization // mechanism will try to store that value but fail because it can only handle // primitives, strings, and Guids. auto fd = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed")); // Insert into the ViewModel for this page to initialize itemsViewSource->View this->DefaultViewModel->Insert("Feed", fd); this->DefaultViewModel->Insert("Items", fd->Items); // When this is a new page, select the first item automatically unless logical page // navigation is being used (see the logical page navigation #region below). if (!UsingLogicalPageNavigation() && itemsViewSource->View != nullptr) { this->itemsViewSource->View->MoveCurrentToFirst(); } else { this->itemsViewSource->View->MoveCurrentToPosition(-1); } } // pageState != null means either (1) we are returning from DetailPage // or (2) we are resuming from termination. If (1), then we still have our // state and don't have to do anything. If (2), then we have to restore the page. else if (!this->DefaultViewModel->HasKey("Feed")) { // All we stored is the Uri string for the feed, not the object. auto uri = safe_cast<String^>(pageState->Lookup("Uri")); // FeedDataSource::InitDataSource has already been called. // It's an asynchronous operation, so our FeedData might not // be available yet. GetFeedAsync uses a task_completion_event to // wait (on its own thread) until the specified FeedData is available. // The next three methods follow the basic async pattern in C++: // 1. Call the async method. auto feedDataOp = FeedDataSource::GetFeedAsync(uri); // 2. Create a task from it. auto feedDataTask = create_task(feedDataOp); // 3. Define the work to be performed after the task completes. feedDataTask.then([this, pageState](FeedData^ feedData) { // Now we have the feedData, so it's safe to get the FeedItem // synchronously. Inserting into DefaultViewModel // initializes the itemsViewSource-View object. this->DefaultViewModel->Insert("Feed", feedData); this->DefaultViewModel->Insert("Items", feedData->Items); // DetailsPage has to get the new Uri from this value. App::Current->Resources->Insert("CurrentFeed", feedData); // Now that we have a FeedData^, we can call GetFeedItem safely and // pass in the title that we stored before the app was terminated. auto itemTitle = safe_cast<String^>(pageState->Lookup("SelectedItem")); auto selectedItem = FeedDataSource::GetFeedItem(feedData, itemTitle); if (selectedItem != nullptr) { this->itemsViewSource->View->MoveCurrentTo(selectedItem); } }); } }
When we resume from termination, we don't have a FeedItem object, we only have a string. Therefore, we have to look up the FeedItem based on the string. But this part is tricky—we have to wait until the FeedItem has been downloaded by FeedDataSource. And we don't want to wait for all the feeds to download; we want to wait only until the one we need is ready for use. All of this synchronization happens here and in the FeedDataSource::GetFeedAsync method. After we have the FeedData object, we can call the GetFeedItem method synchronously to get the FeedItem for the selected post so that we can populate the preview pane.
Note We insert the Items property separately into the DefaultViewModel for the SplitPage to make the items accessible for XAML data-binding.
No extra work is required to navigate back to the Items page. The template includes code to handle the BackButton.Click event and call the Frame.GoBack method.
-
When you run the app at this point, notice that the blog text in the detail pane is displaying raw HTML. To fix that, we have to change the layout that's used for the title and content of the selected blog post. If the app is running, press F12 to break out of it, move through the dialog box, and then press Shift-F5 to stop debugging and return to the Visual Studio code editor.
To implement SplitPage::SaveState
-
In ItemsPage, there was no need to save page state because we always show all the items. In SplitPage, we have to save the currently selected item so that the app can start from exactly this state if it gets terminated and resumed. As we mentioned earlier, because we are relying on the SuspensionManager to do the serialization work for us, we can only save strings and numbers, not FeedItem objects. Therefore, we save the name of the currently selected item. We have to save both the title of the FeedItem and the Uri of the FeedData object so that we can find this FeedItem again after termination and resume.
/// <summary> /// Preserves state that's associated with this page in case the application is suspended or the /// the page is discarded from the navigation cache. Values must conform to the serialization /// requirements of <see cref="SuspensionManager::SessionState"/>. /// </summary> /// <param name="pageState">An empty map to be populated with serializable state.</param> void SplitPage::SaveState(IMap<String^, Object^>^ pageState) { if (itemsViewSource->View != nullptr) { auto selectedItem = itemsViewSource->View->CurrentItem; // TODO: Derive a serializable navigation parameter and pass it to // e->PageState->Insert("SelectedItem", <value>) auto selectedItem = itemsViewSource->View->CurrentItem; if (selectedItem != nullptr) { auto feedItem = safe_cast<FeedItem^>(selectedItem); auto itemTitle = feedItem->Title; e->PageState->Insert("SelectedItem", itemTitle); } // Save the feed title also. auto feedData = safe_cast<FeedData^>(this->DefaultViewModel->Lookup("Feed")); e->PageState->Insert("Uri", feedData->Uri); } }
To modify the bindings and layout in SplitPage and ItemsPage
-
We have to make a few more changes to finish adding functionality to the new pages we added to our app. After we add this code, we can move on to styling and animating.
Because we used a key that's named "Feed" when we added our data to the DefaultViewModel, we have to change the binding in the page title to bind to the Feed property instead of "Group", which is the default. In SplitPage.xaml, change the Text binding of the TextBlock that's named pageTitle to bind to Feed.Title, like this:
<TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" Style="{StaticResource PageHeaderTextStyle}"/>
-
Also in SplitPage.xaml, change the Grid named titlePanel to span 2 columns.
<!-- Back button and page title --> <Grid x:Name="titlePanel" Grid.ColumnSpan="2">
-
In ItemsPage.xaml, the page title is bound to a static resource that has the key AppName. Update the text in this resource to Windows Team Blogs, like this:
<x:String x:Key="AppName">Windows Team Blogs</x:String>
To add a WebView control to SplitPage.xaml
-
In SplitPage.xaml, we have to change the layout that's used to show the title and content of the selected blog post. To do that, replace the ScrollViewer named itemDetail with the following ScrollViewer layout. You should recognize much of this XAML from our previous work in MainPage.xaml. The purpose of the Rectangle element will be shown later in this article.
<!-- Details for selected item --> <ScrollViewer x:Name="itemDetail" AutomationProperties.AutomationId="ItemDetailScrollViewer" Grid.Column="1" Grid.RowSpan="2" Padding="70,0,120,0" DataContext="{Binding SelectedItem, ElementName=itemListView}" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Enabled" ScrollViewer.ZoomMode="Disabled"> <Grid x:Name="itemDetailGrid" Margin="0,60,0,50"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Image Grid.Row="1" Margin="0,0,20,0" Width="180" Height="180" Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/> <StackPanel x:Name="itemDetailTitlePanel" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"> <TextBlock x:Name="itemTitle" Margin="0,-10,0,0" Text="{Binding Title}" Style="{StaticResource SubheaderTextBlockStyle}"/> <TextBlock x:Name="itemSubtitle" Margin="0,0,0,20" Text="{Binding Subtitle}" Style="{StaticResource SubtitleTextBlockStyle}"/> </StackPanel> <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" Grid.Row="2" Grid.Column="1" Margin="0,15,0,20"> <Grid> <WebView x:Name="contentView" /> <Rectangle x:Name="contentViewRect" /> </Grid> </Border> </Grid> </ScrollViewer> -
In SplitPage.xaml.cpp, modify the event handling code that causes the WebView to update when the ListView selection changes. The ItemListView_SelectionChanged function signature and implementation are already there. We just have to add these lines at the end of the method:
// Sometimes there is no selected item, e.g. when navigating back // from detail in logical page navigation. auto fi = dynamic_cast<FeedItem^>(itemListView->SelectedItem); if(fi != nullptr) { contentView->NavigateToString(fi->Content); }
To add a WebView control to DetailPage.xaml
-
In DetailPage.xaml, we have to bind the title text to the blog post title and add a WebView control to show the blog page. To do that, replace the Grid that contains the back button and page title with this Grid and WebView:
<!-- Back button and page title --> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <AppBarButton x:Name="backButton" Height="95" Margin="10,46,0,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}" AutomationProperties.Name="Back" AutomationProperties.AutomationId="BackButton" AutomationProperties.ItemType="Navigation Button" Icon="Back"/> <TextBlock x:Name="pageTitle" Text="{Binding Title}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1" IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40"/> </Grid> <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" Grid.Row="1" Margin="120,15,20,20"> <WebView x:Name="contentView" /> </Border>
To implement LoadState and SaveState in the DetailPage class
-
Add the following private data members to the DetailPage class in DetailPage.xaml.h:
private: Platform::String^ m_itemTitle; Platform::String^ m_feedUri; -
In DetailPage.xaml.cpp, add code to the LoadState method to navigate to the blog post and set the DataContext of the page. As in SplitPage, we have to determine the previous state of the app. The updated method looks like this:
/// <summary> /// Populates the page with content passed during navigation. Any saved state is also /// provided when recreating a page from a prior session. /// </summary> /// <param name="navigationParameter">The parameter value passed to /// <see cref="Frame::Navigate(Type, Object)"/> when this page was initially requested. /// </param> /// <param name="pageState">A map of state preserved by this page during an earlier /// session. This will be null the first time a page is visited.</param> void DetailPage::LoadState(Object^ navigationParameter, IMap<String^, Object^>^ pageState) { // Lookup the URL for the blog title that was either // (a) passed to us in this session or // (b) saved in the SaveState method when our app was suspended. m_itemTitle = safe_cast<String^>(navigationParameter); // We are navigating forward from SplitPage if (pageState == nullptr) { auto feedData = safe_cast<FeedData^>(App::Current->Resources->Lookup("CurrentFeed")); m_feedUri = feedData->Uri; auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle); if (feedItem != nullptr) { DefaultViewModel->Insert("Title", m_itemTitle); // Display the web page. contentView->Navigate(feedItem->Link); } } // We are resuming from suspension: else { // We are resuming, and might not have our FeedData object yet // so must get it asynchronously and wait on the result. String^ uri = safe_cast<String^>(pageState->Lookup("FeedUri")); auto feedDataOp = FeedDataSource::GetFeedAsync(uri); //URL auto feedDataTask = create_task(feedDataOp); feedDataTask.then([this, pageState](FeedData^ feedData) { App::Current->Resources->Insert("CurrentFeed", feedData); m_feedUri = feedData->Uri; m_itemTitle = safe_cast<String^>(pageState->Lookup("Item")); auto feedItem = FeedDataSource::GetFeedItem(feedData, m_itemTitle); if (feedItem != nullptr) { DefaultViewModel->Insert("Title", m_itemTitle); // Display the web page. contentView->Navigate(feedItem->Link); } }); } }
-
In contrast to LoadState, SaveState consists of only two lines of code. As in SplitPage, we save the URI of the feed because we need it in LoadState to look up the FeedItem in the case of resuming from termination.
/// <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="pageState">An empty map to be populated with serializable state.</param> void DetailPage::SaveState(IMap<String^, Object^>^ pageState) { // Store the itemTitle in case we are suspended or terminated. pageState->Insert("Item", m_itemTitle); pageState->Insert("FeedUri", m_feedUri); }
App bars
Most of the navigation in our blog reader app happens when the user picks an item in the UI. But on the Split page, we must provide a way for the user to go to the detail view of the blog post. We could put a button somewhere on the page, but that would distract from the core app experience, which is reading. Instead, we put the button in an app bar that's hidden until the user needs it. We'll add an app bar with a button to navigate to the detail page.
An app bar is a piece of UI that's hidden by default, and is shown or dismissed when the user swipes from the edge of the screen or interacts with the app, or clicks the right mouse button. It can present navigation, commands, and tools to the user. An app bar can appear at the top of the page, at the bottom of the page, or both. We recommend that you put navigation in the top app bar, and tools and commands in the bottom app bar. To add an app bar in XAML, we assign an AppBar control to the TopAppBar or BottomAppBar property of a Page. and then embed an AppBarButton element in the AppBar.
To add a button to the Split page app bar
-
Paste this code after the Page.Resources node to create a top app bar that contains an AppbarButton that points forward
<Page.TopAppBar> <AppBar Padding="10,0,10,0"> <Grid> <AppBarButton x:Name="fwdButton" Height="95" Margin="150,46,0,0" Command="{Binding NavigationHelper.GoForwardCommand, ElementName=pageRoot}" AutomationProperties.Name="Forward" AutomationProperties.AutomationId="ForwardButton" AutomationProperties.ItemType="Navigation Button" HorizontalAlignment="Right" Icon="Forward" Click="fwdButton_Click"/> </Grid> </AppBar> </Page.TopAppBar>
To add navigation to the detail view
-
Add the following method signature to the SplitPage class in SplitPage.xaml.h:
void fwdButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); -
Add the following
#includedirective andusingstatement in SplitPage.xaml.cpp:#include "DetailPage.xaml.h" ... using namespace Windows::UI::Xaml::Interop;
-
Add this method body in SplitPage.xaml.cpp:
void SplitPage::fwdButton_Click(Object^ sender, RoutedEventArgs^ e) { // Navigate to the appropriate destination page, and configure the new page // by passing required information as a navigation parameter. auto selectedItem = dynamic_cast<FeedItem^>(this->itemListView->SelectedItem); // selectedItem will be nullptr if the user invokes the app bar // and clicks on "view web page" without selecting an item. if (this->Frame != nullptr && selectedItem != nullptr) { auto itemTitle = safe_cast<String^>(selectedItem->Title); this->Frame->Navigate(TypeName(DetailPage::typeid), itemTitle); } }
- At this point, the basic app functionality is complete! Try building the sample and running it in the Simulator to test the navigation between pages in both portrait and landscape modes. When you choose an item on ItemsPage, the app should navigate to SplitPage. On SplitPage you can use the back button at the top to go back to ItemsPage, or you can swipe from the top of the screen or right-click to invoke the app bar, and press or click its button to go to the DetailPage. DetailPage has a back button that takes you to SplitPage. As you change the selected item in SplitPage, the preview of the page should appear (note the difference in behavior between landscape mode and portrait mode).
Adding animations and transitions
When we talk about animations, we often think of objects bouncing around on the screen. But in XAML, an animation is essentially just a way to change the value of a property on an object. This makes animations useful for a lot more than just bouncing balls. In our blog reader app, we use some default animations and transitions to adapt our UI to different layouts and orientations. We find these in the Windows.UI.Xaml.Media.Animation namespace.
Adding theme animations
A theme animation is a pre-configured animation that we can put in a Storyboard. The PopInThemeAnimation makes the web view slide in from right to left when the page loads. Increasing the value of the FromHorizontalOffset property makes the effect more extreme. Here, we put the PopInThemeAnimation in a Storyboard and make it a resource in DetailPage.xaml. Set the target of the animation to be the Border that surrounds our web content. This animates the Border and everything in it.
To add a theme animation to the Detail page
-
Paste the following XAML snippet into the
Page.Resourcesnode in DetailPage.xaml:<Storyboard x:Name="PopInStoryboard"> <PopInThemeAnimation Storyboard.TargetName="contentViewBorder" FromHorizontalOffset="400"/> </Storyboard>
-
Paste the following code into the beginning of the DetailPage::LoadState method in DetailPage.xaml.cpp. Our override of LoadState is called by the OnNavigatedTo method in the base class LayoutAwarePage:
// Run the PopInThemeAnimation. Windows::UI::Xaml::Media::Animation::Storyboard^ sb = dynamic_cast<Windows::UI::Xaml::Media::Animation::Storyboard^>(this->FindName("PopInStoryboard")); if (sb != nullptr) { sb->Begin(); } //... rest of method as before
Adding theme transitions
A theme transition is a complete set of animations and a Storyboard that's combined into a prepackaged behavior that we can attach to a UI element. A ContentThemeTransition is used with a ContentControl, and is automatically triggered when the content of the control changes.
In our app, let's add a theme transition to the TextBlock that holds the post titles in the Split page list view. When the content of the TextBlock changes, the ContentThemeTransition is triggered and runs automatically. The animation is predefined, and we don't have to do anything to run it. We just attach it to the TextBlock.
To add a theme transition to SplitPage.xaml
-
In SplitPage.xaml, the TextBlock that's named
pageTitleis an empty-element tag. To add a theme transition, we embed it in the TextBlock and therefore need to change the TextBlock to have an opening and closing tag. Replace the existing tag with the following XAML node:<TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" Style="{StaticResource HeaderTextBlockStyle}" IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40"> <TextBlock.Transitions> <TransitionCollection> <ContentThemeTransition /> </TransitionCollection> </TextBlock.Transitions> </TextBlock>
When the content of the TextBlock changes, the ContentThemeTransition is triggered and runs automatically. The animation is predefined, and we don't have to do anything to run it. We just attach it to the TextBlock. For more info, and a list of theme animations and transitions, see QuickStart: Animations.
Creating a consistent look with styles
We want to make our blog reader app look and feel like the Windows Team Blogs website. We want our users to have a seamless experience when they move between the website and our app. The default dark theme of our new Windows UI doesn't match very well with the Windows Team Blogs website. This is most clear on the detail page, where we load the actual blog page into a WebView, as shown here:

To give our app a consistent appearance that we can update as required, we use brushes and styles. A Brush lets us define a look in one place and use it wherever we need it. By using a Style, we can set values for the properties of a control and reuse those settings across our app.
Before we get into the details, let's look at how we use a brush to set the background color of the pages in our app. Each page in our app has a root Grid that has a Background property that's set to define the background color of the page. We could set each page background individually, like this: <Grid Background="Blue">. However, a better way is to define a Brush as a resource and use it to define the background color of all of our pages. This is how it's done in the Visual Studio templates, as we see here: <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">.
This is fine to set the Background property of the Grid, but you typically need to set more than one property to get the look you want. You can group settings for any number of properties together is a Style, and apply the Style to the control.
You can define resources in an individual page's XAML file, in the App.xaml file, or in a separate resource dictionary XAML file. Where the resource is defined determines the scope in which it can be used. A resource dictionary XAML file can be shared across apps, and more than one resource dictionary can be merged in a single app.
In our blog reader app, we define resources in App.xaml to make them available across our entire app. And we have some resources defined in the XAML file for individual pages. These resources are available only in the page where they are defined. If resources with the same key are defined in both App.xaml and in a page, the resource in the page overrides the resource in App.xaml. In the same way, a resource defined in App.xaml will override a resource defined with the same key in a separate resource dictionary file. For more info, see Quickstart: Styling controls.
In aStyle definition, we need a TargetType attribute and a collection of one or more Setters. We set the TargetType to a string that specifies the type that the Style is applied to, in this case, a Panel. If you try to apply a Style to a control that doesn't match the TargetType attribute, an exception occurs. Each Setter element requires a Property and aValue. These property settings indicate what control property the setting applies to, and the value to set for that property.
To change the Background of our pages, we need to replace the ApplicationPageBackgroundThemeBrush with our own custom brush. For our custom brush, we use the Color #FF0A2562, which is a nice blue that fits with the colors on the http://windowsteamblog.com site. To replace the system theme brush, we create a new Style that is based on LayoutRootStyle, and change the Background property there.
To set the background color for all pages
-
To define a new style for the layout root, paste these brush and style definitions into the ResourceDictionary in App.xaml after the <ResourceDictionary.MergedDictionaries> node:
<SolidColorBrush x:Key="WindowsBlogBackgroundBrush" Color="#FF0A2562"/> <Style x:Key="WindowsBlogLayoutRootStyle" TargetType="Panel" BasedOn="{StaticResource LayoutRootStyle}"> <Setter Property="Background" Value="{StaticResource WindowsBlogBackgroundBrush}"/> </Style>Important Because we based a style on a system style in StandardStyles.xaml, the ResourceDictionary that includes StandardStyles.xaml has to be declared before our app ResourceDictionary in the MergedDictionaries. If it's not, the XAML parser can't find the
LayoutRootStylethat our style is based on. -
Update the root Grid in ItemsPage.xaml, SplitPage.xaml, and DetailPage.xaml to use
WindowsBlogLayoutRootStyle.<Grid Style="{StaticResource WindowsBlogLayoutRootStyle}">
Our new Style is just like the default style, but with a blue background. The line
BasedOn="{StaticResource LayoutRootStyle}"tells our new Style to inherit fromLayoutRootStyleany properties that we don't explicitly set. -
Press F5 to build and run the app, and see the blue pages.
Control and data templates
To give our app the look and feel of the Windows Team Blogs website, we also use custom data templates in addition to Brushes and Styles. We talked about data templates in the section Displaying data.
To add a control template for the date
-
In App.xaml, add a ControlTemplate that defines a square block that shows the date. Define this in App.xaml so we can use it in both ItemsPage.xaml and SplitPage.xaml.
<Application.Resources> <ResourceDictionary> ... <ControlTemplate x:Key="DateBlockTemplate"> <Canvas Height="86" Width="86" Margin="8,8,0,8" HorizontalAlignment="Left" VerticalAlignment="Top"> <TextBlock TextTrimming="WordEllipsis" TextWrapping="NoWrap" Width="Auto" Height="Auto" Margin="8,0,4,0" FontSize="32" FontWeight="Bold"> <TextBlock.Text> <Binding Path="PubDate" Converter="{StaticResource dateConverter}" ConverterParameter="month" /> </TextBlock.Text> </TextBlock> <TextBlock TextTrimming="WordEllipsis" TextWrapping="Wrap" Width="40" Height="Auto" Margin="8,0,0,0" FontSize="34" FontWeight="Bold" Canvas.Top="36"> <TextBlock.Text> <Binding Path="PubDate" Converter="{StaticResource dateConverter}" ConverterParameter="day" /> </TextBlock.Text> </TextBlock> <Line Stroke="White" StrokeThickness="2" X1="54" Y1="46" X2="54" Y2="80" /> <TextBlock TextWrapping="Wrap" Width="20" Height="Auto" FontSize="{StaticResource ControlContentThemeFontSize}" Canvas.Top="42" Canvas.Left="60"> <TextBlock.Text> <Binding Path="PubDate" Converter="{StaticResource dateConverter}" ConverterParameter="year" /> </TextBlock.Text> </TextBlock> </Canvas> </ControlTemplate> ... </ResourceDictionary> </Application.Resources>
Notice that this template defines parameters "day," "month," and "year" that are passed to the new Convert function that we created in the DateConverter earlier in part 2. We format the day, month and year as independent elements, each with a different font size, so we had to write new functionality in the Convert method to return the parts of the date separately.
To add a data template for the items page
-
In ItemsPage.xaml, we add these resources to define the look of the grid items in the default view. Notice that we apply the new styles that we defined earlier.
<Page.Resources> ... <!-- light blue --> <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF557EB9"/> <!-- Grid Styles --> <Style x:Key="GridTitleTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BasicTextStyle}"> <Setter Property="FontSize" Value="26.667"/> <Setter Property="Margin" Value="12,0,12,2"/> </Style> <Style x:Key="GridDescriptionTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BasicTextStyle}"> <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"> <Border Background="{StaticResource BlockBackgroundBrush}" /> <TextBlock Text="{Binding Title}" Style="{StaticResource GridTitleTextStyle}"/> <TextBlock Text="{Binding Description}" Style="{StaticResource GridDescriptionTextStyle}" /> <StackPanel VerticalAlignment="Bottom" Orientation="Horizontal" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}"> <TextBlock Text="Last Updated" Margin="12,4,0,8" Height="42"/> <TextBlock Text="{Binding PubDate, Converter={StaticResource dateConverter}}" Margin="12,4,12,8" /> </StackPanel> </Grid> </DataTemplate> </Page.Resources>
-
Also in ItemsPage.xaml, update the ItemTemplate property of the itemGridView to use our DefaultGridItemTemplate resource instead of Standard250x250ItemTemplate, which is the default template that's defined in StandardStyles.xaml.
<GridView x:Name="itemGridView" AutomationProperties.AutomationId="ItemsGridView" AutomationProperties.Name="Items" TabIndex="1" Grid.Row="1" Margin="0,-4,0,0" Padding="116,0,116,46" SelectionMode="None" IsItemClickEnabled="True" ItemsSource="{Binding Source={StaticResource itemsViewSource}}" ItemTemplate="{StaticResource DefaultGridItemTemplate}" ItemClick="ItemView_ItemClick"/>
To add a data template for the Split page
-
In SplitPage.xaml, add these resources to define the look of the list items in the default view:
<Page.Resources> ... <!-- green --> <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF6BBD46"/> <DataTemplate x:Key="DefaultListItemTemplate"> <Grid HorizontalAlignment="Stretch" Width="Auto" Height="110" Margin="10,10,10,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!-- Green date block --> <Border Background="{StaticResource BlockBackgroundBrush}" Width="110" Height="110" /> <ContentControl Template="{StaticResource DateBlockTemplate}" /> <StackPanel Grid.Column="1" HorizontalAlignment="Left" Margin="12,8,0,0"> <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap" MaxHeight="72" Foreground="#FFFE5815" /> <TextBlock Text="{Binding Author}" FontSize="18.667" /> </StackPanel> </Grid> </DataTemplate> ... </Page.Resources>
-
In SplitPage.xaml, we also update the ItemTemplate property in itemListView to use our DefaultListItemTemplate resource instead of Standard130ItemTemplate, which is the default template. Here's the updated XAML for itemListView.
<ListView x:Name="itemListView" AutomationProperties.AutomationId="ItemsListView" AutomationProperties.Name="Items" TabIndex="1" Grid.Row="1" Margin="-10,-10,0,0" Padding="120,0,0,60" ItemsSource="{Binding Source={StaticResource itemsViewSource}}" IsSwipeEnabled="False" SelectionChanged="ItemListView_SelectionChanged" ItemTemplate="{StaticResource DefaultListItemTemplate}"/>
With our styles applied, our app fits with the look and feel of the Windows Team Blogs website:



By using styles and basing them on other styles, we can quickly define and apply different looks for our app. In the next section, we combine what we know about animations and styles to make our app fluidly adapt to various layouts and orientations while it's running.
Adapting to different layouts
Typically, an app is designed to be viewed full-screen in the landscape orientation. But a new Windows UI must adapt to different orientations and layouts. Specifically, it must support both landscape and portrait orientations. In the landscape orientation, it must support Full Screen, FilledOrNarrow, and Snapped layouts. We saw when we made our blog reader page from a blank template that it didn't look as good in the portrait orientation. In this section, we learn how we can make our app look good in any resolution or orientation.
When developing in Visual Studio, you can use the Simulator debugger to test layouts. You can use the debugger tool bar to debug with the Simulator when you press F5.
The Visual Studio templates include code that handles changes to the view state. This code is in the LayoutAwarePage.cs or LayoutAwarePage.vb file, and it maps our app state to visual states that are defined in our XAML. Because the page layout logic is provided for us, we only have to provide the views to be used for each of the page's visual states.
To use XAML to transition between different views, we use the VisualStateManger to define different VisualStates for our app. Here we have a VisualStateGroup that's defined in ItemsPage.xaml. This group has 4 VisualStates, which are named FullScreenLandscape, FilledOrNarrow, FullScreenPortrait, and Snapped. Different VisualStates from the same VisualStateGroup can't be used at the same time. Each VisualState has animations that tell the app what has to change from the baseline that's specified in the XAML for the UI.
...not for pasting! <!--App Orientation States--> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="FullScreenLandscape" /> <VisualState x:Name="FilledOrNarrow"> ... </VisualState> <VisualState x:Name="FullScreenPortrait"> ... </VisualState> <VisualState x:Name="Snapped"> ... </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups>
We use the FullScreenLandscape state when the app is full-screen in the landscape orientation. Because we designed our default UI for this view, no changes are required and this is just an empty VisualState.
We use the FilledOrNarrow state when the user has another app that's snapped to one side of the screen. In this case, the Items View page just moves over, and no changes are required. This also is just an empty VisualState.
We use the FullScreenPortrait state when our app is rotated from landscape to portrait orientation. In this visual state, we have two animations. One changes the style that's used for the Back button, and the other one changes the margin of itemGridView so that everything fits better on the screen. In the XAML for the iage UI, both a GridView and a ListView are defined and bound to the data collection. By default, the GridView is shown, and the ListView is collapsed. In the Portrait state, we have three animations that collapse the GridView, show the ListView, and change the Style of the Back button to make it smaller.
...Not for pasting! <!-- The entire page respects the narrower 100-pixel margin convention for portrait --> <VisualState x:Name="FullScreenPortrait"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Margin"> <DiscreteObjectKeyFrame KeyTime="0" Value="100,0,90,60"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState>
We use the Snapped state when the user has two apps that are showing and our app is the narrower of the two. In this state, our app is only 320 device-independent pixels (DIPs) wide, so more drastic changes are required. In the XAML for the Items page UI, both a GridView and a ListView are defined and bound to the data collection. By default, itemGridViewScroller is shown, and itemListViewScroller is collapsed. In the Snapped state, we have four animations that collapse itemListViewScroller, show itemListViewScroller, and change the Style of the Back button and page title to make them smaller.
...not for pasting! <!-- The Back button and title have different styles when they're snapped, and the list representation is substituted for the grid that's displayed in all other view states. --> <VisualState x:Name="Snapped"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListScrollViewer" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridScrollViewer" Storyboard.TargetProperty="Visibility"> <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState>
In the Creating a consistent look with styles section of this tutorial, we created styles and templates to give our app a custom look. The default landscape view uses these styles and templates. To keep the custom look in different views, we also have to create custom styles and templates for those views.
To add a data template for the Items page Snapped view
-
In ItemsPage.xaml, we created a data template for the grid items. We also have to provide a new data template for the list items that are shown in Snapped view. We name this template NarrowListItemTemplate and add it to the resources section of ItemsPage.xaml, just after the DefaultGridItemTemplate resource.
<Page.Resources> ... <!-- Used in Snapped view --> <DataTemplate x:Key="NarrowListItemTemplate"> <Grid Height="80"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Border Background="{StaticResource BlockBackgroundBrush}" Width="80" Height="80" /> <ContentControl Template="{StaticResource DateBlockTemplate}" Margin="-12,-12,0,0"/> <StackPanel Grid.Column="1" HorizontalAlignment="Left" Margin="12,8,0,0"> <TextBlock Text="{Binding Title}" MaxHeight="56" TextWrapping="Wrap"/> </StackPanel> </Grid> </DataTemplate> </Page.Resources>
-
To make the ListView show our new data template, we update the ItemTemplate property of itemListView to use our NarrowListItemTemplate resource instead of Standard80ItemTemplate, which is the default template that's defined in StandardStyles.xaml. In ItemsPage.xaml, replace the itemListView with the following snippet:
<ListView x:Name="itemListView" AutomationProperties.AutomationId="ItemsListView" AutomationProperties.Name="Items" TabIndex="1" Grid.Row="1" Visibility="Collapsed" Margin="0,-10,0,0" Padding="10,0,0,60" ItemsSource="{Binding Source={StaticResource itemsViewSource}}" ItemTemplate="{StaticResource NarrowListItemTemplate}" SelectionMode="None" IsItemClickEnabled="True" ItemClick="ItemView_ItemClick"/>
To add a data template for Split page Snapped and FilledOrNarrow views
-
In SplitPage.xaml, we create a ListView template that’s used in the FilledOrNarrow and Snapped views, and in the FullScreenLandscape view if the screen width is less than 1366 DIPs. We also name this template NarrowListItemTemplate and add it to the resources section of SplitPage.xaml, just after the DefaultListItemTemplate resource.
<Page.Resources> ... <!-- Used in FilledOrNarrow and Snapped views --> <DataTemplate x:Key="NarrowListItemTemplate"> <Grid Height="80"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Border Background="{StaticResource BlockBackgroundBrush}" \ Width="80" Height="80"/> <ContentControl Template="{StaticResource DateBlockTemplate}" Margin="-12,-12,0,0"/> <StackPanel Grid.Column="1" HorizontalAlignment="Left" Margin="12,8,0,0"> <TextBlock Text="{Binding Title}" MaxHeight="56" Foreground="#FFFE5815" TextWrapping="Wrap"/> <TextBlock Text="{Binding Author}" FontSize="12" /> </StackPanel> </Grid> </DataTemplate> ... </Page.Resources>
-
To use this data template, we update the visual states where it's used. In the XAML for the Snapped and FilledOrNarrow visual states, we find the animation that targets the ItemTemplate property of itemListView. We then change the value to use the NarrowListItemTemplate resource instead of the default Standard80ItemTemplate resource. Here's the updated XAML for the animation.
<VisualState x:Name="FilledOrNarrow"> <Storyboard> .... <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="ItemTemplate"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NarrowListItemTemplate}"/> </ObjectAnimationUsingKeyFrames> .... </Storyboard> </VisualState> ... <VisualState x:Name="Snapped"> <Storyboard> .... <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="ItemTemplate"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource NarrowListItemTemplate}"/> </ObjectAnimationUsingKeyFrames> .... </Storyboard> </VisualState>
-
We also replaced the item detail section of the Split page with our own detail section that uses a WebView. Because we made this change, the animations in the Snapped_Detail visual state target elements that no longer exist. These will cause errors when we use this visual state, so we have to remove them. In SplitPage.xaml, we remove these animations from the Snapped_Detail visual state.
<VisualState x:Name="Snapped_Detail"> <Storyboard> ... <!-- REMOVE THESE ELEMENTS: --> <!--<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemDetailTitlePanel" Storyboard.TargetProperty="(Grid.Row)"> <DiscreteObjectKeyFrame KeyTime="0" Value="0"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemDetailTitlePanel" Storyboard.TargetProperty="(Grid.Column)"> <DiscreteObjectKeyFrame KeyTime="0" Value="0"/> </ObjectAnimationUsingKeyFrames>--> ... <!--<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemSubtitle" Storyboard.TargetProperty="Style"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource CaptionTextStyle}"/> </ObjectAnimationUsingKeyFrames>--> </Storyboard> </VisualState>
To adjust the WebView margin in Snapped view
-
In DetailPage.xaml, we just have to adjust the margin of our WebView in the Snapped view to use all of the available space. In the XAML for the Snapped visual state, we add an animation to change the value of the Margin property on contentViewBorder, as shown here:
<VisualState x:Name="Snapped"> <Storyboard> ... <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentViewBorder" Storyboard.TargetProperty="Margin"> <DiscreteObjectKeyFrame KeyTime="0" Value="20,5,20,20"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState>
Adding a splash screen and logo
The first impression our app makes on a user comes from the splash screen. The splash screen is shown when a user starts the app, and gives immediate feedback to the user while our app initializes its resources. It's dismissed when the first page of the app is ready to show.
The splash screen consists of a background color and an image that's 624 x 300 pixels. We set these values in the Package.appxmanifest file. You can open this file in the manifest editor. On the Application UI tab of the manifest editor, we set the path of our splash screen image and the background color. The project template provides a default blank image that's named SplashScreen.png. We replace this with our own splash screen image, which clearly identifies our app and immediately draws users into it. Here's the splash screen for our blog reader:

The basic splash screen works well for our blog reader, but you can also extend the splash by using the properties and methods of the SplashScreen class. You can use the SplashScreen class to get the coordinates of the splash screen and use them to place the first page of the app. And you can find out when the splash screen is dismissed so that you know it's time to start any content-entrance animations for the app.
What's next
In this article, we learned 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, an app bar, and a splash screen to make our app fit the personality of Windows 8. 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:
- Selling apps
- One subject not covered in this tutorial is how to make your app accessible. For more information, see Accessibility.
- Check out the learning and reference resource list for creating Windows Windows Store apps with C#, C++, and VB.
Related topics
Build date: 6/21/2013
