Windows Phone 7 Quick Tutorials: Part 5 - Pivot and Panorama

In the previous articles of the series we've seen how to create WP7 Silverlight applications, use the app bar, navigate through the pages and managing the life cycle of the application and persist application data. In this new installment we'll discuss two controls: pivot and panorama.

Overview

These two controls are available in the Microsoft.Phone.Controls namespane from the assembly with the same name. They are similar controls allowing you to organize the visual content of your page into smaller parts (subpages or columns), which you can navigate through by sweeping the finger on the screen. Basically pivot and panorama are like a tab control for desktop applications. Each "subpage", called item (pivot item or panorama item) has a header and you can also navigate through the items by taping on the header. Both look like a big sheet or canvas grouped into subpages, and you have a view port into one of the subpages at a given time. Pivot and panorama differ in the way the control title and the headers look and how the transition from one item to another occurs, the transition being smoother with panorama.

The following image from Microsoft's UI Design and Interactions Guide for Windows Phone 7 shows how a panorama control looks like. The control has one big title, which can be visible only partially at a time, as you navigate through the items (of course, you can tweak that and use a text and an image, like a logo, or just an image). Each item has a header with a text. The rest of the page is for the content of the item, which can be anything.

WP7: the content of the item

On the other hand, the pivot, shown in the following image from the same Microsoft document, has a much smaller title, leaving more space for the content of each item. Header items are similar to panorama's.

Each item has a header with a text

Using one or the other requires same development effort and in fact is very easy to switch between the two controls as we will see in this article. You can read the guidelines paper for more information and suggestions on the two controls.

Demo application

In this article we'll create a simple application that will show how to use the pivot and panorama controls and what the differences between the two are. This application will have a main page with a text block that allows us to enter the title of a movie, a Search button to search the movie in the IMDB database and two radio buttons that specify in which format the results should be displayed, pivot or panorama. Both controls will have four items:

  • overview, with several information about the movie such as release date, plot, main actors, list directors and writers,
  • synopsis, listing the summary of the movie,
  • cast, displaying a list of the actors, and
  • poster, containing the main poster for the movie (as listed in the IMDB database).

The following image shows the main page and the first item of the pivot control displaying results for the "Gladiator" movie.

WP7 Demo

Note: IMDB does not provide a service to query its database. To retrieve information about a movie I used a modified version of an ASP.NET scrapper API implemented by Abhinay Rathore and available here. What it does is performing a Google search for a movie title with the "I'm Feeling Lucky" option, which automatically directs the result to the IMDB website. The returned HTML page is then parsed with regular expressions to extract data from it. Not the best possible approach, but better than nothing. And anyways, the point of the article is presenting the pivot and panorama controls, and not APIs for fetching movie information.

Windows Phone 7 Quick Tutorials: Part 5 - Pivot and Panorama

Using a Pivot

