Data Binding Pivot Control WP7 MVVM

3 minute read

by Peter Daukintis

Ok, having had access to the official WP7 Pivot control in the RTM Tools for Windows Phone 7 for a few days I thought it was time to explore and particularly in it’s data binding capabilities. I knocked up a quick and dirty example for a forum response but felt the need to explore a bit further…

The example consisted of binding the Pivot control to a homogenous view model collection. All well and good but in real life the requirements would be more like handling a heterogenous data source and binding the page headers, etc..

So, we start with the control bound to a PageCollection

<controls:Pivot ItemsSource="{Binding PageCollection}" 
                        Title="MY APPLICATION" 
                        HorizontalContentAlignment="Stretch" 
                        VerticalContentAlignment="Stretch" 
                        >

 

The PageCollection is defined as follows:

public class MainViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<PageViewModel> _pageCollection;

        public ObservableCollection<PageViewModel> PageCollection
        {
            get { return _pageCollection; }
            set
            {
                if (_pageCollection != value)
                {
                    _pageCollection = value;
                    NotifyPropertyChanged("PageCollection");
                }
            }
        }

 

However, we are going to want to have differing objects in this collection so I will also create some PageViewModel derived types:

public class PageViewModel : INotifyPropertyChanged
    {
        private string _titleText;

        public string TitleText
        {
            get { return _titleText; }
            set
            {
                if (_titleText != value)
                {
                    _titleText = value;
                    OnPropertyChanged("TitleText");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class ListPageViewModel : PageViewModel
    {
        private ObservableCollection<string> _contextCollection;

        public ObservableCollection<string> ContextCollection
        {
            get { return _contextCollection; }
            set
            {
                if (_contextCollection != value)
                {
                    _contextCollection = value;
                    OnPropertyChanged("ContextCollection");
                }
            }
        }
    }

    public class ImagePageViewModel : PageViewModel
    {
        private Uri _uri;

        public Uri Uri
        {
            get { return _uri; }
            set
            {
                if (_uri != value)
                {
                    _uri = value;
                    OnPropertyChanged("Uri");
                }
            }
        }
    }

One has a list of strings and another has a Uri which will define the location of an image.

In my main view model constructor I will create the runtime data (consisting of one instance of each type)

        public MainViewModel()
        {
            PageCollection = new ObservableCollection<PageViewModel>
                                 {
                                     new ListPageViewModel
                                         {
                                             TitleText = "List Page",
                                             ContextCollection = new ObservableCollection<string> { "item1", "item2" }
                                         },
                                     new ImagePageViewModel
                                         {
                                             TitleText = "Image Page",
                                             Uri = new Uri("Images/pic.png", UriKind.Relative)
                                         }
                                 };
        }

And to bind the page header I’ll add a header template

<controls:Pivot ItemsSource="{Binding PageCollection}" 
                        Title="MY APPLICATION" 
                        HorizontalContentAlignment="Stretch" 
                        VerticalContentAlignment="Stretch" 
                        >
            <controls:Pivot.HeaderTemplate>
                <DataTemplate>
                    <Grid x:Name="grid">
                        <TextBlock TextWrapping="Wrap"
                                   Text="{Binding TitleText}"
                                   d:LayoutOverrides="Width, Height" />
                    </Grid>
                </DataTemplate>

            </controls:Pivot.HeaderTemplate>
        </controls:Pivot>

 

and we’re also going to need a data template selector to allow us to select a data template at runtime depending on the type of the instance we are binding. This is a little tricky since this is not natively supported in Silverlight but fortunately not too hard to implement. I drew upon this http://geekswithblogs.net/tkokke/archive/2009/09/28/datatemplateselector-in-silverlight.aspx and http://forums.silverlight.net/forums/t/190667.aspx to implement the following (see those posts for explanation):

   public static class Extensions
    {
        public static T FindResource<T>(this DependencyObject initial, string key) where T : DependencyObject
        {
            DependencyObject current = initial;

            while (current != null)
            {
                if (current is FrameworkElement)
                {
                    if ((current as FrameworkElement).Resources.Contains(key))
                    {
                        return (T)(current as FrameworkElement).Resources[key];
                    }
                }

                current = VisualTreeHelper.GetParent(current);
            }

            if (Application.Current.Resources.Contains(key))
            {
                return (T)Application.Current.Resources[key];
            }

            return default(T);
        }
    }

    public class DataTemplateSelector : ContentControl
    {
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            ContentTemplate = this.FindResource<DataTemplate>(newContent.GetType().FullName);
        }
    }

which then allowed me to declare the ItemTemplate for the Pivot control like this:

            <controls:Pivot.ItemTemplate>
                <DataTemplate>
                    <WindowsPhonePivotApplication1:DataTemplateSelector Content="{Binding}" />
                </DataTemplate>
            </controls:Pivot.ItemTemplate>

and to add the data templates (one for each derived type) like this:

    <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Key="WindowsPhonePivotApplication1.ViewModels.ListPageViewModel">
            <ListBox ItemsSource="{Binding ContextCollection}">
                
            </ListBox>
        </DataTemplate>
        <DataTemplate x:Key="WindowsPhonePivotApplication1.ViewModels.ImagePageViewModel">
            <Image Source="{Binding Uri}"></Image>
        </DataTemplate>

and that was it – here is the result…

pivotpage1       pivotpage2

Technorati Tags: ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Windows Live Tags: Pivot,Control,MVVM,Beta,data,example,forum,response,collection,life,requirements,PageCollection,ItemsSource,Title,APPLICATION,HorizontalContentAlignment,Stretch,VerticalContentAlignment,MainViewModel,ObservableCollection,PageViewModel,_pageCollection,objects,_titleText,TitleText,event,PropertyChangedEventHandler,PropertyChangedEventArgs,ListPageViewModel,_contextCollection,ContextCollection,ImagePageViewModel,_uri,location,image,instance,List,Page,Images,Relative,header,template,HeaderTemplate,DataTemplate,Grid,Name,TextBlock,Wrap,Text,LayoutOverrides,Width,archive,explanation,FindResource,DependencyObject,FrameworkElement,Resources,VisualTreeHelper,GetParent,Current,DataTemplateSelector,ContentControl,ContentTemplate,GetType,FullName,ItemTemplate,Content,PhoneApplicationPage,ViewModels,ListBox,Source,result,forums,Extensions,templates,propertyName,runtime,aspx

Comments