Поделиться через


Part 5: Create a blog reader (Windows Store apps using C#/VB and XAML)

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

We introduce the essential code and concepts you need to create a Windows Store app using C# or Visual Basic. You'll use XAML to define the UI, and your selected language to write the app logic.

Important   This tutorial is intended for use with Microsoft Visual Studio 2012 and Windows 8. Parts of it will not work correctly with Microsoft Visual Studio 2013 and Windows 8.1.

 

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

Roadmap: How does this topic relate to others? See: Roadmap for Windows Runtime apps using C# or Visual Basic.

Before you start...

Objectives

In this tutorial, we take a quick tour of the features that you'll use to build Windows Store apps. Through the process of creating a simple blog reader app, we introduce concepts that are core to development with XAML, including layout, controls, templates, and data binding. You learn how to use the page templates and navigation that are built into Microsoft Visual Studio Express 2012 for Windows 8 to quickly start your app development. You then learn how to use custom styles to modify the look of the app, and how to adapt the UI to various layouts and views. Finally, we briefly discuss integrating the app with Windows 8 and publishing it to the Windows Store. By the time you complete this tutorial, you'll be prepared to start building your own Windows Store apps.

Hello World

When you create your Windows Store app using C# or Visual Basic, you typically define the UI using XAML, and write your app logic in an associated code behind file in your selected language. The XAML UI framework for Windows Store apps using C# or Visual Basic is in the Windows.UI.Xaml.* namespaces of the Windows Runtime. If you've written apps using Windows Presentation Foundation (WPF), Microsoft Silverlight, or Silverlight for Windows Phone, you're already familiar with this programming model and you can use this experience to create your Windows Store app using C# or Visual Basic.

The example here shows the XAML that defines the UI for a simple "Hello, world" app and its associated code behind page. Even this simple example shows several concepts that are important to the XAML-based programming model, including partial classes, layout, controls, properties, and events.

<Page
    x:Class="WindowsBlogReader.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WindowsBlogReader"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel>
            <Button Content="Click Me" Click="HelloButton_Click" />
            <TextBlock x:Name="DisplayText" FontSize="48" />
        </StackPanel>
    </Grid>
</Page>
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace WindowsBlogReader
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void HelloButton_Click(object sender, RoutedEventArgs e)
        {
            DisplayText.Text = "Hello, world";
        }
    }
}
NotInheritable Public Class MainPage
    Inherits Page

    Public Sub New()
        InitializeComponent()
    End Sub
    
    Private Sub HelloButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        DisplayText.Text = "Hello, world"
    End Sub
End Class

A "Hello, world" app is good place to start. But it doesn't take you very far down the road to profitability, so we'll take a different path as we explore building Windows Store apps. The sample app that you use to get started is a simple blog reader that downloads and displays data from a Really Simple Syndication (RSS) 2.0 or Atom 1.0 feed. It seems appropriate to use the feeds from the Windows team blogs site. When you're done, the app looks like this.

Creating Windows Store apps in Visual Studio

In this section, you will learn how to:

  • create a new Windows Store app project in Visual Studio Express 2012 for Windows 8.

Visual Studio is a powerful Integrated Development Environment (IDE) for developing Windows apps. It provides source file management; integrated build, deployment and launching support; XAML, Visual Basic, C#, C++, graphics, and manifest editing; debugging, and more. Visual Studio comes in several editions, but you'll use Visual Studio Express 2012 for Windows 8. You can download it for free along with the Windows Software Development Kit (SDK) for Windows 8 so that you have everything you need to build, package and deploy your Windows Store apps.

To get started creating an app, you create a new Windows Store project using C# or Visual Basic. Visual Studio Express 2012 for Windows 8 includes several templates for Windows Store projects that give you a head start on apps using a variety of layouts. The Blank App (XAML) project template provides the minimum files you need for any Windows Store app.

To create a new Windows Store project

  1. Open Visual Studio Express 2012 for Windows 8.

  2. Select File > New Project. The New Project dialog opens.

  3. Under Templates in the left pane, expand Visual C# or Visual Basic.

  4. Select the Windows Store template type.

  5. In the center pane, select Blank App (XAML).

  6. Enter a name for the project. Name this project "WindowsBlogReader".

    Here's a new project being created in Visual Studio Express 2012 for Windows 8.

  7. Click OK. Your project files are created.

When you create your project, Visual Studio creates the project files and displays them in Solution Explorer. Let's look at the folders and files that the Blank App (XAML) template creates.