To add a page with a pivot control, use the Add New Item command and from the available pages select Windows Phone Pivot Page. This will add a new page with the layout root containing a pivot (we'll call this MoviePivotPage.xaml).

[wp7pp_pivottemplate.png]

The XAML code looks like this:

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <!--Pivot Control-->
    <controls:Pivot Title="my application">
        <!--Pivot item one-->
        <controls:PivotItem Header="item1">
            <Grid>
        </controls:PivotItem>
        
        <!--Pivot item two-->
        <controls:PivotItem Header="item2">
            <Grid>
        </controls:PivotItem>
        
    </controls:Pivot>
</Grid>

As you can see the wizard defines two pivot items (the class that represents a pivot item is simply called PivotItem), with an empty grid as the content. We can either use the grid to add more controls, or replace it with a stack panel or a list (as we'll see in this example) or any other control. The following listing shows how the pivot will look in this example, with various controls used to display movie information in each of the four pivot items (overview, synopsis, cast and poster).

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <!--Pivot Control-->
    <controls:Pivot Title="{Binding Path=Title}">
        <!--Pivot item one-->
        <controls:PivotItem Header="overview">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock Grid.Row="0" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Year" />
                <TextBlock Grid.Row="0" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=Year}" />

                <TextBlock Grid.Row="1" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Release date" />
                <TextBlock Grid.Row="1" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=ReleaseDate}" />

                <TextBlock Grid.Row="2" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Rating" />
                <TextBlock Grid.Row="2" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=Rating}" />

                <TextBlock Grid.Row="3" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Runtime" />
                <TextBlock Grid.Row="3" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=Runtime}" />

                <TextBlock Grid.Row="4" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Plot" />
                <TextBlock Grid.Row="4" Grid.Column="1" 
                           Margin="5,5,5,5"
                           TextWrapping="Wrap"
                           Text="{Binding Path=Plot}" />

                <TextBlock Grid.Row="5" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Genre(s)" />
                <ListBox Grid.Row="5" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listGenres"/>

                <TextBlock Grid.Row="6" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Staring" />
                <ListBox Grid.Row="6" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listStars"/>

                <TextBlock Grid.Row="7" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Director(s)" />
                <ListBox Grid.Row="7" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listDirectors"/>

                <TextBlock Grid.Row="8" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Writer(s)" />
                <ListBox Grid.Row="8" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listWriters"/>

            </Grid>
        </controls:PivotItem>

        
        <controls:PivotItem Header="synopsis">
            <StackPanel>
                <TextBlock Text="Tagline"
                           Style="{StaticResource PhoneTextTitle2Style}"/>
                <TextBlock Text="{Binding Path=Tagline}"
                           TextWrapping="Wrap"
                           Margin="10,10,10,10"/>

                <TextBlock Text="Storyline"
                           Style="{StaticResource PhoneTextTitle2Style}"/>
                <TextBlock Text="{Binding Path=Storyline}"
                           TextWrapping="Wrap"
                           Margin="10,10,10,10"/>
            </StackPanel>
        </controls:PivotItem>

        <!--Pivot item three-->
        <controls:PivotItem Header="cast">
            <ListBox ItemsSource="{Binding}"
                     x:Name="listCast"/>
        </controls:PivotItem>

        <!--Pivot item four-->
        <controls:PivotItem Header="poster">
            <Image x:Name="imgPoster"/>
        </controls:PivotItem>
    </controls:Pivot>
</Grid>

First step is to create an IMDb object holding information about the searched movie (parsed from the IMDB HTML results page) and pass it to the pivot page. We do that with the transient dictionary as we've seen in a previous article.

IMDb movie = new IMDb(response);

PhoneApplicationService.Current.State["movie"] = movie;
if (rbtnPivot.IsChecked.HasValue && rbtnPivot.IsChecked.Value)
{
    this.NavigationService.Navigate(new Uri("/MoviePivotPage.xaml", UriKind.Relative));
}

Then, on the results page, we override the OnNavigatedTo handler, fetch the movie from the transient cache and set it as data context for the page and controls.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    object obj;
    if (PhoneApplicationService.Current.State.TryGetValue("movie", out obj))
    {
        IMDb movie = (IMDb)obj;
        this.DataContext = movie;
        this.listGenres.DataContext = movie.Genres;
        this.listStars.DataContext = movie.Stars;
        this.listDirectors.DataContext = movie.Directors;
        this.listWriters.DataContext = movie.Writers;
        this.listCast.DataContext = movie.Cast;
        this.imgPoster.Source = new BitmapImage(new Uri(movie.Poster, UriKind.Absolute));
    }

    base.OnNavigatedTo(e);
}

If we now run the application, search for "gladiator" and display the results in the pivot, we'll see the pages filled with information as shown in the following image. You can sweep your finger across the screen (if you're using a real device) or use the mouse to simulate that if you're using the emulator, to navigate through the pivot items.

[wp7pp_pivot_2.jpg]

The Pivot control defines several events:

  • LoadingPivotItem: gives you the opportunity to dynamically load or change the content of a pivot item before it is displayed.
  • LoadedPivotItem: indicates that an item was completely loaded.
  • UnloadingPivotItem: gives you the opportunity to dynamically load, change or remove the content of a pivot item as it is removed.
  • UloadedPivotItem: indicates that the pivot item has been completely unloaded from the visual pivot.
  • SelectionChanged: indicates that the currently selected item changed.

