Share via


Exercise 3: Introduction to Panorama Control

In this exercise we continue to develop the Wazup application. At the end of this exercise the Wazup application will have a blog page that will show us posts from the Windows Phone Developer Blog, in addition to what we've accomplished in the previous exercises.

The exercise will include the following tasks:

  • Add a blog page that uses a Panorama control
  • Implement Blog page functionality

Task 1 – Adding Blog Page

In this task we will add a placeholder page for the Blog. The page will contain a panorama control that can show several pages of information. We will use the panorama control on the next task to show 4 different sub-pages of the blog page.

  1. Open Microsoft Visual Studio 2010 Express for Windows Phone or Visual Studio 2010
  2. Open the Begin.sln starter solution from the Source\Ex3-Panorama\Begin folder of this lab. Alternatively, you can continue working on the solution created in previous exercise.
  3. Add a folder to the project, named BlogImages by right-clicking on Wazup (project name) and selecting Add New Folder.
  4. Add to the BlogImages folder all the files which are located in the Source\Assets\BlogImages folder in this lab.
  5. Select all the files in the BlogImages folder and change the Build Action property to Content.

    This will cause the images to be added to the XAP file instead of as resources inside the DLL file.

  6. Add a new item to the Views folder by right-clicking the Views folder and selecting AddNew Item…
  7. Select Windows Phone Panorama Page and name it BlogPage.xaml.

    Figure 6

    Add new Windows Phone Panorama Page

  8. Compile and run the application.
  9. Stop the debugging and return to the code. This step concludes the current task.

Task 2 – Implementing Blog Page Functionality

In this task we will implement the functionality of the Blog page in the Wazup application. At the end of this task the Wazup application will have a working Blog page.

During this task we will use the Panorama control to show 4 sub-pages of the blog page.