File Name Description
Properties/AssemblyInfo (.vb or .cs) Contains the name and version metadata that is embedded into the generated assembly.
Package.appxmanifest Contains metadata that describes your app, including display name, description, logos, and capabilities.
Assets/* The default logo and splash screen images that you can replace with your own.
Common/StandardStyles.xaml Contains default styles and templates for the app.
App.xaml, App.xaml.cs/vb These files specify app-level logic. The App class is required to display the user interface.
MainPage.xaml The default start page that you use to create the user interface.
MainPage.xaml.cs/vb The code-behind file that contains the logic for the default start page.

 

Getting data into an app

In this section, you will learn how to:

  • create a custom data class
  • retrieve an RSS or Atom data feed asynchronously.

Before you create the UI for the app, you write the code to get the blog feed data so you have something to show in the UI. The Windows team blogs expose the full text of the posts in both RSS and Atom form. The blog data that you want to display in the reader app is the title, author, date, and content from each of the latest blog posts.

To start, you need to download the data for each of the posts. Fortunately, the Windows Runtime contains a set of classes that does a lot of the work of processing the feed data for you. You find these classes in the Windows.Web.Syndication namespace. It’s possible to use these classes directly to show the data in the UI. But in the blog reader, you create your own data classes. This gives you some additional flexibility and allows you to treat RSS and Atom feeds in the same way.

You use 3 classes to hold and retrieve the feed data in the blog reader app. You put all 3 classes in one file named FeedData.cs/vb. The FeedData class holds info about the RSS or Atom feed. The FeedItem class holds info about individual blog posts that the feed contains. The FeedDataSource class contains a collection of feeds and a method to retrieve the feeds from the network.

To add data classes to the project

  1. Select Project > Add Class. The New Item dialog box opens.

  2. Enter "FeedData" as the name for the class file.

  3. Click Add. Your new class file is created.

  4. Copy this code into the FeedData.cs/vb file. Replace any code that is in the file.

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Threading.Tasks;
    using Windows.Web.Syndication;
    
    namespace WindowsBlogReader
    {
        // FeedData
        // Holds info for a single blog feed, including a list of blog posts (FeedItem).
        public class FeedData
        {
            public string Title { get; set; }
            public string Description { get; set; }
            public DateTime PubDate { get; set; }
    
            private List<FeedItem> _Items = new List<FeedItem>();
            public List<FeedItem> Items
            {
                get
                {
                    return this._Items;
                }
            }
        }
    
        // FeedItem
        // Holds info for a single blog post.
        public class FeedItem
        {
            public string Title { get; set; }
            public string Author { get; set; }
            public string Content { get; set; }
            public DateTime PubDate { get; set; }
            public Uri Link { get; set; }
        }
    
        // FeedDataSource
        // Holds a collection of blog feeds (FeedData), and contains methods needed to
        // retreive the feeds.
        public class FeedDataSource
        {
            private ObservableCollection<FeedData> _Feeds = new ObservableCollection<FeedData>();
            public ObservableCollection<FeedData> Feeds
            {
                get
                {
                    return this._Feeds;
                }
            }
    
            public async Task GetFeedsAsync()
            {
                Task<FeedData> feed1 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/developers/atom.aspx");
                Task<FeedData> feed2 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/windowsexperience/atom.aspx");
                Task<FeedData> feed3 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/extremewindows/atom.aspx");
                Task<FeedData> feed4 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/business/atom.aspx");
                Task<FeedData> feed5 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx");
                Task<FeedData> feed6 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/windowssecurity/atom.aspx");
                Task<FeedData> feed7 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/springboard/atom.aspx");
                Task<FeedData> feed8 =
                    GetFeedAsync("https://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx");
                // There is no Atom feed for this blog, so use the RSS feed.
                Task<FeedData> feed9 =
                    GetFeedAsync("https://windowsteamblog.com/windows_live/b/windowslive/rss.aspx");
                Task<FeedData> feed10 =
                    GetFeedAsync("https://windowsteamblog.com/windows_live/b/developer/atom.aspx");
                Task<FeedData> feed11 =
                    GetFeedAsync("https://windowsteamblog.com/ie/b/ie/atom.aspx");
                Task<FeedData> feed12 =
                    GetFeedAsync("https://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx");
                Task<FeedData> feed13 =
                    GetFeedAsync("https://windowsteamblog.com/windows_phone/b/wmdev/atom.aspx");
                Task<FeedData> feed14 =
                    GetFeedAsync("https://windowsteamblog.com/windows_phone/b/windowsphone/atom.aspx");
    
                this.Feeds.Add(await feed1);
                this.Feeds.Add(await feed2);
                this.Feeds.Add(await feed3);
                this.Feeds.Add(await feed4);
                this.Feeds.Add(await feed5);
                this.Feeds.Add(await feed6);
                this.Feeds.Add(await feed7);
                this.Feeds.Add(await feed8);
                this.Feeds.Add(await feed9);
                this.Feeds.Add(await feed10);
                this.Feeds.Add(await feed11);
                this.Feeds.Add(await feed12);
                this.Feeds.Add(await feed13);
                this.Feeds.Add(await feed14);
            }
    
            private async Task<FeedData> GetFeedAsync(string feedUriString)
            {
                Windows.Web.Syndication.SyndicationClient client = new SyndicationClient();
                Uri feedUri = new Uri(feedUriString);
    
                try
                {
                    SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri);
    
                    // This code is executed after RetrieveFeedAsync returns the SyndicationFeed.
                    // Process the feed and copy the data you want into the FeedData and FeedItem classes.
                    FeedData feedData = new FeedData();
    
                    if (feed.Title != null && feed.Title.Text != null)
                    {
                        feedData.Title = feed.Title.Text;
                    }
                    if (feed.Subtitle != null && feed.Subtitle.Text != null)
                    {
                        feedData.Description = feed.Subtitle.Text;
                    }
                    if (feed.Items != null && feed.Items.Count > 0)
                    {
                        // Use the date of the latest post as the last updated date.
                        feedData.PubDate = feed.Items[0].PublishedDate.DateTime;
    
                        foreach (SyndicationItem item in feed.Items)
                        {
                            FeedItem feedItem = new FeedItem();
                            if (item.Title != null && item.Title.Text != null)
                            {
                                feedItem.Title = item.Title.Text;
                            }
                            if (item.PublishedDate != null)
                            {
                                feedItem.PubDate = item.PublishedDate.DateTime;
                            }
                            if (item.Authors != null && item.Authors.Count > 0)
                            {
                                feedItem.Author = item.Authors[0].Name.ToString();
                            }
                            // Handle the differences between RSS and Atom feeds.
                            if (feed.SourceFormat == SyndicationFormat.Atom10)
                            {
                                if (item.Content != null && item.Content.Text != null)
                                {
                                    feedItem.Content = item.Content.Text;
                                }
                                if (item.Id != null)
                                {
                                    feedItem.Link = new Uri("https://windowsteamblog.com" + item.Id);
                                }
                            }
                            else if (feed.SourceFormat == SyndicationFormat.Rss20)
                            {
                                if (item.Summary != null && item.Summary.Text != null)
                                {
                                    feedItem.Content = item.Summary.Text;
                                }
                                if (item.Links != null && item.Links.Count > 0)
                                {
                                    feedItem.Link = item.Links[0].Uri;
                                }
                            }
                            feedData.Items.Add(feedItem);
                        }    
                    }
                    return feedData;
                }
                catch (Exception)
                {
                    return null;
                }
            }
    
            // Returns the feed that has the specified title.
            public static FeedData GetFeed(string title)
            {
                // Simple linear search is acceptable for small data sets
                var _feedDataSource = App.Current.Resources["feedDataSource"] as FeedDataSource;
    
                var matches = _feedDataSource.Feeds.Where((feed) => feed.Title.Equals(title));
                if (matches.Count() == 1) return matches.First();
                return null;
            }
    
            // Returns the post that has the specified title.
            public static FeedItem GetItem(string uniqueId)
            {
                // Simple linear search is acceptable for small data sets
                var _feedDataSource = App.Current.Resources["feedDataSource"] as FeedDataSource;
                var _feeds = _feedDataSource.Feeds;
    
                var matches = _feedDataSource.Feeds.SelectMany(group => group.Items).Where((item) => item.Title.Equals(uniqueId));
                if (matches.Count() == 1) return matches.First();
                return null;
            }
        }
    }
    
    Imports Windows.Web.Syndication
    
    ' FeedData
    ' Holds info for a single blog feed, including a list of blog posts (FeedItem).
    Public Class FeedData
        Public Property Title() As String = String.Empty
        Public Property Description() As String = String.Empty
        Public Property PubDate() As DateTime
    
        Private _Items As New List(Of FeedItem)()
        Public ReadOnly Property Items() As List(Of FeedItem)
            Get
                Return Me._Items
            End Get
        End Property
    End Class
    
    ' FeedItem
    ' Holds info for a single blog post.
    Public Class FeedItem
        Public Property Title() As String = String.Empty
        Public Property Author() As String = String.Empty
        Public Property Content() As String = String.Empty
        Public Property PubDate() As DateTime
        Public Property Link() As Uri
    End Class
    
    ' FeedDataSource
    ' Holds a collection of blog feeds (FeedData), and contains methods needed to
    ' retreive the feeds.
    Public Class FeedDataSource
        Private _Feeds As New ObservableCollection(Of FeedData)()
        Public ReadOnly Property Feeds() As ObservableCollection(Of FeedData)
            Get
                Return Me._Feeds
            End Get
        End Property
    
        Public Async Function GetFeedsAsync() As Task
            Dim feed1 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/developers/atom.aspx")
            Dim feed2 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/windowsexperience/atom.aspx")
            Dim feed3 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/extremewindows/atom.aspx")
            Dim feed4 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/business/atom.aspx")
            Dim feed5 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/bloggingwindows/atom.aspx")
            Dim feed6 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/windowssecurity/atom.aspx")
            Dim feed7 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/springboard/atom.aspx")
            Dim feed8 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows/b/windowshomeserver/atom.aspx")
            ' There is no Atom feed for this blog, so use the RSS feed.
            Dim feed9 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows_live/b/windowslive/rss.aspx")
            Dim feed10 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows_live/b/developer/atom.aspx")
            Dim feed11 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/ie/b/ie/atom.aspx")
            Dim feed12 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows_phone/b/wpdev/atom.aspx")
            Dim feed13 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows_phone/b/wmdev/atom.aspx")
            Dim feed14 As Task(Of FeedData) =
                GetFeedAsync("https://windowsteamblog.com/windows_phone/b/windowsphone/atom.aspx")
    
            Me.Feeds.Add(Await feed1)
            Me.Feeds.Add(Await feed2)
            Me.Feeds.Add(Await feed3)
            Me.Feeds.Add(Await feed4)
            Me.Feeds.Add(Await feed5)
            Me.Feeds.Add(Await feed6)
            Me.Feeds.Add(Await feed7)
            Me.Feeds.Add(Await feed8)
            Me.Feeds.Add(Await feed9)
            Me.Feeds.Add(Await feed10)
            Me.Feeds.Add(Await feed11)
            Me.Feeds.Add(Await feed12)
            Me.Feeds.Add(Await feed13)
            Me.Feeds.Add(Await feed14)
        End Function
    
        Private Async Function GetFeedAsync(feedUriString As String) As Task(Of FeedData)
            Dim Client As New SyndicationClient
            Dim FeedUri As New Uri(feedUriString)
    
            Try
                Dim Feed As SyndicationFeed = Await Client.RetrieveFeedAsync(FeedUri)
    
                ' This code is executed after RetrieveFeedAsync returns the SyndicationFeed.
                ' Process the feed and copy the data you want into the FeedData and FeedItem classes.
                Dim FeedData As New FeedData
    
                If Feed.Title IsNot Nothing AndAlso Feed.Title.Text IsNot Nothing Then
                    FeedData.Title = Feed.Title.Text
                End If
                If Feed.Subtitle IsNot Nothing AndAlso Feed.Subtitle.Text IsNot Nothing Then
                    FeedData.Description = Feed.Subtitle.Text
                End If
                If Feed.Items IsNot Nothing AndAlso Feed.Items.Count > 0 Then
                    ' Use the date of the latest post as the last updated date.
                    FeedData.PubDate = Feed.Items(0).PublishedDate.DateTime
                    For Each Item As SyndicationItem In Feed.Items
                        Dim FeedItem As New FeedItem
                        If Item.Title IsNot Nothing AndAlso Item.Title.Text IsNot Nothing Then
                            FeedItem.Title = Item.Title.Text
                        End If
                        FeedItem.PubDate = Item.PublishedDate.DateTime
                        If Item.Authors IsNot Nothing AndAlso Item.Authors.Count > 0 Then
                            FeedItem.Author = Item.Authors(0).Name.ToString()
                        End If
                        ' Handle the differences between RSS and Atom feeds.
                        If Feed.SourceFormat = SyndicationFormat.Atom10 Then
                            If Item.Content IsNot Nothing AndAlso Item.Content.Text IsNot Nothing Then
                                FeedItem.Content = Item.Content.Text
                            End If
                            If Item.Id IsNot Nothing Then
                                FeedItem.Link = New Uri("https://windowsteamblog.com" + Item.Id)
                            End If
                        ElseIf Feed.SourceFormat = SyndicationFormat.Rss20 Then
                            If Item.Summary IsNot Nothing AndAlso Item.Summary.Text IsNot Nothing Then
                                FeedItem.Content = Item.Summary.Text
                            End If
                            If Item.Links IsNot Nothing AndAlso Item.Links.Count > 0 Then
                                FeedItem.Link = Item.Links(0).Uri
                            End If
                        End If
                        FeedData.Items.Add(FeedItem)
                    Next
                End If
                Return FeedData
    
            Catch Ex As Exception
                Return Nothing
            End Try
        End Function
    
        Public Shared Function GetFeed(title As String) As FeedData
            ' Simple linear search is acceptable for small data sets
            Dim _feedDataSource = DirectCast(App.Current.Resources("feedDataSource"), FeedDataSource)
    
            Dim matches = _feedDataSource.Feeds.Where(Function(feed) feed.Title.Equals(title))
            If matches.Count() = 1 Then
                Return matches.First()
            End If
            Return Nothing
        End Function
    
        Public Shared Function GetItem(uniqueId As String) As FeedItem
            ' Simple linear search is acceptable for small data sets
            Dim _feedDataSource = DirectCast(App.Current.Resources("feedDataSource"), FeedDataSource)
            Dim _feeds = _feedDataSource.Feeds
    
            Dim matches = _feedDataSource.Feeds.SelectMany(Function(group) group.Items).Where(Function(item) item.Title.Equals(uniqueId))
    
            If matches.Count() = 1 Then
                Return matches.First()
            End If
            Return Nothing
        End Function
    End Class
    
  5. Click Build > Build solution to make sure the solution builds without error.

Retrieving the feed data

Let's take a closer look at how you download the blog feeds. The Windows.Web.Syndication.SyndicationClient class retrieves a fully parsed RSS or Atom feed, so you can use the data without worrying about parsing XML. To download a feed using the SyndicationClient class, you have to use the asynchronous RetrieveFeedAsync method. The asynchronous programming model is common in the Windows Runtime to help apps remain responsive. Fortunately, much of the complexity you might expect when using asynchronous methods has been taken care of for you.

Using await in C# and Visual Basic

Using the await keyword in C# and Visual Basic, the code for retrieving the feed asynchronously is similar to the code you would use to retrieve the feed synchronously. Let's take a look.

In the GetFeedsAsync method, you call GetFeedAsync for each of the blog feeds you want to retrieve. Where possible, you pass in the URL for the Atom feed because it includes author data that you want to show. If there's no Atom feed, use the RSS feed. When each blog feed is returned, you add it to the FeedDataSource.Feeds collection.

To do this using synchronous methods, your code might look like this. Here, the GetFeed method returns a FeedData object. When the FeedData is returned, it's added to the Feeds collection.

public void GetFeeds()
{
    FeedData feed1 = 
        GetFeed("https://windowsteamblog.com/windows/b/developers/atom.aspx");
    ...
    this.Feeds.Add(feed1);
    ...
}
Public Sub GetFeedsAsync()
    Dim feed1 As FeedData =
        GetFeed("https://windowsteamblog.com/windows/b/developers/atom.aspx")
    ...
    Me.Feeds.Add(feed1)
    ...
End Function

Now let's look at how you do this using "async" methods, and see how the await keyword helps. The first thing to notice is that you add the async keyword to the method signature. You can use the await keyword only in a method that's defined as async. You specify the return type of GetFeedsAsync as Task.

public async Task GetFeedsAsync()
{
    Task<FeedData> feed1 = 
        GetFeedAsync("https://windowsteamblog.com/windows/b/developers/atom.aspx");
    ...
    this.Feeds.Add(await feed1);
    ...
}
Public Async Function GetFeedsAsync() As Task
    Dim feed1 As Task(Of FeedData) =
        GetFeedAsync("https://windowsteamblog.com/windows/b/developers/atom.aspx")
    ...
    Me.Feeds.Add(Await feed1)
    ...
End Function

In your async code, the GetFeedAsync method returns a Task<FeedData>, or Task(Of FeedData) in VB, that represents the FeedData that will eventually be the return value of the method.

At this point in the code:

this.Feeds.Add(await feed1);
Me.Feeds.Add(Await feed1)

the call to GetFeedAsync is made, and a Task<FeedData> is returned. Then the next line of code is executed (to get feed2), but feed1 is not added to the Feeds collection until the FeedData object is actually passed to the Task<FeedData> that's waiting for it. We come back to this shortly. Now let's look at what happens in the GetFeedAsync method.

You specify the return type of GetFeedAsync as Task<FeedData>. This tells the compiler to generate a Task that represents the FeedData object that the method retrieves.

private async Task<FeedData> GetFeedAsync(string feedUriString)
{
...
}
Private Async Function GetFeedAsync(feedUriString As String) As Task(Of FeedData)
...
End Function

Inside the method, you instantiate a SyndicationClient and call its RetrieveFeedAsync method to get the SyndicationFeed that contains the RSS or Atom info that you want.

Windows.Web.Syndication.SyndicationClient client = new SyndicationClient();
Uri feedUri = new Uri(feedUriString);
...
    SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri);
Dim Client As New SyndicationClient
Dim FeedUri As New Uri(feedUriString)
...
    Dim Feed As SyndicationFeed = Await Client.RetrieveFeedAsync(FeedUri)

Because RetrieveFeedAsync is an asynchronous method, you use the await keyword again. The await keyword tells the compiler to do a lot of work for you behind the scenes. The compiler schedules the rest of the method after this call as a callback to be executed when the call returns. It then immediately returns control to the calling thread, typically the UI thread, so that the app remains responsive. The Task<FeedData> that represents the eventual outcome of this method, a FeedData object, is returned to the caller at this point.

When RetrieveFeedAsync returns the SyndicationFeed with the data you want, the rest of the code in your method is executed. And, importantly, it's executed in the same thread context that you made the original call from (the UI thread), so you don't have to worry about using a dispatcher if you want to update the UI in this code. With the SyndicationFeed retrieved, you copy the parts you need into your FeedData and FeedItem data classes.

// This code is executed after RetrieveFeedAsync returns the SyndicationFeed.
// Process the feed and copy the data you want into the FeedData and FeedItem classes.
FeedData feedData = new FeedData();

if (feed.Title != null && feed.Title.Text != null)
{
    feedData.Title = feed.Title.Text;
}
if (feed.Subtitle != null && feed.Subtitle.Text != null)
{
    feedData.Description = feed.Subtitle.Text;
}
if (feed.Items != null && feed.Items.Count > 0)
{
    // Use the date of the latest post as the last updated date.
    feedData.PubDate = feed.Items[0].PublishedDate.DateTime;

    foreach (SyndicationItem item in feed.Items)
    {
        FeedItem feedItem = new FeedItem();
        if (item.Title != null && item.Title.Text != null)
        {
            feedItem.Title = item.Title.Text;
        }
        if (item.PublishedDate != null)
        {
            feedItem.PubDate = item.PublishedDate.DateTime;
        }
        if (item.Authors != null && item.Authors.Count > 0)
        {
            feedItem.Author = item.Authors[0].Name.ToString();
        }
        // Handle the differences between RSS and Atom feeds.
        if (feed.SourceFormat == SyndicationFormat.Atom10)
        {
            if (item.Content != null && item.Content.Text != null)
            {
                feedItem.Content = item.Content.Text;
            }
            if (item.Id != null)
            {
                feedItem.Link = new Uri("https://windowsteamblog.com" + item.Id);
            }
        }
        else if (feed.SourceFormat == SyndicationFormat.Rss20)
        {
            if (item.Summary != null && item.Summary.Text != null)
            {
                feedItem.Content = item.Summary.Text;
            }
            if (item.Links != null && item.Links.Count > 0)
            {
                feedItem.Link = item.Links[0].Uri;
            }
        }
        feedData.Items.Add(feedItem);
    }    
}
return feedData;
' This code is executed after RetrieveFeedAsync returns the SyndicationFeed.
' Process the feed and copy the data you want into the FeedData and FeedItem classes.
Dim FeedData As New FeedData

If Feed.Title IsNot Nothing AndAlso Feed.Title.Text IsNot Nothing Then
    FeedData.Title = Feed.Title.Text
End If
If Feed.Subtitle IsNot Nothing AndAlso Feed.Subtitle.Text IsNot Nothing Then
    FeedData.Description = Feed.Subtitle.Text
End If
If Feed.Items IsNot Nothing AndAlso Feed.Items.Count > 0 Then
    ' Use the date of the latest post as the last updated date.
    FeedData.PubDate = Feed.Items(0).PublishedDate.DateTime
    For Each Item As SyndicationItem In Feed.Items
       Dim FeedItem As New FeedItem
       If Item.Title IsNot Nothing AndAlso Item.Title.Text IsNot Nothing Then
           FeedItem.Title = Item.Title.Text
       End If
       FeedItem.PubDate = Item.PublishedDate.DateTime
       If Item.Authors IsNot Nothing AndAlso Item.Authors.Count > 0 Then
           FeedItem.Author = Item.Authors(0).Name.ToString()
       End If
       ' Handle the differences between RSS and Atom feeds.
       If Feed.SourceFormat = SyndicationFormat.Atom10 Then
           If Item.Content IsNot Nothing AndAlso Item.Content.Text IsNot Nothing Then
               FeedItem.Content = Item.Content.Text
           End If
           If Item.Id IsNot Nothing Then
               FeedItem.Link = New Uri("https://windowsteamblog.com" + Item.Id)
           End If
       ElseIf Feed.SourceFormat = SyndicationFormat.Rss20 Then
           If Item.Summary IsNot Nothing AndAlso Item.Summary.Text IsNot Nothing Then
               FeedItem.Content = Item.Summary.Text
           End If
           If Item.Links IsNot Nothing AndAlso Item.Links.Count > 0 Then
               FeedItem.Link = Item.Links(0).Uri
           End If
       End If
       FeedData.Items.Add(FeedItem)
    Next
End If
Return FeedData

When the code gets to the return statement, it's not actually returning in the sense that a synchronous method returns. Remember that the method returned to the caller immediately after the "await" statement. It returned a Task<FeedData> to represent the eventual result of the method. Here, you finally get the result. The line return feedData;, gives the FeedData object that is the result of the method to the Task<FeedData> that is waiting for it.

The Task was awaited at this line in the GetFeedsAsync method.

this.Feeds.Add(await feed1);
Me.Feeds.Add(Await feed1)

When the Task gets the FeedData result that it's waiting for, the code execution proceeds and the FeedData is added to the FeedDataSource.Feeds collection.

Using the data in the app

To use the data in the app, you create an instance of the data source as a resource in App.xaml. You name the instance feedDataSource.

To add a resource to an app

  1. Double-click App.xaml in Solution Explorer. The file opens in the XAML editor.
  2. Add the resource declaration, <local:FeedDataSource x:Key="feedDataSource"/>, to the root ResourceDictionary, after the MergedDictionaries collection.

Here's the complete XAML for the Application.Resources section after you add the new resource.

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>

                <!-- 
                    Styles that define common aspects of the platform look and feel
                    Required by Visual Studio project and item templates
                 -->
                <ResourceDictionary Source="Common/StandardStyles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            
            <local:FeedDataSource x:Key="feedDataSource"/>
        </ResourceDictionary>
    </Application.Resources>

To retrieve the feeds, you add code to the OnLaunched method override in the App.xaml.cs/vb file. This method is executed each time a user launches the app.

Here, you add the async keyword to the method declaration because you use the await keyword inside the method. Because the app requires an active internet connection to download feeds, you use the NetworkInformation class to check for a connection. If the app is connected, you download the feeds; otherwise, you notify the user. You add code to get the app's instance of FeedDataSource and check to see if it already contains feeds. If it doesn't, you call the FeedDataSource.GetFeedsAsync method to retrieve the feeds.

To retrieve the feeds

  1. Double-click App.xaml.cs/vb in Solution Explorer. The file opens in the code editor.

  2. Add the async keyword to the OnLaunched method signature.

    protected async override void OnLaunched(LaunchActivatedEventArgs args)
    
    Protected Overrides Async Sub OnLaunched(args As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs)
    
  3. Add this code to the OnLaunched method, immediately after the instantiation of the new Frame, rootFrame = New Frame().

                    // Add this code after "rootFrame = new Frame();"
                    var connectionProfile = Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile();
                    if (connectionProfile != null)
                    {
                        FeedDataSource feedDataSource = (FeedDataSource)App.Current.Resources["feedDataSource"];
                        if (feedDataSource != null)
                        {
                            if (feedDataSource.Feeds.Count == 0)
                            {
                                await feedDataSource.GetFeedsAsync();
                            }
                        }
                    }
                    else
                    {
                        var messageDialog = new Windows.UI.Popups.MessageDialog("An internet connection is needed to download feeds. Please check your connection and restart the app.");
                        var result = messageDialog.ShowAsync();
                    }
    
                ' Add this code after "rootFrame = new Frame()"
                Dim connectionProfile = Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile()
                If connectionProfile IsNot Nothing Then
                    Dim feedDataSource = DirectCast(App.Current.Resources("feedDataSource"), FeedDataSource)
                    If feedDataSource IsNot Nothing Then
                        If feedDataSource.Feeds.Count = 0 Then
                            Await feedDataSource.GetFeedsAsync()
                        End If
                    End If
                Else
                    Dim messageDialog = New Windows.UI.Popups.MessageDialog("An internet connection is needed to download feeds. Please check your connection and restart the app.")
                    Dim result = messageDialog.ShowAsync()
                End If
    

Here's the full code for the OnLaunched method after you add the code.

        protected async override void OnLaunched(LaunchActivatedEventArgs args)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                // Add this code after "rootFrame = new Frame();"
                var connectionProfile = Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile();
                if (connectionProfile != null)
                {
                    FeedDataSource feedDataSource = (FeedDataSource)App.Current.Resources["feedDataSource"];
                    if (feedDataSource != null)
                    {
                        if (feedDataSource.Feeds.Count == 0)
                        {
                            await feedDataSource.GetFeedsAsync();
                        }
                    }
                }
                else
                {
                    var messageDialog = new Windows.UI.Popups.MessageDialog("An internet connection is needed to download feeds. Please check your connection and restart the app.");
                    var result = messageDialog.ShowAsync();
                }

                if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: Load state from previously suspended application
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // 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(typeof(ItemsPage), args.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }
            // Ensure the current window is active
            Window.Current.Activate();
        }
    Protected Overrides Async Sub OnLaunched(args As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs)
        Dim rootFrame As Frame = Window.Current.Content

        ' Do not repeat app initialization when the Window already has content,
        ' just ensure that the window is active

        If rootFrame Is Nothing Then
            ' Create a Frame to act as the navigation context and navigate to the first page
            rootFrame = New Frame()

            ' Add this code after "rootFrame = new Frame()"
            Dim connectionProfile = Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile()
            If connectionProfile IsNot Nothing Then
                Dim feedDataSource = DirectCast(App.Current.Resources("feedDataSource"), FeedDataSource)
                If feedDataSource IsNot Nothing Then
                    If feedDataSource.Feeds.Count = 0 Then
                        Await feedDataSource.GetFeedsAsync()
                    End If
                End If
            Else
                Dim messageDialog = New Windows.UI.Popups.MessageDialog("An internet connection is needed to download feeds. Please check your connection and restart the app.")
                Dim result = messageDialog.ShowAsync()
            End If

            If args.PreviousExecutionState = ApplicationExecutionState.Terminated Then
                ' TODO: Load state from previously suspended application
            End If
            ' Place the frame in the current Window
            Window.Current.Content = rootFrame
        End If
        If rootFrame.Content Is Nothing Then
            ' 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 Not rootFrame.Navigate(GetType(ItemsPage), args.Arguments) Then
                Throw New Exception("Failed to create initial page")
            End If
        End If

        ' Ensure the current window is active
        Window.Current.Activate()
    End Sub

You can build and run the app in debugging mode to make sure it builds and retrieves the feeds without error. If there are no errors, the app runs, but only shows a black screen. If there are errors, Visual Studio shows info about the error. To learn more about running apps in Visual Studio, see Running Windows Store apps from Visual Studio.

To run the app in debugging mode

  1. To run the app, do one of these:
    • Click Debug > Start debugging.
    • Press F5.
  2. To stop debugging:
    1. Press Alt+Tab to return to Visual Studio.
    2. In Visual Studio, do one of these.
      • Click Debug > Stop debugging.
      • Press Shift+F5.

Now you create the UI to show the data.

Adding pages and navigation

To show the Windows team blogs, you must add pages to the app and handle navigation between these pages. First, you want a page that lists all of the Windows team blogs. When a reader picks a blog from this page, you navigate to a new page and load the list of posts for that blog. You also add a detail page so a user can read individual blog posts without the list view taking up space.

Page templates

Fortunately, you don't have to create each page from a blank template. Visual Studio Express 2012 for Windows 8 ships with a collection of page templates that are useful for a variety of situations. Here are the available page templates.

Page Type Description
Group Detail Page Displays details for a single group and previews for each item in the group.
Grouped Items Page Displays grouped collections.
Item Detail Page Displays one item in detail, and enables navigation to adjacent items.
Items Page Displays a collection of items.
Split Page Displays a list of items and details for the selected item.
Basic Page An empty page that can adapt to different orientations and views, and has a title and back button.
Blank Page A blank page for a Windows Store app.

 

You use the Items Page template to show the list of Windows team blogs. You use the Split Page template to show the posts for each blog, and the text of the selected post. You use the Basic Page template for the detail page.

To add pages to the app

  1. Select Project > Add New Item. The Add New Item dialog opens.

    Here's the Add New Item dialog.

  2. Under Visual C# or Visual Basic in the left pane, pick the Windows Store template type.

  3. In the center pane, select the type of page to add to your project. Select the Items Page template.

  4. Enter a name for the page. Enter "ItemsPage.xaml".

  5. Click Add.

    The first time you add a new page to the Blank App template (other than a Blank Page), Visual Studio shows a dialog with a message that you need to add files that are missing from your project. Click Yes to add these files. Files for several utility classes are added to your project in the Common folder.

    The XAML and code behind files for your page are added to the project.

  6. Click Build > Build solution to build the app. The new page will show an error in the designer until you build the helper classes it depends on.

  7. Repeat steps 1-5 to add a split page.

    • In step 3, select the Split Page template.
    • In step 4, name the page, "SplitPage.xaml".
  8. Repeat steps 1-5 to add a basic page.

    • In step 3, select the Basic Page template.
    • In step 4, name the page, "DetailPage.xaml".

When you add the page templates to your project and look at the XAML and code behind, it's apparent that these page templates do a lot of work for you. In fact, it's easy to get lost, so let's take a closer look at the page templates and see what's in them. All the XAML page templates for Windows Store apps have the same format.

The XAML for the page templates has 3 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.
App Content The controls and content that make up the app UI are defined within the root layout panel.
Visual State Manager Animations and transitions that adapt the app to different layouts and orientations are defined in the VisualStateManager section. We talk more about this in the Adapting to different layouts section.

 

The template pages you use are all derived from the LayoutAwarePage class and can do much more by default than the Blank Page that's used for the MainPage.xaml in a Blank app. LayoutAwarePage is an implementation of Page that enables important functionality for Windows Store app development:

  • The mapping of application view state to visual state lets the page adapt to different resolutions, orientations, and views.
  • GoBack and GoHome event handlers support basic navigation.
  • A default view model gives you a simple bindable data source.
  • SaveState and LoadState methods work with the SuspensionManager class to manage app session state.

The page templates also use styles and templates found in StandardStyles.xaml that apply the design guidelines for Windows Store apps. You'll use some of these styles as a starting point and modify copies of them to customize the look of the app.

The XAML UI 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 forward and back through pages you've visited. You can pass data between pages as you navigate.

In the Visual Studio project templates, a Frame named rootFrame is set as the content of the app window. This Frame hosts all the Pages in the app. Let's look at the relevant code in App.xaml.cs/vb.

        protected async override void OnLaunched(LaunchActivatedEventArgs args)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

               ...

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // 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(typeof(MainPage), args.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }
            // Ensure the current window is active
            Window.Current.Activate();
        }
    Protected Overrides Async Sub OnLaunched(args As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs)
        Dim rootFrame As Frame = Window.Current.Content

        ' Do not repeat app initialization when the Window already has content,
        ' just ensure that the window is active

        If rootFrame Is Nothing Then
            ' Create a Frame to act as the navigation context and navigate to the first page
            rootFrame = New Frame()

           ...

            ' Place the frame in the current Window
            Window.Current.Content = rootFrame
        End If
        If rootFrame.Content Is Nothing Then
            ' 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 Not rootFrame.Navigate(GetType(MainPage), args.Arguments) Then
                Throw New Exception("Failed to create initial page")
            End If
        End If

        ' Ensure the current window is active
        Window.Current.Activate()
    End Sub

This code gets an existing Frame if there is one; otherwise, it creates a new one and sets it as the window's content. It then navigates to MainPage. Because you want the first page in the app to be ItemsPage, you change the call to the Navigate method and pass in ItemsPage as the Page to navigate to.

To navigate to ItemsPage

  1. Double-click App.xaml.cs/vb in Solution Explorer. The file opens in the code editor.

  2. In the OnLaunched method, update the call to rootFrame.Navigate and pass ItemsPage as the first parameter as shown here.

                if (!rootFrame.Navigate(typeof(ItemsPage), args.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
    
                If Not rootFrame.Navigate(GetType(ItemsPage), args.Arguments) Then
                    Throw New Exception("Failed to create initial page")
                End If
    
  3. Press F5 to build and run the app. Now the ItemsPage is loaded instead of the default MainPage.

    Note   The default MainPage.xaml file is no longer needed in this project, and you can safely delete it.

     

In the items page, you don't see any content yet because you haven't connected any data to the UI. You do that now.

Loading ItemsPage

When ItemsPage is loaded, you need to get the app's instance of the data source and hook it up to the UI. For pages that are derived from LayoutAwarePage, you can hook up your data to the DefaultViewModel provided by LayoutAwarePage. You do this in the LoadState method override.

To load data in the items page

  1. Double-click ItemPage.xaml.cs/vb in Solution Explorer to open it.

  2. Add this code to the LoadState method.

                FeedDataSource feedDataSource = (FeedDataSource)App.Current.Resources["feedDataSource"];
    
                if (feedDataSource != null)
                {
                    this.DefaultViewModel["Items"] = feedDataSource.Feeds;
                }
    
            Dim feedDataSource = DirectCast(App.Current.Resources("feedDataSource"), FeedDataSource)
            If feedDataSource IsNot Nothing Then
                Me.DefaultViewModel("Items") = feedDataSource.Feeds
            End If
    
  3. Press F5 to build and run the app.

Now the ItemsPage is loaded and shows a grid of all the blogs. It looks something like this, but the items might be arranged differently depending on your screen resolution.

When the user picks a blog from the collection, you navigate from the items page to the split page . To do this navigation, you want the GridView items to respond to a click like a button, instead of being selected. To make the GridView items clickable, you set the SelectionMode and IsItemClickEnabled properties as shown here. You then add a handler for GridView's ItemClick event.

To navigate to SplitPage

  1. Double-click ItemsPage.xaml in Solution Explorer to open it.
  2. Select the GridView control named itemGridView.
  3. In the Properties panel, expand the Common section, then click the down arrow at the bottom of the section to expand the advanced properties.
  4. Check the box for the IsItemClickEnabled property. This sets the property to true.
  5. Add a handler for the ItemClick event.
    1. In the Properties panel, click the Events button ().

    2. Find the ItemClick event in the event list. In the text box for the event, type "ItemView_ItemClick" for the name of the method that handles the ItemClick event.

    3. Press Enter. The event handler method is created and opened in the code editor so you can add code that's executed when the event occurs.

    4. Add this code to the ItemView_ItemClick event handler in the code behind page.

      Here, you navigate to the split page and pass the title of the selected feed.

                  // Navigate to the split page, configuring the new page
                  // by passing the title of the clicked item as a navigation parameter
      
                  if (e.ClickedItem != null)
                  {
                      string title = ((FeedData)e.ClickedItem).Title;
                      this.Frame.Navigate(typeof(SplitPage), title);
                  }
      
              ' Navigate to the split page, configuring the new page
              ' by passing the title of the clicked item as a navigation parameter
              If e.ClickedItem IsNot Nothing Then
                  Dim title = (DirectCast(e.ClickedItem, FeedData)).Title
                  Me.Frame.Navigate(GetType(SplitPage), title)
              End If
      
  6. In ItemsPage.xaml, select the ListView control named itemListView.
  7. In the Properties Window, click the Properties button ().
  8. Repeat steps 4-5.3 for the itemListView control.

The itemListView list is shown in place of the grid when the app is Snapped. We talk more about this in the section Adapting to different layouts. For now, you just make the same changes to the ListView that you made to the GridView so they have the same behavior.

To navigate between pages, you use the Frame control's Navigate, GoForward, and GoBack methods. To navigate back, the Visual Studio page templates include a Back button. The handler for the BackButton's Click event calls the Frame.GoBack method.

The Navigate(TypeName, Object) method lets you navigate and pass a data object to the new page. You use this method to pass data between your pages. The first parameter, typeof(SplitPage), is the Type of the page that you are navigating to. The second parameter is the data object that you pass to the page you're navigating to. In this case, you pass the title of the clicked item.

You could pass your custom data objects as navigation parameters, but they won't be persisted and will even cause an exception when you save the app's session state using the SuspensionManager class. In order to save the navigation state of the app using SuspensionManager, you must pass only basic, serializable types as the navigation parameter. That's why you pass the title of the item, and have a method in the FeedDataSource class to get the item from the title.

Before you move on to the split page, you need to make one more change to the items page. The default page title is "My Application". You change this to "Windows Team Blogs".

To change the page title

  1. Double-click ItemsPage.xaml in Solution Explorer to open it.
  2. Select the TextBlock named pageTitle.
  3. In the Properties Window, click the Properties button ().
  4. Under Common in the Properties panel, click the property marker for the Text property. The property menu opens. Note   The property marker is the small box symbol to the right of each property value. The Text property marker is green to indicate that it's set to a resource.  
  5. In the property menu, select Edit Resource.... The Edit Resource dialog opens.
  6. In the Edit Resource dialog, change the value from "My Application" to "Windows Team Blogs".
  7. Click OK.

In ItemsPage.xaml, the page title is bound to a static resource with the key AppName. The text in this resource is updated to "Windows Team Blogs", and the XAML now looks like this.

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

Press F5 to build and run the app. Now when you click an item the split page is loaded. But the split page is still blank because you haven't connected it to the data yet.

Loading SplitPage

In the SplitPage.xaml.cs/vb code behind page, you need to do something with the object that was just passed from the items page. For this, you again override the LoadState method. This method is already added in the page template code, so you just need to modify it to hook up your data. The navigationParameter has the data object that was passed from the items page. You cast this back to a string, and then pass it as the parameter to the FeedDataSource.GetFeed method. You add the returned feed data to DefaultViewModel with the key Feed, and add the FeedData.Items property to DefaultViewModel with the key Items.

To load data in the split page

  1. Double-click SplitPage.xaml.cs/vb in Solution Explorer to open it in the code editor.

  2. Add this code to the top of the LoadState method, after the "TODO" comments.

    Tip   Expand the "Page state management" region to see the LoadState method.

     

                // TODO: Assign a bindable group to this.DefaultViewModel["Group"]
                // TODO: Assign a collection of bindable items to this.DefaultViewModel["Items"]
                string feedTitle = (string)navigationParameter;
                FeedData feedData = FeedDataSource.GetFeed(feedTitle);
                if (feedData != null)
                {
                    this.DefaultViewModel["Feed"] = feedData;
                    this.DefaultViewModel["Items"] = feedData.Items;
                }
    
            ' TODO: Assign a bindable group to Me.DefaultViewModel("Group")
            ' TODO: Assign a collection of bindable items to Me.DefaultViewModel("Items")
            Dim feedTitle = DirectCast(navigationParameter, String)
    
            Dim feedData = FeedDataSource.GetFeed(feedTitle)
            If feedData IsNot Nothing Then
                Me.DefaultViewModel("Feed") = feedData
                Me.DefaultViewModel("Items") = feedData.Items
            End If
    

Here's the updated LoadState method.

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            // TODO: Assign a bindable group to this.DefaultViewModel["Group"]
            // TODO: Assign a collection of bindable items to this.DefaultViewModel["Items"]
            string feedTitle = (string)navigationParameter;
            FeedData feedData = FeedDataSource.GetFeed(feedTitle);
            if (feedData != null)
            {
                this.DefaultViewModel["Feed"] = feedData;
                this.DefaultViewModel["Items"] = feedData.Items;
            }

            if (pageState == null)
            {
                // 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 (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
                {
                    this.itemsViewSource.View.MoveCurrentToFirst();
                }
            }
            else
            {
                // Restore the previously saved state associated with this page
                if (pageState.ContainsKey("SelectedItem") && this.itemsViewSource.View != null)
                {
                    // TODO: Invoke this.itemsViewSource.View.MoveCurrentTo() with the selected
                    //       item as specified by the value of pageState["SelectedItem"]
                }
            }
        }
    Protected Overrides Sub LoadState(navigationParameter As Object, pageState As Dictionary(Of String, Object))

        ' TODO: Assign a bindable group to Me.DefaultViewModel("Group")
        ' TODO: Assign a collection of bindable items to Me.DefaultViewModel("Items")
        Dim feedTitle = DirectCast(navigationParameter, String)

        Dim feedData = FeedDataSource.GetFeed(feedTitle)
        If feedData IsNot Nothing Then
            Me.DefaultViewModel("Feed") = feedData
            Me.DefaultViewModel("Items") = feedData.Items
        End If

        If pageState Is Nothing Then

            ' 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 Not Me.UsingLogicalPageNavigation() AndAlso Me.itemsViewSource.View IsNot Nothing Then
                Me.itemsViewSource.View.MoveCurrentToFirst()
            End If
        Else

            ' Restore the previously saved state associated with this page
            If pageState.ContainsKey("SelectedItem") AndAlso Me.itemsViewSource.View IsNot Nothing Then
                ' TODO: Invoke Me.itemsViewSource.View.MoveCurrentTo() with the selected
                '       item as specified by the value of pageState("SelectedItem")
            End If
        End If
    End Sub

Press F5 to build and run the app. Now when you navigate to the split page, the data is displayed and you can select from the list of blog posts. But the page title is still blank. You'll fix that now.

In the Visual Studio page template, the "TODO" comments indicate where you add your data object to DefaultViewModel with the key Group.

            // TODO: Assign a bindable group to this.DefaultViewModel["Group"]
            ...
                this.DefaultViewModel["Feed"] = feedData;
        ' TODO: Assign a bindable group to Me.DefaultViewModel("Group")
        ...
            Me.DefaultViewModel("Feed") = feedData

You can think of a blog feed as a group of blog posts, but it makes the code easier to read if you use the key Feed in place of Group. Because you used the key Feed instead, you need to change the binding in the page title to bind to the Feed property instead of Group. You also make the title span 2 columns so it's not cut off.

To update the page title

  1. Double-click SplitPage.xaml in Solution Explorer to open it.

  2. Select the Grid named titlePanel.

  3. Under Layout in the Properties panel, set the ColumnSpan property to "2" and press Enter.

    Note   At the default width of the Properties panel, the ColumnSpan property is shown as Colum....

     

    The XAML is updated as shown here.

    <!-- Back button and page title -->
    <Grid x:Name="titlePanel" Grid.ColumnSpan="2">
    
  4. Select the TextBlock named pageTitle.

  5. Under Common in the Properties panel, click the property marker for the Text property. The property menu opens.

    Note   The Text property marker is orange to indicate that it's set to a binding.

     

  6. In the property menu, select Create Data Binding.... The Create Data Binding dialog opens.

  7. In the Create Data Binding dialog, change the value in the Path text box to "Feed.Title" and click OK.

    The XAML for the TextBlock is updated like this.

    <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding Feed.Title}" 
               Style="{StaticResource PageHeaderTextStyle}"/>
    
  8. Press F5 to build and run the app. Now when you navigate to the split page, the page title is shown.

Add the WebView

By default, the split page template shows the details for the selected item in a TextBlock. However, the string that contains the post content is not plain text, but a string of HTML. When you show the string in a TextBlock, you see a bunch of HTML tags, which is not what you want. Instead, you use a WebView control to display the HTML. You also change the layout used to show the title and content of the selected blog post.

To add the web view

  1. In SplitPage.xaml, delete the ScrollViewer named itemDetail and all of its contents.

  2. In the XAML editor, add this XAML in place of the itemDetailScrollViewer you deleted.

            <!-- Details for selected item -->
            <ScrollViewer
                x:Name="itemDetail"
                AutomationProperties.AutomationId="ItemDetailScrollViewer"
                Grid.Column="1"
                Grid.Row="1"
                Padding="70,0,120,0"
                DataContext="{Binding SelectedItem, ElementName=itemListView}"
                Style="{StaticResource VerticalScrollViewerStyle}">
    
                <Grid x:Name="itemDetailGrid">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
    
                    <TextBlock x:Name="itemTitle" Text="{Binding Title}" 
                               Style="{StaticResource SubheaderTextStyle}"/>
                    <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" 
                            Grid.Row="1" Margin="0,15,0,20">
                        <Grid>
                            <WebView x:Name="contentView" />
                            <Rectangle x:Name="contentViewRect" />
                        </Grid>    
                    </Border>
                </Grid>
            </ScrollViewer>
    

The WebView control gives you a way to host HTML data within your app. But if you look at its Source property, you see that it takes the Uri of the web page to display. Your HTML data is just a string of HTML. It doesn't have a Uri that you can bind to the Source property. Luckily, there is a NavigateToString method that you can pass your string of HTML to.

Whenever you use a WebView control to view content that might not be available, handle any failures that occur.

Here, you handle the NavigationCompleted event to show the error message in the WebView control if the navigation fails.

To handle navigation errors

  1. Select the WebView named contentView that you added in the previous step.

  2. In the Properties Window, click the Events button ().

  3. Find the NavigationCompleted event in the event list. In the text box for the event, type "ContentView_NavigationCompleted" for the name of the method that handles the NavigationCompleted event.

  4. Press Enter. The event handler method is created and opened in the code editor so you can add code that's executed when the event occurs.

  5. Add this code to the ContentView_NavigationCompleted event handler in the code behind page.

        if (!e.IsSuccess)
        {
            string errorString = "<p>Page could not be loaded.</p><p>Error is: " + e.WebErrorStatus.ToString() + "</p>";
            this.contentView.NavigateToString(errorString);
        }
    

To populate the WebView, you add your code to the ItemListView_SelectionChanged event handler. In the event handler, you cast the selected item to a FeedItem and get the string of HTML from the Content property. You then pass the string to the NavigateToString method. If no item is selected, you clear the WebView by passing an empty string.

To populate the web view

  1. Double-click SplitPage.xaml.cs/vb in Solution Explorer. The file opens in the code editor.

  2. Add this code to the end of the ItemListView_SelectionChanged method to populate the WebView with the content of the selected blog post.

    Note   Expand the "Logical page navigation" region to see the ItemListView_SelectionChanged method.

     

                // Add this code to populate the web view
                //  with the content of the selected blog post.
                Selector list = sender as Selector;
                FeedItem selectedItem = list.SelectedItem as FeedItem;
                if (selectedItem != null)
                {
                    this.contentView.NavigateToString(selectedItem.Content);
                }
                else
                {
                    this.contentView.NavigateToString("");
                }      
    
            ' Add this code to populate the web view
            ' with the content of the selected blog post.
            Dim list = DirectCast(sender, Selector)
            Dim selectedItem = DirectCast(list.SelectedItem, FeedItem)
            If selectedItem IsNot Nothing Then
                Me.contentView.NavigateToString(selectedItem.Content)
            Else
                Me.contentView.NavigateToString("")
            End If
    
  3. Press F5 to build and run the app.

The split page is looking better now, and shows the selected blog post in a more readable format.

Load DetailPage

Now you add content to the detail page. In DetailPage.xaml, you need to bind the title TextBlock to the blog post title and add a WebView control to show the blog page.

To update the detail page

  1. Double-click DetailPage.xaml in Solution Explorer to open it.

  2. Bind the page title TextBlock to the Title property of the selected blog post.

    1. Select the TextBlock named pageTitle.
    2. In the Properties Window, click the Properties button ().
    3. Under Common in the Properties panel, click the property marker for the Text property. The property menu opens.
    4. In the property menu, select Create Data Binding.... The Create Data Binding dialog opens.
    5. In the Create Data Binding dialog, select Data context in the Binding type drop-down list.
    6. Enter "Title" in the Path text box then click OK. Note  The message in the Create Data Binding dialog says the data context is not set. That's okay because you set the data context in code when the you run the app.  
  3. In the XAML editor, add this XAML after the Grid that has the page title, and before the VisualStateManager.VisualStateGroups section.

            <Border x:Name="contentViewBorder" BorderBrush="Gray" BorderThickness="2" 
                    Grid.Row="1" Margin="120,15,20,20">
                <WebView x:Name="contentView" 
                         NavigationCompleted="contentView_NavigationCompleted"/>
            </Border>
    
  4. Double-click DetailPage.xaml.cs/vb in Solution Explorer to open it.

  5. Add this code to the LoadState method override.

    This code navigates the WebView to the Uniform Resource Identifier (URI) of the blog post and sets the DataContext of the page.

                // Add this code to navigate the web view to the selected blog post.
                string itemTitle = (string)navigationParameter;
                FeedItem feedItem = FeedDataSource.GetItem(itemTitle);
                if (feedItem != null)
                {
                    this.contentView.Navigate(feedItem.Link);
                    this.DataContext = feedItem;
                }
    
            ' Add this code to navigate the web view to the selected blog post.
            Dim itemTitle = DirectCast(navigationParameter, String)
            Dim feedItem = FeedDataSource.GetItem(itemTitle)
            If feedItem IsNot Nothing Then
                Me.contentView.Navigate(feedItem.Link)
                Me.DataContext = feedItem
            End If
    
  6. Add this code to the DetailPage class to handle the WebView.NavigationCompleted event.

            private void contentView_NavigationCompleted(object sender, WebViewNavigationCompletedEventArgs e)
            {
                if (!e.IsSuccess)
                {
                    string errorString = "<p>Page could not be loaded.</p><p>Error is: " + e.WebErrorStatus.ToString() + "</p>";
                    this.contentView.NavigateToString(errorString);
                }
            }
    
        Private Sub ContentView_NavigationFailed(sender As Object, e As WebViewNavigationFailedEventArgs)
            Dim errorString = "<p>Page could not be loaded.</p><p>Error is: " & e.WebErrorStatus.ToString() & "</p>"
            Me.contentView.NavigateToString(errorString)
        End Sub
    
  7. Press F5 to build and run the app.

    All the data is hooked up now, but there's no way to navigate to the details page you created. You add navigation to the detail page next.

Adding an app bar

Most of the navigation in the blog reader app happens when the user picks an item in the UI. But on the split page, you must provide a way for the user to go to the detail view of the blog post. You could put a button somewhere on the page, but that would distract from the core app experience, which is reading. Instead, you put the button in an app bar that's hidden until the user needs it.

An app bar is a piece of UI that is hidden by default, and is shown or dismissed when the user swipes from the edge of the screen or interacts with the app. It presents 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. You can control how and when the app bar is shown and dismissed by setting the IsSticky and IsOpen properties. You can also respond to the app bar being opened or dismissed by handling the Opened and Closed events.

To add an app bar in XAML, you assign an AppBar control to aPage's TopAppBar or BottomAppBar property. You'll add a top app bar with a button to navigate to the detail page. The StandardStyles.xaml file contains a variety of app bar button styles for common scenarios. You use these styles as a guide to create a style for your button. You put your style in the Page.Resources section of SplitPage.xaml, and add the Page.TopAppBar XAML just after the resources section.

To add an app bar

  1. Double-click SplitPage.xaml in Solution Explorer to open it.

  2. In the XAML editor, add this XAML in the Page.Resources section near the top of the page.

    This adds the Style resource for the app bar button. We talk more about styles later in the section Creating a consistent look with styles.

        <Style x:Key="WebViewAppBarButtonStyle" TargetType="Button" 
               BasedOn="{StaticResource AppBarButtonStyle}">
            <Setter Property="AutomationProperties.AutomationId" Value="WebViewAppBarButton"/>
            <Setter Property="AutomationProperties.Name" Value="View Web Page"/>
            <Setter Property="Content" Value="&#xE12B;"/>
        </Style>
    
  3. Add this XAML after the Page.Resources section of the page.

    This adds an AppBar control to the TopAppBar property of the page.

    <Page.TopAppBar>
       <AppBar Padding="10,0,10,0">
            <Grid>
    
                <Button HorizontalAlignment="Right" 
                        Style="{StaticResource WebViewAppBarButtonStyle}"/>
            </Grid>
        </AppBar>
    </Page.TopAppBar>
    
  4. Select the Button in the app bar you just added.

  5. In the Properties Window, click the Events button ().

  6. Find the Click event at the top of the event list. In the text box for the event, enter "ViewDetail_Click" for the name of the method that handles the Click event.

  7. Press Enter. The event handler method is created and opened in the code editor so you can add code that's executed when the event occurs.

  8. Add this code to the event handler method to handle navigation to the detail page.

                FeedItem selectedItem = this.itemListView.SelectedItem as FeedItem;
                if (selectedItem != null && this.Frame != null)
                {
                    string itemTitle = selectedItem.Title;
                    this.Frame.Navigate(typeof(DetailPage), itemTitle);
                }
    
            Dim selectedItem = DirectCast(Me.itemListView.SelectedItem, FeedItem)
            If selectedItem IsNot Nothing AndAlso Me.Frame IsNot Nothing Then
                Dim itemTitle = selectedItem.Title
                Me.Frame.Navigate(GetType(DetailPage), itemTitle)
            End If
    
  9. Press F5 to build and run the app. Click a blog to navigate to the split page, and select a blog post to read. Right-click the page header to show the app bar, and click the Show Web View button to navigate to the detail page.

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 the blog reader app, you use animations to adapt your UI to different layouts and orientations. You take a closer look at this in the next section, but first, you need to understand how animations work.

To use an animation, you put it inside of a Storyboard. When the Storyboard runs, the property changes as specified by the animation. A Storyboard can have one or many animations in it. Each animation specifies a target object, a property to change on that object, and a new value for the property.

For example, in the blog reader app, you have a ListView named itemListView. Here's an animation that changes the Visibility property of itemListView to Visible when the Storyboard runs.

<Storyboard>
    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" 
                                   Storyboard.TargetProperty="Visibility" >
        <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
    </ObjectAnimationUsingKeyFrames>
</Storyboard>

Adding theme animations

Run the app again, and notice how UI elements slide in smoothly when a page loads. Except for the WebView control you added. It just shows up. Windows 8 uses animations and transitions to enhance the user experience throughout the UI. You want to have the same experience in your app to match the personality of Windows 8. Fortunately, you can use built in theme animations and theme transitions in your app to match the ones used in Windows 8. You find these in the Windows.UI.Xaml.Media.Animation namespace.

A theme animation is a pre-configured animation that you can put inside 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, you 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 your web content. This animates the Border and everything in it.

To add a theme resource

  1. Double-click DetailPage.xaml in Solution Explorer to open it.

  2. In the XAML editor, add this XAML to the Page.Resources section.

        <Storyboard x:Name="PopInStoryboard">
            <PopInThemeAnimation  Storyboard.TargetName="contentViewBorder" 
                                  FromHorizontalOffset="400"/>
        </Storyboard>
    

In the code behind page, you start the Storyboard in the LoadState method so that the pop-in animation is applied to the Border when the user navigates to the detail page.

To start an animation storyboard

  1. Double-click DetailPage.xaml.cs/vb in Solution Explorer to open it.

  2. Add this code at the beginning of the LoadState method.

                // Run the PopInThemeAnimation 
                Windows.UI.Xaml.Media.Animation.Storyboard sb =
                    this.FindName("PopInStoryboard") as Windows.UI.Xaml.Media.Animation.Storyboard;
                if (sb != null) sb.Begin();
    
            ' Run the PopInThemeAnimation 
            Dim sb As Windows.UI.Xaml.Media.Animation.Storyboard = Me.FindName("PopInStoryboard")
            If sb IsNot Nothing Then
                sb.Begin()
            End If
    
  3. Press F5 to build and run the app. Navigate to the detail page. Now the WebView control slides in with the other UI elements when the split page is loaded.

Here's the updated code for the LoadState method.

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            // Run the PopInThemeAnimation 
            Windows.UI.Xaml.Media.Animation.Storyboard sb =
                this.FindName("PopInStoryboard") as Windows.UI.Xaml.Media.Animation.Storyboard;
            if (sb != null) sb.Begin();

            // Add this code to navigate the web view to the selected blog post.
            string itemTitle = (string)navigationParameter;
            FeedItem feedItem = FeedDataSource.GetItem(itemTitle);
            if (feedItem != null)
            {
                this.contentView.Navigate(feedItem.Link);
                this.DataContext = feedItem;
            }
        }
    Protected Overrides Sub LoadState(navigationParameter As Object, pageState As Dictionary(Of String, Object))
        ' Run the PopInThemeAnimation 
        Dim sb As Windows.UI.Xaml.Media.Animation.Storyboard = Me.FindName("PopInStoryboard")
        If sb IsNot Nothing Then
            sb.Begin()
        End If

        ' Add this code to navigate the web view to the selected blog post.
        Dim itemTitle = DirectCast(navigationParameter, String)
        Dim feedItem = FeedDataSource.GetItem(itemTitle)
        If feedItem IsNot Nothing Then
            Me.contentView.Navigate(feedItem.Link)
            Me.DataContext = feedItem
        End If
    End Sub

You can also follow these steps and use the same code to add this animation to the contentViewBorder element in SplitPage.

For more info, and a full list of theme animations and theme transitions, see Quickstart: Animating your UI using library animations.

Creating a consistent look with styles

You want to make the blog reader app look and feel like the Windows team blogs website. You want your users to have a seamless experience when they move between the website and the app. The default dark theme for the project doesn't match very well with the Windows team blogs website. This is most clear on the detail page, where you load the actual blog page into a WebView, as shown here:

To give the app a consistent appearance that you can update as needed, you use brushes and styles. A Brush lets you define a look in one place and use it wherever you need it. A Style lets you set values for a control's properties and reuse those settings across your app.

Before we get into the details, let's look at how you use a brush to set the background color of the pages in your app. Each page in your app has a root Grid with a Background property set to define the page's background color. You could set each page's background individually, like this.

<Grid Background="Blue">

A better way is to define a Brush as a resource and use it to define the background color of all of your pages. You define objects and values as resources to make them reusable. To use an object or value as a resource, you must set its x:Key attribute. You use this key to refer to the resource from your XAML. Here, the background is set to a resource with the key ApplicationPageBackgroundThemeBrush, which is a system defined SolidColorBrush.

<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, like StandardStyles.xaml. Where the resource is defined determines the scope in which it can be used. Visual Studio creates the StandardStyles.xaml file as part of the project template and puts it in the Common folder. It's a resource dictionary that contains the values, styles, and data templates used in the Visual Studio page templates. A resource dictionary XAML file can be shared across apps, and more than one resource dictionary can be merged in a single app.

In the blog reader app, you define resources in App.xaml to make them available across the entire app. And you 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.

Now let's look at an example of using Styles in the app. The look of the template pages is defined by a style with the key LayoutRootStyle. The Style definition is in the StandardStyles.xaml file.

<Grid Style="{StaticResource LayoutRootStyle}">
    <!-- Page layout roots typically use entrance animations  
         and a theme-appropriate background color -->

    <Style x:Key="LayoutRootStyle" TargetType="Panel">
        <Setter Property="Background" 
                Value="{StaticResource ApplicationPageBackgroundThemeBrush}"/>
        <Setter Property="ChildrenTransitions">
            <Setter.Value>
                <TransitionCollection>
                    <EntranceThemeTransition/>
                </TransitionCollection>
            </Setter.Value>
        </Setter>
    </Style>

In the Style definition, you need a TargetType attribute and a collection of one or more Setters. You 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 a Value. These property settings indicate what control property the setting applies to, and the value to set for that property.

To change the Background of your pages, you need to replace the ApplicationPageBackgroundThemeBrush with your own custom brush. For your custom brush, you use the Color #FF0A2562, which is a nice blue that fits with the colors on the https://windowsteamblog.com site. To replace the system theme brush, you create a new Style that is based on LayoutRootStyle, and change the Background property there. Here, you define a new style for the layout root.

To change the page background

  1. Double-click App.xaml in Solution Explorer to open it.

  2. In the XAML editor, add these brush and style definitions in the ResourceDictionary with your other app specific resources, after the feedDataSource resource.

                <SolidColorBrush x:Key="WindowsBlogBackgroundBrush" 
                                        Color="#FF0A2562"/>
                <Style x:Key="WindowsBlogLayoutRootStyle" 
                               TargetType="Panel" 
                               BasedOn="{StaticResource LayoutRootStyle}">
                    <Setter Property="Background" 
                                    Value="{StaticResource WindowsBlogBackgroundBrush}"/>
                </Style>
    

Here's the full XAML for the ResourceDictionary section of App.xaml.

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>

                <!-- 
                    Styles that define common aspects of the platform look and feel
                    Required by Visual Studio project and item templates
                 -->
                <ResourceDictionary Source="Common/StandardStyles.xaml"/>
            </ResourceDictionary.MergedDictionaries>

            <local:FeedDataSource x:Key="feedDataSource"/>

            <SolidColorBrush x:Key="WindowsBlogBackgroundBrush" 
                                    Color="#FF0A2562"/>
            <Style x:Key="WindowsBlogLayoutRootStyle" 
                           TargetType="Panel" 
                           BasedOn="{StaticResource LayoutRootStyle}">
                <Setter Property="Background" 
                                Value="{StaticResource WindowsBlogBackgroundBrush}"/>
            </Style>
        </ResourceDictionary>
    </Application.Resources>

Important   Because you based a style on a system style in StandardStyles.xaml, the ResourceDictionary that includes StandardStyles.xaml has to be declared in the XAML before your style that's based on it. If it's not, the XAML parser can't find the LayoutRootStyle that your style is based on.

 

The line BasedOn="{StaticResource LayoutRootStyle}" tells your new Style to inherit from LayoutRootStyle any properties that you don't explicitly set. Your new Style is just like the default style, but with a blue background, and you can use it for all of your pages.

To change the background of each page

  1. Double-click ItemsPage.xaml in Solution Explorer to open it.

  2. Select the page's root layout Grid.

  3. Under Miscellaneous in the Properties panel, find the Style property.

  4. Click the property marker next to the Style property to open the menu.

  5. In the menu, select Local Resource > WindowsBlogLayoutRootStyle.

  6. Repeat steps 2-5 for SplitPage.xaml and DetailPage.xaml.

    The XAML for the root Grid in each page now looks like this.

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

To give the app the look and feel of the Windows team blogs website, you also use custom data templates in addition to Brushes and Styles. Let's start by using templates to format the list items in the split page.

Formatting data with a data template

In SplitPage.xaml, the look of the items in itemListView is defined in the Standard130ItemTemplate, which is in StandardStyles.xaml. The item template is assigned in this line of XAML: ItemTemplate="{StaticResource Standard130ItemTemplate}". To see the effect of this template, you can remove it and see what the list looks like without it.

You have the ListView bound to the Items property of the FeedData object, so the correct data is there. But when you run the app like this, the ListView doesn't know what to show, so it just calls ToString on the object it's bound to. That gives you a list of "WindowsBlogReader.FeedItem" strings like you saw previously. Clearly, this is not what you want to show.

You can make it a little better by setting the DisplayMemberPath property on the ListView. This tells the list view to call ToString on the specified property of the bound object, instead of on the object itself. If you set DisplayMemberPath=Title, the ListView shows a list of blog post titles.

Here's the ListView XAML with the DisplayMemberPath set to the Title property.

<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"
    DisplayMemberPath="Title"/>

When you run the app, the list items look like this. This is closer to what you want to show.

But what you really want is to show the title, author, and published date for each post in the list. To show more than one property of the data item, you define a template that tells the ListView exactly how you want the data displayed. The controls for viewing collections of data items are derived from the ItemsControl class. These controls have an ItemTemplate property that you can assign a DataTemplate to. The DataTemplate defines how your data looks.

Important   You can't use the DisplayMemberPath and an ItemTemplate at the same time. When you set the ItemTemplate property, be sure to remove the DisplayMemberPath setting.

 

The default item template is close to what you want to show. Let’s use it as the starting point for your template.

To create a new ItemTemplate

  1. In Solution Explorer, open SplitPage.xaml.

  2. Select the ListView named "itemListView".

  3. Under Miscellaneous in the Properties panel, click the property marker for the ItemTemplate property. The property menu opens.

    Note   The property marker is the small box symbol to the right of each property value. The ItemTemplate property marker is green to indicate that it's set to a resource.

     

  4. In the property menu, select Convert to New Resource.... The Create DataTemplate Resource dialog opens.

  5. In the Create DataTemplate Resource dialog, enter "DefaultListItemTemplate" in the Name (Key) text box.

    Make sure the Define in setting is set to This document > "LayoutAwarePage:pageRoot".

    Note   The XAML page Design view must be showing in Visual Studio. If only the XAML editor is showing, some options are disabled.

     

  6. Click OK.

    A new DataTemplate is created in the Resources section of SplitPage.xaml. This is a copy of the Standard130ItemTemplate resource that you can modify. The ItemTemplate property of itemListView is also updated to use DefaultListItemTemplate.

Defining the bit of UI inside the DataTemplate is just like defining any other UI. This template has a Grid with 2 columns. The first column has a square border with a placeholder image. The second column has a StackPanel with 3 TextBlocks stacked on top of each other. By default, the Text properties of the TextBlocks are bound to properties of the sample data source used in the Visual Studio templates. The default DataContext for these bindings is the object displayed in the ListView, which is a FeedItem. So you can update the bindings to show the Title, Author, and PubDate properties of the FeedItem.

Here’s the XAML for the data template with the updated bindings.

        <DataTemplate x:Key="DefaultListItemTemplate">
            <Grid Height="110" Margin="6">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" 
                        Width="110" Height="110">
                    <Image Source="{Binding Image}" Stretch="UniformToFill" 
                           AutomationProperties.Name="{Binding Title}"/>
                </Border>
                <StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="10,0,0,0">
                    <TextBlock Text="{Binding Title}" 
                               Style="{StaticResource TitleTextStyle}" TextWrapping="NoWrap"/>
                    <TextBlock Text="{Binding Author}" 
                                                                                                                            Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
                    <TextBlock Text="{Binding PubDate}" 
                                                                                                                            Style="{StaticResource BodyTextStyle}" MaxHeight="60"/>
                </StackPanel>
            </Grid>
        </DataTemplate>

Here's what the list looks like when you run the app.

Now the list shows the data you want, but it doesn't reflect the branding of the Windows team blog website, as you see here.

To make the list view look more like the web site, you use a ContentControl and ControlTemplate to show the date in a green block, and update the look of the title TextBlock to show the titles in orange. You also use a value converter to format the date the way you want it.

Formatting data with a value converter

In ItemListView's DataTemplate, you bind the PubDate property, which is a DateTime, to a TextBlock.Text property. The binding engine automatically converts PubDate from a DateTime to a string. But the automatic conversion shows both the date and the time, and you want to show only the date. To fix this, you can create your own value converter that converts a DateTime to a string, and in it you can format the string however you want to.

To create a value converter, you create a class that implements the IValueConverter interface and then implement the Convert and ConvertBack methods. Converters can change data from one type to another, translate data based on cultural info, or modify other aspects of the presentation.

The Convert and ConvertBack methods also let you pass in a parameter so that you can use the same instance of the converter with different options. In this example, you have a formatting converter that produces different formats of the date based on the input parameter. You can use the ConverterParameter of the Binding class to pass a parameter as an argument into the Convert and ConvertBack methods. you use this feature to format the date to show it in the block format used on the blog web site.

Here, you create a date converter that converts the date value passed in and formats it so that it shows only the day, the month, and the year.

To add a value converter

  1. Select Project > Add Class. The New Item dialog box opens.

  2. Enter DateConverter as the name for the class file.

  3. Click Add. The DateConverter.cs/vb class file is created.

  4. Copy this code into the DateConverter.cs/vb file. Replace any code that's in the file.

    using System;
    using Windows.Globalization.DateTimeFormatting;
    
    namespace WindowsBlogReader
    {
        public class DateConverter : Windows.UI.Xaml.Data.IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, string culture)
            {
                if (value == null)
                    throw new ArgumentNullException("value", "Value cannot be null.");
    
                if (!typeof(DateTime).Equals(value.GetType()))
                    throw new ArgumentException("Value must be of type DateTime.", "value");
    
                DateTime dt = (DateTime)value;
    
                if (parameter == null)
                {
                    // Date "7/27/2011 9:30:59 AM" returns "7/27/2011"
                    return DateTimeFormatter.ShortDate.Format(dt);
                }           
                else if ((string)parameter == "day")
                {
                    // Date "7/27/2011 9:30:59 AM" returns "27"
                    DateTimeFormatter dateFormatter = new DateTimeFormatter("{day.integer(2)}");
                    return dateFormatter.Format(dt);
                }
                else if ((string)parameter == "month")
                {
                    // Date "7/27/2011 9:30:59 AM" returns "JUL"
                    DateTimeFormatter dateFormatter = new DateTimeFormatter("{month.abbreviated(3)}");
                    return dateFormatter.Format(dt).ToUpper();
                }
                else if ((string)parameter == "year")
                {
                    // Date "7/27/2011 9:30:59 AM" returns "2011"
                    DateTimeFormatter dateFormatter = new DateTimeFormatter("{year.full}");
                    return dateFormatter.Format(dt);
                }
                else
                {
                    // Requested format is unknown. Return in the original format.
                    return dt.ToString();
                }
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, string culture)
            {
                string strValue = value as string;
                DateTime resultDateTime;
                if (DateTime.TryParse(strValue, out resultDateTime))
                {
                    return resultDateTime;
                }
                return Windows.UI.Xaml.DependencyProperty.UnsetValue;
            }
        }
    }
    
    Imports Windows.Globalization.DateTimeFormatting
    
    Public Class DateConverter
        Implements IValueConverter
    
        Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As String) _
            As Object Implements IValueConverter.Convert
            If value Is Nothing Then
                Throw New ArgumentNullException("value", "Value cannot be null.")
            End If
            If Not GetType(DateTime).Equals(value.GetType()) Then
                Throw New ArgumentException("Value must be of type DateTime.", "value")
            End If
    
            Dim dt As DateTime = DirectCast(value, DateTime)
    
            If parameter Is Nothing Then
                ' Date "7/27/2011 9:30:59 AM" returns "7/27/2011"
                Return DateTimeFormatter.ShortDate.Format(dt)
    
            ElseIf DirectCast(parameter, String) = "day" Then
                ' Date "7/27/2011 9:30:59 AM" returns "27"
                Dim dateFormatter As DateTimeFormatter = New DateTimeFormatter("{day.integer(2)}")
                Return dateFormatter.Format(dt)
    
            ElseIf DirectCast(parameter, String) = "month" Then
                ' Date "7/27/2011 9:30:59 AM" returns "JUL"
                Dim dateFormatter As DateTimeFormatter = New DateTimeFormatter("{month.abbreviated(3)}")
                Return dateFormatter.Format(dt).ToUpper()
    
            ElseIf DirectCast(parameter, String) = "year" Then
                ' Date "7/27/2011 9:30:59 AM" returns "2011"
                Dim dateFormatter As DateTimeFormatter = New DateTimeFormatter("{year.full}")
                Return dateFormatter.Format(dt)
    
            Else
                ' Requested format is unknown. Return in the original format.
                Return dt.ToString()
    
            End If
        End Function
    
        Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As String) _
            As Object Implements Windows.UI.Xaml.Data.IValueConverter.ConvertBack
            Dim StrValue As String = DirectCast(value, String)
            Dim Result As Date
            If DateTime.TryParse(StrValue, Result) Then
                Return Result
            End If
            Return DependencyProperty.UnsetValue
        End Function
    End Class
    
  5. Click Build > Build solution to make sure the solution builds without error.

Before you can use the DateConverter class, you must declare an instance of it in your XAML. You declare it as an app resource with the key dateConverter in App.xaml. Declaring it here makes it available for every page in your app to use.

To add the dateConverter app resource

  1. Double-click App.xaml in Solution Explorer to open it.
  2. Add the resource declaration, <local:DateConverter x:Key="dateConverter" />, to the ResourceDictionary after the feedDataSource resource.

Now you can use the DateConverter in the bindings of the date block you create next. With the converter added, the binding engine uses your custom DateConverter to change a DateTime to a string. The string that it returns is formatted the way you want, with only the day, month, and year.

In App.xaml, you add a ControlTemplate that defines a square block that shows the date. You define this in App.xaml so you can use it in both ItemsPage.xaml and SplitPage.xaml.

To add a control template to App.xaml

  1. Double-click App.xaml in Solution Explorer to open it.

  2. Add this XAML in the ResourceDictionary with your other app specific resources, after the WindowsBlogLayoutRootStyle resource.

    <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>
    

Now you can use this control template in SplitPage.xaml to update the look of the list items in the default view.

To use the control template in SplitPage.xaml

  1. Double-click SplitPage.xaml in Solution Explorer to open it.

  2. In the XAML editor, find the DefaultListItemTemplate resource you created previously.

  3. Replace the XAML for the Border element with this XAML.

                    <Border Background="#FF6BBD46" Width="110" Height="110">
                        <ContentControl Template="{StaticResource DateBlockTemplate}" />
                    </Border>
    
  4. Replace the XAML for the 3 TextBlocks in the StackPanel element with this XAML.

                        <TextBlock Text="{Binding Title}" FontSize="26.667" TextWrapping="Wrap"
                                   MaxHeight="72" Foreground="#FFFE5815" />
                        <TextBlock Text="{Binding Author}" FontSize="18.667" />
    

Here’s the updated XAML for the data template.

        <DataTemplate x:Key="DefaultListItemTemplate">
            <Grid Height="110" Margin="6">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Border Background="#FF6BBD46" Width="110" Height="110">
                    <ContentControl Template="{StaticResource DateBlockTemplate}" />
                </Border>
                <StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="10,0,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>

Important   This template is used in the default full screen landscape view. If your screen resolution is less than 1366 pixels wide, the split page uses the FilledOrNarrow view state, and you will not see this template in the list view. To see this template, run the app in the simulator in Visual Studio with a resolution of 1366 x 768 or greater. For more info about testing the app in the simulator, see Running Windows Store apps from Visual Studio.

 

To finish styling the split page, you need to change the background color of the app bar and the color of the selected item in the list view.

To update the app bar background

  1. In SplitPage.xaml, select the AppBar control.

  2. Under Brush in the Properties panel, or in the XAML editor, set the Background property to "#F20A2562".

    <AppBar Padding="10,0,10,0" Background="#F20A2562">
    

By default, the selected item in the list view has a purple background. You override the default list view theme brushes to make the selected item blue. To override built-in theme brushes, you define a brush with the same key in App.xaml and give it a new value.

To update the list view colors

  1. Double-click App.xaml in Solution Explorer to open it.

  2. Add this XAML in the ResourceDictionary after your other app specific resources.

    <SolidColorBrush x:Key="ListViewItemSelectedBackgroundThemeBrush" Color="#FF465985"/>
    <SolidColorBrush x:Key="ListViewItemSelectedPointerOverBackgroundThemeBrush" Color="#FF384A72"/>
    <SolidColorBrush x:Key="ListViewItemSelectedPointerOverBorderThemeBrush" Color="#FF384A72" />
    

To finish styling the app, you apply similar styles to the items page. In ItemsPage.xaml, you add these resources to define the look of the grid items in the default view.

To style ItemsPage.xaml

  1. Double-click ItemsPage.xaml in Solution Explorer to open it.

  2. In the XAML editor, add this XAML in the Page.Resources section after the AppName resource.

            <!-- 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>
    
  3. Select the GridView control named itemGridView.

  4. Under Miscellaneous in the Properties panel, click the property marker for the ItemTemplate property. The property menu opens.

  5. In the menu, select Local Resource > DefaultGridItemTemplate.

    Note   The XAML page Design view must be showing in Visual Studio. If only the XAML editor is showing, local page resources are not listed.

     

    Here's the full updated XAML for itemGridView.

            <!-- Horizontal scrolling grid used in most view states -->
            <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}}"
    
                ItemTemplate="{StaticResource DefaultGridItemTemplate}"
    
                SelectionMode="None"
                IsSwipeEnabled="false" 
                IsItemClickEnabled="True" 
                ItemClick="ItemView_ItemClick"/>
    
  6. Press F5 to build and run the app. The items page now uses the new template.

With your styles applied, the app fits in with the look and feel of the Windows team blogs website:

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

Adapting to different layouts

Note   In this section, you apply concepts discussed in Part3: Navigation, layout, and views. For more info about adapting your UI to different views, and tool support that's available, review Part 3.

 

Usually, an app is designed to be viewed full screen in the landscape orientation. But a Windows Store app 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, Filled, and Snapped layouts.

Tip   To test the app in different orientations and resolutions, you can run it in the simulator. To run your Windows Store app in the simulator, select Simulator from the drop-down list next to the Start Debugging button on the Standard toolbar. For more info about the simulator, see Running Windows Store apps from Visual Studio.

 

The Visual Studio templates include code that handles changes to the view state. This code is in the LayoutAwarePage.cs/vb file, and it maps the app state to visual states defined in yourXAML. Because the page layout logic is provided for you, you only need to provide the views to be used for each of the page's visual states.

To transition between different views using XAML, you use the VisualStateManager to define different VisualStates for your app. Here you have a VisualStateGroup defined in ItemsPage.xaml. This group has 4 VisualStates named FullScreenLandscape, Filled, 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 needs to change from the baseline specified in the XAML for the UI.

<!--App Orientation States-->
  <VisualStateManager.VisualStateGroups>
     <VisualStateGroup x:Name="ApplicationViewStates">
        <VisualState x:Name="FullScreenLandscape" />
        <VisualState x:Name="Filled"> ... </VisualState>
        <VisualState x:Name="FullScreenPortrait"> ... </VisualState>
        <VisualState x:Name="Snapped"> ... </VisualState>
    </VisualStateGroup>
 </VisualStateManager.VisualStateGroups>

You use the FullScreenLandscape state when the app is full screen in the landscape orientation. Because you designed the default UI for this view, no changes are needed and this is just an empty VisualState.

You use the Filled state when the user has another app snapped to one side of the screen. The items view page just moves over in this case, and no changes are needed. This is just an empty VisualState, too.

You use the FullScreenPortrait state when the app is rotated from landscape to portrait orientation. In this visual state, you have 2 animations. One changes the style used for the back button, and the other changes the margin of itemGridView, so that everything fits better on the screen.

<!-- 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="Padding">
            <DiscreteObjectKeyFrame KeyTime="0" Value="96,136,86,56"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>

You use the Snapped state when the user has two apps showing, and your app is the narrower of the two. In this state, your app is only 320 device-independent pixels (DIPs) wide, so you need to make more drastic changes to the layout. In the XAML for the items page UI, both a GridView and a ListView are defined and bound to the data collection. By default, itemGridView is shown, and itemListView is collapsed. In the Snapped state, you have 4 animations that collapse itemGridView, show itemListView, and change the Style of the back button and page title to make them smaller.

<!--
    The back button and title have different styles when snapped, and the list representation is substituted
    for the grid 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="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>

In the Creating a consistent look with styles section of this tutorial, you created styles and templates to give the app a custom look. The default landscape view uses these styles and templates . To keep the custom look in different views, you also need to create custom styles and templates for those views.

In ItemsPage.xaml, you created a new data template for the grid items. You also need to provide a new data template for the list items that are shown in Snapped view. You name this template NarrowListItemTemplate and add it to the resources section of ItemsPage.xaml, just after the DefaultGridItemTemplate resource.

To add a template for the snapped view

  1. Double-click ItemsPage.xaml in Solution Explorer to open it.

  2. In the XAML editor, add this XAML in the Page.Resources section after the DefaultGridItemTemplate resource.

            <!-- 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>
    
  3. Select the ListView control named itemListView.

  4. Under Miscellaneous in the Properties panel, click the property marker for the ItemTemplate property. The property menu opens.

  5. In the menu, select Local Resource > NarrowListItemTemplate.

    Here's the full updated XAML for itemListView.

            <!-- Vertical scrolling list only used when snapped -->
            <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"
                IsSwipeEnabled="false" 
                IsItemClickEnabled="True" 
                ItemClick="ItemView_ItemClick"/>
    

In ItemsPage, you have both a GridView and a ListView, and you apply the templates in the default XAML. You show the GridView for the default view. When the app is snapped, you replace the grid with the ListView by using the VisualStateManager to change the Visibility properties of the two views.

In SplitPage, you use a different way to change the UI when the view changes. You have a single ListView that you show in all views. To change it's look when the view changes, you use the VisualStateManager to apply a new template to the ListView.

In SplitPage.xaml, you create a similar ListView template that’s used in the Filled and Snapped views, and in the FullScreenLandscape view if the screen width is less than 1366 DIPs. You also name this template NarrowListItemTemplate and add it to the resources section of SplitPage.xaml, just after the DefaultListItemTemplate resource.

To add a narrow template

  1. Double-click SplitPage.xaml in Solution Explorer to open it.

  2. In the XAML editor, add this XAML in the Page.Resources section after the DefaultListItemTemplate resource.

            <!-- green -->
            <SolidColorBrush x:Key="BlockBackgroundBrush" Color="#FF6BBD46"/>
    
            <!-- Used in Filled 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>
    

To use this data template, you update the visual states where it's used.

To update the visual states

  1. In the XAML for the FilledOrNarrow and Snapped visual states, find the animation that targets the ItemTemplate property of itemListView.

    Tip   Press Ctrl+F to open the Quick Find box, and search for "Standard80ItemTemplate" if you have trouble finding it.

     

  2. Change the value to use the NarrowListItemTemplate resource, like this: Value="{StaticResource NarrowListItemTemplate}".

    You use your custom template instead of the default Standard80ItemTemplate resource.

Here's the updated XAML for the animations.

<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>

You also replaced the item detail section of the split page with your own detail section that uses a WebView. Because you made this change, some animations in the Snapped_Detail visual state target elements that no longer exist. These will cause errors when the app is snapped, so you need to remove them.

To remove unused animations

  • In SplitPage.xaml, delete these animations from the Snapped_Detail visual state.

    <VisualState x:Name="Snapped_Detail">
        <Storyboard>
        ...
            <!--<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>
    

When you select a blog and navigate to the split page in the snapped view, you expect the list of blog posts to be shown. Instead, the text of the first blog post is shown because it's selected by default. To change this behavior, you make sure nothing is selected when you navigate to the split page in the snapped view.

To unselect the first item

  1. Double-click SplitPage.xaml.cs/vb in Solution Explorer to open it.

  2. In the LoadState method, find this block of code.

                if (pageState == null)
                {
                    // 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 (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
                    {
                        this.itemsViewSource.View.MoveCurrentToFirst();
                    }
                }
    
            If pageState Is Nothing Then
    
                ' 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 Not Me.UsingLogicalPageNavigation() AndAlso Me.itemsViewSource.View IsNot Nothing Then
                    Me.itemsViewSource.View.MoveCurrentToFirst()
                End If
    ...
    
  3. Add the else clause as shown here. The call MoveCurrentToPosition(-1) makes the current item not set.

                if (pageState == null)
                {
                    // 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 (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
                    {
                        this.itemsViewSource.View.MoveCurrentToFirst();
                    }
                    else
                    {
                        this.itemsViewSource.View.MoveCurrentToPosition(-1);
                    }
                }
    
            If pageState Is Nothing Then
    
                ' 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 Not Me.UsingLogicalPageNavigation() AndAlso Me.itemsViewSource.View IsNot Nothing Then
                    Me.itemsViewSource.View.MoveCurrentToFirst()
                Else
                    Me.itemsViewSource.View.MoveCurrentToPosition(-1)
                End If
    ...
    

In DetailPage.xaml, you just need to adjust the margin of the WebView in the Snapped view to use all the available space.

To update DetailPage.xaml

  1. Double-click DetailPage.xaml in Solution Explorer to open it.

  2. In the XAML for the Snapped visual state, add an animation to change the value of the Margin property on contentViewBorder.

            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentViewBorder" Storyboard.TargetProperty="Margin">
                <DiscreteObjectKeyFrame KeyTime="0" Value="20,5,20,20"/>
            </ObjectAnimationUsingKeyFrames>
    >
    

Here's the full XAML for the snapped state.

                <!-- The back button and title have different styles when snapped -->
                <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="contentViewBorder" Storyboard.TargetProperty="Margin">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="20,5,20,20"/>
                        </ObjectAnimationUsingKeyFrames>

                    </Storyboard>
                </VisualState>

Press F5 to build and run the app. Try it in different views and orientations.

Managing app lifecycle and state

Note   In this section, you apply concepts discussed in Part 2: Manage app lifecycle and state. For more info about saving app state, review Part 2.

 

To conserve system resources, the system automatically suspends (and sometimes terminates) Windows Store apps that are running in the background. A well-designed app can be suspended, terminated, and relaunched by the system and seem as though it were running the entire time. Here, you add code to handle suspending and resuming the app. Fortunately, most of the infrastructure is provided by the LayoutAwarePage and SuspensionManager classes in the Visual Studio templates, so you just need to add a bit of code to save and restore settings.

Managing navigation state

To use the SuspensionManager class to manage app state, you register the root Frame that holds your content with SuspensionManager. When the app is suspended, you call SuspensionManager.SaveAsync to save your app's navigation state, including which page is the current page and the navigation parameter that was passed to that page. When the app is restored, the navigation state is restored and the navigation parameter is passed back to the page.

To save navigation state

  1. Double-click App.xaml.cs/vb in Solution Explorer to open it.

  2. Add the async keyword to the OnSuspending method.

    private async void OnSuspending(object sender, SuspendingEventArgs e)
    
    Private Async Sub OnSuspending(sender As Object, e As SuspendingEventArgs) 
        Handles Me.Suspending
    
  3. Add this code to the OnSuspending method, after the "TODO" comment.

    SuspensionManager.SaveAsync saves the navigation state of the Frame and then gives the Page an opportunity to save its content.

                //TODO: Save application state and stop any background activity
                await WindowsBlogReader.Common.SuspensionManager.SaveAsync();
    
            ' TODO: Save application state and stop any background activity
            Await Common.SuspensionManager.SaveAsync()
    

When the app is restored after being suspended and terminated, this code is executed to restore the navigation state of the app.

To load navigation state

  1. In App.xaml.cs/vb, add this code in the OnLaunched method immediately after the instantiation of the new Frame.

    Here, you register the Frame with the SuspensionManager class.

                    // Add this code after "rootFrame = new Frame();"
                    WindowsBlogReader.Common.SuspensionManager.RegisterFrame(rootFrame, "AppFrame");
    
                ' Add this code after "rootFrame = New Frame()"
                Common.SuspensionManager.RegisterFrame(rootFrame, "AppFrame")
    
  2. Add this code in the OnLaunched method after the "TODO" comment.

    SuspensionManager.RestoreAsync restores the navigation state of the Frame and then gives the Page an opportunity to restore its content.

                        //TODO: Load state from previously suspended application
                        await WindowsBlogReader.Common.SuspensionManager.RestoreAsync();
    
                    ' TODO: Load state from previously suspended application
                    Await Common.SuspensionManager.RestoreAsync()
    

Managing page state

You also need to save any page specific settings when the app is suspended and restored. In the blog reader app, you just need to save and restore the selected item in the split page. You do this in the overrides of the SaveState and LoadState methods that are provided by the LayoutAwarePage class.

To save page state

  1. Double-click SplitPage.xaml.cs/vb in Solution Explorer to open it.

  2. Add this code in the SaveState method after the "TODO" comment.

    The SuspensionManager class serializes and saves the pageState dictionary to an XML file. Data saved in pageState is saved only for this session. Here, you save the title of the blog post selected in the list.

                    // TODO: Derive a serializable navigation parameter and assign it to
                    //       pageState["SelectedItem"]
                    if (selectedItem != null)
                    {
                        string itemTitle = ((FeedItem)selectedItem).Title;
                        pageState["SelectedItem"] = itemTitle;
                    }
    
                ' TODO: Derive a serializable navigation parameter and assign it to
                '       pageState("SelectedItem")
                If selectedItem IsNot Nothing Then
                    Dim itemTitle = (DirectCast(selectedItem, FeedItem)).Title
                    pageState("SelectedItem") = itemTitle
                End If
    

To load page state

  • In SplitPage.xaml.cs/vb, add this code in the LoadState method after the third "TODO" comment.

                        // TODO: Invoke this.itemsViewSource.View.MoveCurrentTo() with the selected
                        //       item as specified by the value of pageState["SelectedItem"]
    
                        string itemTitle = (string)pageState["SelectedItem"];
                        FeedItem selectedItem = FeedDataSource.GetItem(itemTitle);
                        this.itemsViewSource.View.MoveCurrentTo(selectedItem);
    
                    ' TODO: Invoke Me.itemsViewSource.View.MoveCurrentTo() with the selected
                    '       item as specified by the value of pageState("SelectedItem")
                    Dim itemTitle = DirectCast(pageState("SelectedItem"), String)
                    Dim selectedItem = FeedDataSource.GetItem(itemTitle)
                    Me.itemsViewSource.View.MoveCurrentTo(selectedItem)
    

The first impression your app makes on a user comes from the logo and splash screen. The logo appears in the Windows Store and on the start screen. The splash screen is shown instantly when a user launches the app, and gives immediate feedback to the user while your app initializes its resources. It's dismissed when the first page of your app is ready to show.

The splash screen consists of a background color and an image that's 620 x 300 pixels. You set these values in the Package.appxmanifest file. Double click this file to open it in the manifest editor. On the Application UI tab of the manifest editor, you set the path to your splash screen image and the background color. The project template provides a default blank image named SplashScreen.png. You replace this with your own splash screen image that clearly identifies your app and immediately draws users into your app. You can also specify a logo by replacing the template logo files with your own. Here's the splash screen for the blog reader.

The basic splash screen works well for the blog reader, but you can also extend the splash screen 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 you know it's time to start any content entrance animations for the app.

See the code

Did you get stuck, or do you want to check your work? If so, see Blog reader complete code.

What's next

You learned how to use built-in page templates from Visual Studio Express 2012 for Windows 8 to build a complete multi-page app, and how to navigate and pass data between the pages. You learned how to use styles and templates to make your app fit the personality of the Windows team blogs website. You also learned how to use theme animations, an app bar, and a splash screen to make your app fit the personality of Windows 8. Finally, you learned how to adapt your app to various layouts and orientations so that it always looks its best.

For more info about developing apps, see the learning and reference resource list for creating Windows Store apps.

Roadmap for Windows Runtime apps using C# or Visual Basic.