The following events are fired when we open the pivot page with the "overview" item first displayed, and then we navigate to the "synopsis" item: SelectionChanged ("overview" added) -> LoadingPivotItem ("overview") -> LoadedPivotItem ("overview") -> UnloadingPivotItem ("overview") -> SelectionChanged ("overview" removed, "synopsis" added) -> LoadingPivotItem ("synopsis") -> UnloadedPivotItem ("overview") -> LoadedPivotItem ("synopsis").

Let's see an example for using these events: instead of loading the poster image when we display the page, we'll only load and display it when the user navigates to the "poster" pivot item. This way we won't load unnecessary resources (in case the user never views the poster item).

<controls:Pivot Title="{Binding Path=Title}"
                LoadedPivotItem="Pivot_LoadedPivotItem">                

Then, the page code will change as shown below. Notice that the image is loaded only the first time the LoadedPivotItem fires for the "poster" item.

public partial class MoviePivotPage : PhoneApplicationPage
{
    IMDb m_movie;

    public MoviePivotPage()
    {
        InitializeComponent();
    }

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        object obj;
        if (PhoneApplicationService.Current.State.TryGetValue("movie", out obj))
        {
            m_movie = (IMDb)obj;
            this.DataContext = m_movie;
            this.listGenres.DataContext = m_movie.Genres;
            this.listStars.DataContext = m_movie.Stars;
            this.listDirectors.DataContext = m_movie.Directors;
            this.listWriters.DataContext = m_movie.Writers;
            this.listCast.DataContext = m_movie.Cast;
        }

        base.OnNavigatedTo(e);
    }

    private void Pivot_LoadedPivotItem(object sender, PivotItemEventArgs e)
    {
        string header = e.Item.Header as string;
        if (header == "poster")
        {
            if(imgPoster.Source == null)
                imgPoster.Source = new BitmapImage(new Uri(m_movie.Poster, UriKind.Absolute));
        }
    }
}

Windows Phone 7 Quick Tutorials: Part 5 - Pivot and Panorama

Using a Panorama

Using a panorama is extremely similar to a pivot. In fact the XAML code is identical if you replace the types Pivot with Panorama and PivotItem with PanoramaItem. We will add a new Windows Phone Panorama Page to the project and call it MoviePanoramaPage.xaml.

[wp7pp_panoramatemplate.png]

The XAML code added by the wizard is shown (the essential part) below.

<!--LayoutRoot contains the root grid where all other page content is placed-->
<Grid x:Name="LayoutRoot">
    <controls:Panorama Title="my application">

        <!--Panorama item one-->
        <controls:PanoramaItem Header="item1">
            <Grid/>
        </controls:PanoramaItem>

        <!--Panorama item two-->
        <controls:PanoramaItem Header="item2">
            <Grid/>
        </controls:PanoramaItem>
    </controls:Panorama>
</Grid>

We will modify the XAML code to define four panorama items with the same controls we added to the pivot items and the same bindings. The following listing shows the code.