What distinguishes the Panorama control from the Pivot control is that where the Pivot control shows multiple different pages, the Panorama control presents a single page divided to sub-pages that we can scroll, like a long horizontal canvas that extends the confines of the screen. Unlike the Pivot control, the Panorama control has a background image that stretches across all the sub-pages giving the feel of a wide screen.

  1. Add reference to the System.ServiceModel.Syndication assembly (from the Browse tab), located in \Source\Assets\Binaries \System.ServiceModel.Syndication.dll. This assembly is needed for the blog service utility class that we will add shortly.
  2. Now we will add the utility classes used to work with the blog service. Add the BlogService.cs, ImageItem.cs, ImageService.cs, and RssItem.cs files that are located in Source\Assets\Services folder of this lab to the Services folder .
  3. Open the file BlogPage.xaml
  4. Add the following XML namespaces to phone:PhoneApplicationPage element next to the other XML namespaces definitions:

    XAML

    xmlns:localHelpers="clr-namespace:Wazup.Helpers" xmlns:localWindowsControls="clr-namespace:System.Windows.Controls"

  5. Set the DataContext property of the BlogPage to be the BlogPage itself. Do this by adding the following line to the phone:PhoneApplicationPage element:

    XAML

    DataContext="{Binding RelativeSource={RelativeSource Self}}"

  6. Now we will define the UI for the blog page. Locate the LayoutRoot grid and replace the content with the following code. The blog page contains a single control: a Panorama control that will show 4 different sub-pages. In the Panorama control we first define the background image. This background image will stretch across all the sub-pages of the Panorama control, giving the feeling of a wide horizontal canvas which we will see section by section.Next we define the 4 sub-pages of the Panorama control:

    • The first sub-page shows the last 5 blog posts
    • The second sub-page shows all the posts in the blog
    • The third sub-page shows all the comments in the blog
    • The fourth sub-page shows a list of phone images

    The DataTemplates we used in each of these sub-pages will be defined in the next step.

    XAML

    <Grid x:Name="LayoutRoot"> <controls:Panorama x:Name="PanoramaControl" Title="windows phone blog"> <controls:Panorama.Background> <ImageBrush ImageSource="/Resources/PanoramaBG.png"/> </controls:Panorama.Background> <controls:PanoramaItem Header="last 5 posts"> <Grid> <ListBox ItemsSource="{Binding LastPosts}" ItemTemplate="{StaticResource PostLargeTemplate}" /> <localHelpers:ProgressBarWithText Text="Loading Posts..." ShowProgress="{Binding IsPostsLoading}" /> </Grid> </controls:PanoramaItem> <controls:PanoramaItem Header="all posts"> <Grid> <ListBox ItemsSource="{Binding Posts}" ItemTemplate="{StaticResource PostSmallTemplate}" /> <localHelpers:ProgressBarWithText Text="Loading Posts" ShowProgress="{Binding IsPostsLoading}" /> </Grid> </controls:PanoramaItem> <controls:PanoramaItem Header="comments"> <Grid> <ListBox ItemsSource="{Binding Comments}" ItemTemplate="{StaticResource CommentTemplate}" /> <localHelpers:ProgressBarWithText Text="Loading Comments" ShowProgress="{Binding IsCommentsLoading}" /> </Grid> </controls:PanoramaItem> <controls:PanoramaItem Header="images"> <Grid> <ListBox ItemsSource="{Binding Images}" ItemTemplate="{StaticResource ImageTemplate}" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <localWindowsControls:WrapPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox> </Grid> </controls:PanoramaItem> </controls:Panorama> </Grid>

  7. Here we define four data templates:

    • PostLargeTemplate defines a DataTemplate that is used in the "Last 5 Posts" sub-page. It transforms an RssItem object into a HyperlinkButton control that contains the title of the post and navigates to the post when clicked, and a TextBlock that contains the start of the post content.
    • PostSmallTemplate defines a DataTemplate that is used in the "All Posts" sub-page. It transforms an RssItem object into a HyperlinkButton control that contains the title of the post and navigates to the post when clicked, and a TextBlock that contains the start of the post content.
    • CommentTemplate defines a DataTemplate that is used in the "Comments" sub-page. It transforms an RssItem object into a HyperlinkButton control that contains the title of the comment and navigates to the comment's post when clicked, and a TextBlock that contains the start of the comment content.
    • ImageTemplate defines a DataTemplate that is used in the "Images" sub-page. It transforms an ImageItem object into an Image control that contains the loaded image and a TextBlock that contains the image name.

    Add the following code into BlogPage resources section.

    XAML

    <phone:PhoneApplicationPage x:Class="Wazup.Views.BlogPage" ...> <phone:PhoneApplicationPage.Resources> <DataTemplate x:Key="PostLargeTemplate"> <Grid Margin="12,0,12,40"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <HyperlinkButton Content="{Binding Title}" NavigateUri="{Binding Url}" TargetName="_blank" FontSize="29.333" Margin="0,0,0,3" HorizontalContentAlignment="Left" MaxHeight="75" Style="{StaticResource WrappedHyperlinkButtonStyle}" /> <TextBlock Grid.Row="1" Text="{Binding PlainSummary}" MaxHeight="80" TextWrapping="Wrap" Margin="0" Foreground="White" /> </Grid> </DataTemplate> <DataTemplate x:Key="PostSmallTemplate"> <Grid Margin="12,0,12,40"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <HyperlinkButton Content="{Binding Title}" NavigateUri="{Binding Url}" TargetName="_blank" HorizontalContentAlignment="Left" FontSize="24" MaxHeight="60" Style="{StaticResource WrappedHyperlinkButtonStyle}" Margin="0,0,0,3" /> <TextBlock Grid.Row="1" Text="{Binding PlainSummary}" FontSize="20" MaxHeight="50" TextWrapping="Wrap" Margin="0" Foreground="White" /> </Grid> </DataTemplate> <DataTemplate x:Key="CommentTemplate"> <Grid Margin="12,0,12,40"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <HyperlinkButton Content="{Binding Title}" NavigateUri="{Binding Url}" TargetName="_blank" HorizontalContentAlignment="Left" FontSize="20" MaxHeight="60" Style="{StaticResource WrappedHyperlinkButtonStyle}" Margin="0,0,0,2" /> <TextBlock Grid.Row="1" Text="{Binding PlainSummary}" FontSize="20" MaxHeight="30" TextWrapping="Wrap" Margin="0" Foreground="White" /> </Grid> </DataTemplate> <DataTemplate x:Key="ImageTemplate"> <StackPanel Margin="0,3,20,12"> <Border Background="#19FFFFFF" BorderBrush="#FFFFC425" BorderThickness="1" Margin="0"> <Image Source="{Binding FileName}" Stretch="None" Height="173" Width="173" /> </Border> <TextBlock Text="{Binding Name}" HorizontalAlignment="Left" Margin="0,0,0,3" Foreground="White" /> </StackPanel> </DataTemplate> </phone:PhoneApplicationPage.Resources>

  8. We now add buttons to the application bar in the blog page. After the LayoutRoot grid ends, add the following code:

    XAML

    <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton x:Name="AppbarButtonDigg" IconUri="/Resources/App.Digg.png" Text="Digg" Click="AppbarButtonDigg_Click" /> <shell:ApplicationBarIconButton x:Name="AppbarButtonTwitter" IconUri="/Resources/App.Twitter.png" Text="Twitter" Click="AppbarButtonTwitter_Click" /> <shell:ApplicationBarIconButton x:Name="AppbarButtonBlog" IconUri="/Resources/App.Blog.png" Text="Blog" Click="AppbarButtonBlog_Click" /> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>

  9. Open the file BlogPage.xaml.cs
  10. Replace the using section of the BlogPage class with the following code:

    C#

    using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Windows; using Microsoft.Phone.Controls; using Wazup.Services;

  11. Add to the BlogPage class the following dependency properties:
    • LastPosts of type ObservableCollection<RssItem> - used to save the collection of last 5 posts
    • Posts of type ObservableCollection<RssItem> - used to save the collection of posts
    • Comments of type ObservableCollection<RssItem> - used to save the collection of comments
    • Images of type ObservableCollection<ImageItem> - used to save the collection of images
    • IsPostsLoading of type bool – used to indicate whether posts loading is currently in progress
    • IsCommentsLoading of type bool – used to indicate whether comments loading is currently in progress

      C#

      #region LastPosts /// <summary> /// LastPosts Dependency Property /// </summary> public static readonly DependencyProperty LastPostsProperty = DependencyProperty.Register("LastPosts", typeof(ObservableCollection<RssItem>), typeof(BlogPage), new PropertyMetadata((ObservableCollection<RssItem>)null)); /// <summary> /// Gets or sets the LastPosts property. This dependency property /// indicates what are the last posts. /// </summary> public ObservableCollection<RssItem> LastPosts { get { return (ObservableCollection<RssItem>)GetValue(LastPostsProperty); } set { SetValue(LastPostsProperty, value); } } #endregion #region Posts /// <summary> /// Posts Dependency Property /// </summary> public static readonly DependencyProperty PostsProperty = DependencyProperty.Register("Posts", typeof(ObservableCollection<RssItem>), typeof(BlogPage), new PropertyMetadata((ObservableCollection<RssItem>)null)); /// <summary> /// Gets or sets the Posts property. This dependency property /// indicates what are all the posts. /// </summary> public ObservableCollection<RssItem> Posts { get { return (ObservableCollection<RssItem>)GetValue(PostsProperty); } set { SetValue(PostsProperty, value); } } #endregion #region Comments /// <summary> /// Comments Dependency Property /// </summary> public static readonly DependencyProperty CommentsProperty = DependencyProperty.Register("Comments", typeof(ObservableCollection<RssItem>), typeof(BlogPage), new PropertyMetadata((ObservableCollection<RssItem>)null)); /// <summary> /// Gets or sets the Comments property. This dependency property /// indicates what are the posts comments. /// </summary> public ObservableCollection<RssItem> Comments { get { return (ObservableCollection<RssItem>)GetValue(CommentsProperty); } set { SetValue(CommentsProperty, value); } } #endregion #region Images /// <summary> /// Images Dependency Property /// </summary> public static readonly DependencyProperty ImagesProperty = DependencyProperty.Register("Images", typeof(ObservableCollection<ImageItem>), typeof(BlogPage), new PropertyMetadata((ObservableCollection<ImageItem>)null)); /// <summary> /// Gets or sets the Images property. This dependency property /// indicates what are the images. /// </summary> public ObservableCollection<ImageItem> Images { get { return (ObservableCollection<ImageItem>)GetValue(ImagesProperty); } set { SetValue(ImagesProperty, value); } } #endregion #region IsPostsLoading /// <summary> /// IsPostsLoading Dependency Property /// </summary> public static readonly DependencyProperty IsPostsLoadingProperty = DependencyProperty.Register("IsPostsLoading", typeof(bool), typeof(BlogPage), new PropertyMetadata((bool)false)); /// <summary> /// Gets or sets the IsPostsLoading property. This dependency property /// indicates whether we are currently loading posts. /// </summary> public bool IsPostsLoading { get { return (bool)GetValue(IsPostsLoadingProperty); } set { SetValue(IsPostsLoadingProperty, value); } } #endregion #region IsCommentsLoading /// <summary> /// IsCommentsLoading Dependency Property /// </summary> public static readonly DependencyProperty IsCommentsLoadingProperty = DependencyProperty.Register("IsCommentsLoading", typeof(bool), typeof(BlogPage), new PropertyMetadata((bool)false)); /// <summary> /// Gets or sets the IsCommentsLoading property. This dependency property /// indicates whether we are currently loading comments. /// </summary> public bool IsCommentsLoading { get { return (bool)GetValue(IsCommentsLoadingProperty); } set { SetValue(IsCommentsLoadingProperty, value); } } #endregion

  12. Override the functions OnNavigateTo and OnNavigateFrom of the page. Use the StateManager class to save and load page state when the current page is changing. In the function OnNavigateTo, we first try to load the values from the state object, then we check if LastPosts has a value; if it doesn't, we load the data using the BlogService and ImageService functions. The BlogService.GetBlogPosts function accepts a callback function to call if the posts loading is complete and a callback function to call if the posts loading failed, for example, the blog rss feed is currently down. BlogService.GetBlogComments work similarly. The ImageService.GetImages function returns a list of ImageItem objects that represents an image.

    C#

    private const string LastPostsKey = "LastPostsKey"; private const string PostsKey = "PostsKey"; private const string CommentsKey = "CommentsKey"; private const string ImagesKey = "ImagesKey"; protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { this.SaveState(LastPostsKey, LastPosts); this.SaveState(PostsKey, Posts); this.SaveState(CommentsKey, Comments); this.SaveState(ImagesKey, Images); } protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { // try to load data from state object LastPosts = this.LoadState<ObservableCollection<RssItem>>(LastPostsKey); Posts = this.LoadState<ObservableCollection<RssItem>>(PostsKey); Comments = this.LoadState<ObservableCollection<RssItem>>(CommentsKey); Images = this.LoadState<ObservableCollection<ImageItem>>(ImagesKey); if (LastPosts != null) { return; } // if data wasn't loaded we get it from the blog service IsPostsLoading = true; BlogService.GetBlogPosts( delegate(IEnumerable<RssItem> rssItems) { const int NumberOfLastPosts = 5; LastPosts = new ObservableCollection<RssItem>(); Posts = new ObservableCollection<RssItem>(); foreach (RssItem rssItem in rssItems) { IsPostsLoading = false; Posts.Add(rssItem); if (LastPosts.Count < NumberOfLastPosts) { LastPosts.Add(rssItem); } } }, delegate(Exception exception) { IsPostsLoading = false; System.Diagnostics.Debug.WriteLine(exception); }); IsCommentsLoading = true; BlogService.GetBlogComments( delegate(IEnumerable<RssItem> rssItems) { IsCommentsLoading = false; Comments = new ObservableCollection<RssItem>(); foreach (RssItem rssItem in rssItems) { Comments.Add(rssItem); } }, delegate(Exception exception) { IsCommentsLoading = false; System.Diagnostics.Debug.WriteLine(exception); }); // load images from somewhere Images = new ObservableCollection<ImageItem>(); IEnumerable<ImageItem> images = ImageService.GetImages(); foreach (ImageItem imageItem in images) { Images.Add(imageItem); } }

  13. Add the following event handlers in the BlogPage class. In this step we handle the application bar buttons. In each event handler we use the Navigation class to go to the correct page.

    C#

    #region Appbar handlers /// <summary> /// Handles the Click event of the AppbarButtonDigg control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private void AppbarButtonDigg_Click(object sender, EventArgs e) { this.GoToPage(ApplicationPages.Digg); } /// <summary> /// Handles the Click event of the AppbarButtonTwitter control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private void AppbarButtonTwitter_Click(object sender, EventArgs e) { this.GoToPage(ApplicationPages.Trends); } /// <summary> /// Handles the Click event of the AppbarButtonBlog control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private void AppbarButtonBlog_Click(object sender, EventArgs e) { // Set "last 5 posts" as default item // this has the side effect of changing the selected item PanoramaControl.DefaultItem = PanoramaControl.Items[0]; } #endregion

  14. Compile and run the application.
  15. At this stage the blog page should work and look like the following:

    Figure 7

    Blog page showing blog posts

  16. Stop the debugging and return to the code. This step concludes the exercise and the lab.

During this exercise you learned how to use the Panorama control to show a wide page with several sub-pages.

Note:
The complete solution for this exercise is provided at the following location: Source\Ex3-Panorama\End.