Windows Phone 7: Databinding and the Pivot Control
Lately I have been working on a Windows Phone 7 application in my spare time. It has been a lot of fun but, I have to admit, there has been some frustration as well. The problems I ran into are caused by my unfamiliarity with WP7 (evidently), the strict requirements of the Windows Phone Marketplace and flaws in WP7.
One of the controls that will be used a lot on the Windows Phone is the Pivot control.
According to the Windows Phone 7 UI Design and Interaction Guide the Pivot control:
A pivot control provides a quick way to manage views or pages within the application. This control can be used for filtering large datasets, viewing multiple data sets, or switching application views. The control places individual views horizontally next to each other, and manages the left and right navigation. Flicking or panning horizontally on the page cycles the pivot functionality.
The content of the page inside the pivot control is defined by the application.
To me it feels a bit like a tab control because you can only see one Pivot Item at the time. While the Panorama control is a way of looking at one set of data in many ways the Pivot controls can show different (but related) sets of data.
The WP7 application I was developing needed to show a list of courses. These courses are grouped into main categories and the a main category has one or more subcategories. I decided to present the following flow to the user:
First: select a category (this is a Pivot control with two Pivot Items: “category” and “search”)
This feels natural. When you select a category the fun begins. Let’s have a look at the Adobe category:
This could be a standard page but you have to realize that this category is a subcategory. To make this clear let’s look at the Microsoft Development category:
It is quite obvious that C# and VB.NET (and something starting with .NE) are subcategories that contain courses. So the number of subcategories in each category differs.
How do you cope with that using the Pivot control?
The answer appears to be easy: the Pivot control is an ItemsControl (a bit of a ListBox) and for each item in the DataContext a PivotItem will be created. In Xaml:
1: <controls:Pivot Title="{Binding Path=LocalizedResources.ApplicationName, Source={StaticResource LocalizedContent}}"2: x:Name="categoryPivot"3: ItemsSource="{Binding Subcategories}">4: <controls:Pivot.HeaderTemplate>5: <DataTemplate>6: <TextBlock Text="{Binding Name}" />7: </DataTemplate>8: </controls:Pivot.HeaderTemplate>9: <controls:Pivot.ItemTemplate>10: <DataTemplate>11: <ListBox ItemsSource="{Binding Courses}"12: ItemTemplate="{StaticResource courseNameTemplate}"13: SelectionChanged="coursesList_SelectionChanged"/>14: </DataTemplate>15: </controls:Pivot.ItemTemplate>16: </controls:Pivot>In line 3 you can see  the binding that tells the Pivot control where to find the items that each should be displayed in a Pivot Item. So the Pivot control will be bound to a Category and the Subcategories property of the Category will magically cause multiple Pivot Items to appear.
In lines 4 to 8 I added a DataTemplate to specify how each Pivot Item’s header should be displayed and what data should be shown. The binding in line 6 is relative to the subcategory shown in a single PivotItem.
Lines 9 to 15 define the template that specifies what should be shown on each Pivot Item. The binding in line 11 is relative to the subcategory and in this case binds the Courses property of the subcategory to the ListBox on the Pivot Item.
So far so good. And if all the data is present when the Pivot control is being loaded into memory, attached to the visual tree and rendered all will be fine.
But.
We have a lot of courses so I only retrieve the subcategories and their courses when they are needed (of course I cache them after that). In code it looks like this:
1: public partial class Subcategories : PageBase2: {3: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)4: {5: base.OnNavigatedTo(e);
6: if (IsNewInstance && this.NavigationContext.QueryString.ContainsKey("category"))7: {8: string category = this.NavigationContext.QueryString["category"];9: if (!String.IsNullOrEmpty(category))
10: {11: int categoryID;
12: if (Int32.TryParse(category, out categoryID))13: {14: ((App)App.Current).CoursesContext.LoadSubcategoriesAsync(categoryID);15: }16: }17: }18: }19: }In line 6 there is a quick check if this instance of the page has not been loaded with data before (IsNewInstance property that is defined in the PageBase class) and (if so) continues to asynchronously load the data from a web service in line 14.
The LoadSubcategoriesAsync method fills an already existing collection with subcategories. The DataContext of the page is set to the parent of the collection; a MainCategory  object.
I expected the Pivot control to be loaded with Pivot items which did happen but the first Pivot Item was blank. The header showed “C#” but the Content was clear:
It took more than a little web searching to get to the bottom of this error. Even my beloved StackOverflow did not have a solution that fitted me.
This post helped me to solve the problem. So I changed my code a little bit: reset the DataContext before updating the collection (line 16) and re-binding it after the update (line 27):
1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)2: {3: base.OnNavigatedTo(e);
4: if (IsNewInstance && this.NavigationContext.QueryString.ContainsKey("category"))5: {6: string category = this.NavigationContext.QueryString["category"];7: if (!String.IsNullOrEmpty(category))
8: {9: int mainCategoryID;
10: if (Int32.TryParse(category, out mainCategoryID))11: {12: ((App)App.Current).CourseContext.LoadSubcategoriesCompleted += CourseContext_LoadSubcategoriesCompleted;13: ((App)App.Current).CourseContext.LoadSubcategoriesAsync(mainCategoryID);14:15: // bugfix for the pivot control not showing the first pivotitem when binding - Part 1
16: this.DataContext = null;17: }18: }19: }20: }21:22: void CourseContext_LoadSubcategoriesCompleted(object sender, EventArgs e)23: {24: ((App)App.Current).CourseContext.LoadSubcategoriesCompleted -= CourseContext_LoadSubcategoriesCompleted;25:26: // bugfix for the pivot control not showing the first pivotitem when binding - Part 2
27: Dispatcher.BeginInvoke(() =>28: {29: int mainCategoryID = Int32.Parse(this.NavigationContext.QueryString["category"]);30: this.DataContext = ((App)App.Current).CourseContext.MainCategories.FirstOrDefault(mc => mc.ID == mainCategoryID);
31: });32: }The only concern I still have is the sheer amount of Pivot Items when a category has many subcategories. Not because of the amount of data but because the user gets lost swiping from subcategory to subcategory.