<!--LayoutRoot contains the root grid where all other page content is placed-->
<Grid x:Name="LayoutRoot">
    <controls:Panorama Title="{Binding Path=Title}">

        <!--Panorama item one-->
        <controls:PanoramaItem Header="overview">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock Grid.Row="0" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Year" />
                <TextBlock Grid.Row="0" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=Year}" />

                <TextBlock Grid.Row="1" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Release date" />
                <TextBlock Grid.Row="1" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=ReleaseDate}" />

                <TextBlock Grid.Row="2" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Rating" />
                <TextBlock Grid.Row="2" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=Rating}" />

                <TextBlock Grid.Row="3" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Runtime" />
                <TextBlock Grid.Row="3" Grid.Column="1" 
                           Margin="5,5,5,5"
                           Text="{Binding Path=Runtime}" />

                <TextBlock Grid.Row="4" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Plot" />
                <TextBlock Grid.Row="4" Grid.Column="1" 
                           Margin="5,5,5,5"
                           TextWrapping="Wrap"
                           Text="{Binding Path=Plot}" />

                <TextBlock Grid.Row="5" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Genre(s)" />
                <ListBox Grid.Row="5" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listGenres"/>

                <TextBlock Grid.Row="6" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Staring" />
                <ListBox Grid.Row="6" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listStars"/>

                <TextBlock Grid.Row="7" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Director(s)" />
                <ListBox Grid.Row="7" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listDirectors"/>

                <TextBlock Grid.Row="8" Grid.Column="0" 
                           Margin="5,5,5,5"
                           Text="Writer(s)" />
                <ListBox Grid.Row="8" Grid.Column="1" 
                         Margin="5,5,5,5"
                         ItemsSource="{Binding}"
                         x:Name="listWriters"/>

            </Grid>
        </controls:PanoramaItem>

        <!--Panorama item two-->
        <controls:PanoramaItem Header="synopsis">
            <StackPanel>
                <TextBlock Text="Tagline"
                           Style="{StaticResource PhoneTextTitle2Style}"/>
                <TextBlock Text="{Binding Path=Tagline}"
                           TextWrapping="Wrap"
                           Margin="10,10,10,10"/>

                <TextBlock Text="Storyline"
                           Style="{StaticResource PhoneTextTitle2Style}"/>
                <TextBlock Text="{Binding Path=Storyline}"
                           TextWrapping="Wrap"
                           Margin="10,10,10,10"/>
            </StackPanel>
        </controls:PanoramaItem>

        <!--Panorama item three-->
        <controls:PanoramaItem Header="cast">
            <ListBox ItemsSource="{Binding}"
                     x:Name="listCast"/>
        </controls:PanoramaItem>

        <!--Panorama item four-->
        <controls:PanoramaItem Header="poster">
            <Image x:Name="imgPoster"/>
        </controls:PanoramaItem>
        
    </controls:Panorama>
</Grid>

Next, we should override the OnNavigatedTo method to set the data context for the bindings, but the method looks identical with the one shown for the pivot page so I won't show it again. Changes to the main page are minimal and very similar with what we did for the pivot page.

IMDb movie = new IMDb(response);

PhoneApplicationService.Current.State["movie"] = movie;
if (rbtnPivot.IsChecked.HasValue && rbtnPivot.IsChecked.Value)
{
    this.NavigationService.Navigate(new Uri("/MoviePivotPage.xaml", UriKind.Relative));
}
else if (rbtnPanorama.IsChecked.HasValue && rbtnPanorama.IsChecked.Value)
{
    this.NavigationService.Navigate(new Uri("/MoviePanoramaPage.xaml", UriKind.Relative));
}

The following image strip shows the results for the same movie in the panorama page.

[wp7pp_panorama_2.jpg]

If you compare this image strip with the one for the pivot page you can immediately see some differences: the size of the title is much bigger for panorama, which leaves less space for the panorama items content vertically. On the other hand, the horizontal size is also less with the panorama, because of the aesthetic nature of panorama which creates the impression of a single, continuous canvas, with left part of the next item also visible on the screen. When you run the application and sweep the screen you can notice another difference in the transition between two items of the pivot and panorama: pivot items slide swiftly into the view, while panorama items transit smoothly through the screen, creating the impression of the continuous canvas.

Panorama defines a single event, SelectionChanged. The loading and unloading events don't make sense for panorama, because this control is basically a big single canvas, everything being loaded at the same time.

Conclusions

Pivot and Panorama are two WP7 specific controls with many similarities, yet some important aesthetic differences, such as the transition between the items, or the size and span of the title. You can easily switch between the two, basically all you have to do is replacing Pivot with Panorama and PivotItem with PanoramaItem. Though they are primally design to be used in the portrait layout, they also work with the panorama layout. You should read the UI Design and Interactions Guide for Windows Phone 7 to learn more about when you should use the two and what rules you should follow.



About the Author

Marius Bancila

Marius Bancila is a Microsoft MVP for VC++. He works as a software developer for a Norwegian-based company. He is mainly focused on building desktop applications with MFC and VC#. He keeps a blog at www.mariusbancila.ro/blog, focused on Windows programming. He is the co-founder of codexpert.ro, a community for Romanian C++/VC++ programmers.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds