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...
- This is the last tutorial in a series. Before you start this tutorial, we recommend that you read Part 1: Create a "Hello, world!" app, Part 2: Manage app lifecycle and state, Part 3: Navigation, layout, and views, and Part 4: File access and pickers. This tutorial introduces new concepts, and adds to concepts from the previous tutorials.
- You can complete this tutorial without reading the previous ones. However, we assume that you understand concepts from previous tutorials and can complete basic tasks in Microsoft Visual Studio.
- We assume you have a basic understanding of XAML and the concepts in the XAML overview.
- We assume you're using the default window layout in Visual Studio. If you change the default layout, you can reset it in the Window menu by picking the Reset Window Layout command.
- You can see the complete code for this tutorial in Blog reader complete code.
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
Open Visual Studio Express 2012 for Windows 8.
Select File > New Project. The New Project dialog opens.
Under Templates in the left pane, expand Visual C# or Visual Basic.
Select the Windows Store template type.
In the center pane, select Blank App (XAML).
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.
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
Select Project > Add Class. The New Item dialog box opens.
Enter "FeedData" as the name for the class file.
Click Add. Your new class file is created.
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
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
- Double-click App.xaml in Solution Explorer. The file opens in the XAML editor.
- 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
Double-click App.xaml.cs/vb in Solution Explorer. The file opens in the code editor.
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)
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
- To run the app, do one of these:
- Click Debug > Start debugging.
- Press F5.
- To stop debugging:
- Press Alt+Tab to return to Visual Studio.
- 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
Select Project > Add New Item. The Add New Item dialog opens.
Here's the Add New Item dialog.
Under Visual C# or Visual Basic in the left pane, pick the Windows Store template type.
In the center pane, select the type of page to add to your project. Select the Items Page template.
Enter a name for the page. Enter "ItemsPage.xaml".
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.
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.
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".
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
andGoHome
event handlers support basic navigation.- A default view model gives you a simple bindable data source.
SaveState
andLoadState
methods work with theSuspensionManager
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.
Navigating between pages
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.
Navigating to ItemsPage
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
Double-click App.xaml.cs/vb in Solution Explorer. The file opens in the code editor.
In the OnLaunched method, update the call to
rootFrame.Navigate
and passItemsPage
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
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
Double-click ItemPage.xaml.cs/vb in Solution Explorer to open it.
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
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.
Navigating to SplitPage
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
- Double-click ItemsPage.xaml in Solution Explorer to open it.
- Select the GridView control named
itemGridView
. - In the Properties panel, expand the Common section, then click the down arrow at the bottom of the section to expand the advanced properties.
- Check the box for the IsItemClickEnabled property. This sets the property to true.
- Add a handler for the ItemClick event.
In the Properties panel, click the Events button ().
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.
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.
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
- In ItemsPage.xaml, select the ListView control named
itemListView
. - In the Properties Window, click the Properties button ().
- 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
- Double-click ItemsPage.xaml in Solution Explorer to open it.
- Select the TextBlock named
pageTitle
. - In the Properties Window, click the Properties button ().
- 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.
- In the property menu, select Edit Resource.... The Edit Resource dialog opens.
- In the Edit Resource dialog, change the value from "My Application" to "Windows Team Blogs".
- 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
Double-click SplitPage.xaml.cs/vb in Solution Explorer to open it in the code editor.
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
Double-click SplitPage.xaml in Solution Explorer to open it.
Select the Grid named
titlePanel
.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">
Select the TextBlock named
pageTitle
.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.
In the property menu, select Create Data Binding.... The Create Data Binding dialog opens.
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}"/>
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
In SplitPage.xaml, delete the ScrollViewer named
itemDetail
and all of its contents.In the XAML editor, add this XAML in place of the
itemDetail
ScrollViewer 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
Select the WebView named
contentView
that you added in the previous step.In the Properties Window, click the Events button ().
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.
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.
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
Double-click SplitPage.xaml.cs/vb in Solution Explorer. The file opens in the code editor.
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
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
Double-click DetailPage.xaml in Solution Explorer to open it.
Bind the page title TextBlock to the
Title
property of the selected blog post.- Select the TextBlock named
pageTitle
. - In the Properties Window, click the Properties button ().
- Under Common in the Properties panel, click the property marker for the Text property. The property menu opens.
- In the property menu, select Create Data Binding.... The Create Data Binding dialog opens.
- In the Create Data Binding dialog, select Data context in the Binding type drop-down list.
- 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.
- Select the TextBlock named
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>
Double-click DetailPage.xaml.cs/vb in Solution Explorer to open it.
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
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
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
Double-click SplitPage.xaml in Solution Explorer to open it.
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=""/> </Style>
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>
Select the Button in the app bar you just added.
In the Properties Window, click the Events button ().
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.
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.
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
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
Double-click DetailPage.xaml in Solution Explorer to open it.
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
Double-click DetailPage.xaml.cs/vb in Solution Explorer to open it.
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
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
Double-click App.xaml in Solution Explorer to open it.
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
Double-click ItemsPage.xaml in Solution Explorer to open it.
Select the page's root layout Grid.
Under Miscellaneous in the Properties panel, find the Style property.
Click the property marker next to the Style property to open the menu.
In the menu, select Local Resource > WindowsBlogLayoutRootStyle.
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}">
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
In Solution Explorer, open SplitPage.xaml.
Select the ListView named "itemListView".
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.
In the property menu, select Convert to New Resource.... The Create DataTemplate Resource dialog opens.
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.
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 ofitemListView
is also updated to useDefaultListItemTemplate
.
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
Select Project > Add Class. The New Item dialog box opens.
Enter
DateConverter
as the name for the class file.Click Add. The DateConverter.cs/vb class file is created.
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
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
- Double-click App.xaml in Solution Explorer to open it.
- Add the resource declaration,
<local:DateConverter x:Key="dateConverter" />
, to the ResourceDictionary after thefeedDataSource
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
Double-click App.xaml in Solution Explorer to open it.
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
Double-click SplitPage.xaml in Solution Explorer to open it.
In the XAML editor, find the
DefaultListItemTemplate
resource you created previously.Replace the XAML for the Border element with this XAML.
<Border Background="#FF6BBD46" Width="110" Height="110"> <ContentControl Template="{StaticResource DateBlockTemplate}" /> </Border>
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
In SplitPage.xaml, select the AppBar control.
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
Double-click App.xaml in Solution Explorer to open it.
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
Double-click ItemsPage.xaml in Solution Explorer to open it.
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>
Select the GridView control named
itemGridView
.Under Miscellaneous in the Properties panel, click the property marker for the ItemTemplate property. The property menu opens.
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"/>
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
Double-click ItemsPage.xaml in Solution Explorer to open it.
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>
Select the ListView control named
itemListView
.Under Miscellaneous in the Properties panel, click the property marker for the ItemTemplate property. The property menu opens.
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
Double-click SplitPage.xaml in Solution Explorer to open it.
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
In the XAML for the
FilledOrNarrow
andSnapped
visual states, find the animation that targets the ItemTemplate property ofitemListView
.Tip Press Ctrl+F to open the Quick Find box, and search for "Standard80ItemTemplate" if you have trouble finding it.
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
Double-click SplitPage.xaml.cs/vb in Solution Explorer to open it.
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 ...
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
Double-click DetailPage.xaml in Solution Explorer to open it.
In the XAML for the
Snapped
visual state, add an animation to change the value of the Margin property oncontentViewBorder
.<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
Double-click App.xaml.cs/vb in Solution Explorer to open it.
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
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
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")
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
Double-click SplitPage.xaml.cs/vb in Solution Explorer to open it.
Add this code in the
SaveState
method after the "TODO" comment.The
SuspensionManager
class serializes and saves thepageState
dictionary to an XML file. Data saved inpageState
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)
Adding a splash screen and logo
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.