20
Sep
10

Data Binding Pivot Control WP7 MVVM

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


16 Responses to “Data Binding Pivot Control WP7 MVVM”


  1. 1 Drew
    November 27, 2010 at 9:12 am

    Hi Peter,

    Any chance you can share a sample project for this? I’m having a bit of trouble getting this working.

    Thanks!
    Drew

  2. 3 c0x3y
    January 17, 2011 at 5:56 pm

    I have managed to bind a collection to a pivot control but hooking up an ICommand to dynamically created pivot items does not appear to be working. I’m using MVVM light.

    The ICommand connects successfully if the pivot items are static.

    Any ideas?

  3. 5 ryantomlinson
    January 25, 2011 at 3:47 pm

    Hi Peter,

    This is great. However, I am looking to also have a dynamic HeaderTemplate as well as the ItemTemplate. Unfortunately this doesn’t work in the Header when using the DataTemplateSelector implementation that you wrote.

    Any ideas on how to get this to work?

    Kind regards,

    Ryan

  4. 6 David
    January 26, 2011 at 5:20 pm

    I’m totally blocked by a pivot bindable via a list.
    Please could you take a look at this code?

    I have this XAML Page code

    and its c# code

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Resources;
    using System.Windows.Media.Imaging;
    using Microsoft.Phone.Controls;
    using System.Collections.ObjectModel;

    using System.ComponentModel;
    using System.Windows.Data;
    using System.Globalization;

    namespace PanoramaAleph
    {
    public partial class Lista_Resultados : PhoneApplicationPage
    {
    private static ListaCartasViewModel _lcvmResultados = null;

    ///
    /// A static ViewModel used by the views to bind against.
    ///
    /// The MainViewModel object.
    public static ListaCartasViewModel lcvmResultados
    {
    get
    {
    // Delay creation of the view model until necessary
    if (_lcvmResultados == null)
    _lcvmResultados = new ListaCartasViewModel();

    return _lcvmResultados;
    }
    }

    public Lista_Resultados()
    {
    InitializeComponent();

    this.DataContext = lcvmResultados;
    PivotCartas.DataContext = lcvmResultados;

    this.Loaded += new RoutedEventHandler(MainPage_Loaded);

    }

    // Load data for the ViewModel Items
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
    if (!lcvmResultados.IsDataLoaded)
    lcvmResultados.LoadData(Carta.CartasConsultadas);
    }
    }
    }

    The listacartasViewModel is the next class:

    using System;
    using System.Net;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;

    namespace PanoramaAleph
    {
    public class ListaCartasViewModel : INotifyPropertyChanged
    {
    private ObservableCollection _Lista;

    public ObservableCollection Lista
    {
    get {return _Lista;}
    set
    {
    if (_Lista != value)
    {
    _Lista = value;
    NotifyPropertyChanged(“Lista”);
    }
    }
    }

    public bool IsDataLoaded
    {
    get;
    private set;
    }

    ///
    /// Creates and adds a few ItemViewModel objects into the Items collection.
    ///
    public void LoadData(List elista)
    {
    // Sample data; replace with real data
    this.Lista = new ObservableCollection();
    elista.ForEach((elemento) => this.Lista.Add(new CartaViewModel { Nombre = elemento.ToString() }));
    this.IsDataLoaded = true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName)
    {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (null != handler)
    {
    handler(this, new PropertyChangedEventArgs(propertyName));
    }
    }
    }
    }

    And the CartaViewModel

    using System;
    using System.Net;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;

    namespace PanoramaAleph
    {
    public class CartaViewModel : INotifyPropertyChanged
    {
    private string _Nombre;

    public string Nombre
    {
    get { return _Nombre; }
    set
    {
    if (_Nombre != value)
    {
    _Nombre = value;
    NotifyPropertyChanged(“Nombre”);
    }
    }
    }
    /*
    public override string ToString()
    {
    return Nombre;
    }
    */
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName)
    {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (null != handler)
    {
    handler(this, new PropertyChangedEventArgs(propertyName));
    }
    }
    }
    }

    First of all. This page isn’t the main page, the main page is a panorama and as soon as I click an appbar button this page loads.
    ListacartasViewModel isn’t on the MainViewModel class. Is it a problem? or as soon as its implemented by the INotifyPropertyChanged intercace there is no problem?

    the page Binds correctly the list of items to the static lcvmResultados but I must set de DataContext by code on the page’s constructor but how cound I set it in the XAML?, if I set DataContext= “{Binding lcvmResultados}” doesn’t work

    Binding the list of items by code, it works. It creates as many pivot items as items in my list, but the list elements doesn’t bind its properties correctly. In the template Im binding de Nombre property of CartaViewModel but it doesn’t work, it shows “PanoramaAleph.CartaViewModel” the name of que list’s item class, but not its Nombre property.
    If I override the TosTring() method of the CartaViewModel, the “Nombre” bindings shows the Class ToString() result, so it can’t find the Nombre property. Am I doing something wrong?

    thank in advance

  5. 7 David
    January 26, 2011 at 5:24 pm

    Sorry, previous code are crazy, XAML don’t shows!!!

    again…

    I must link you to an apphub forum post to see correctly the cample code…

    http://forums.create.msdn.com/forums/p/73593/449107.aspx#449107

  6. 8 David
    January 28, 2011 at 7:31 am

    After three days of frustrating intense Investigation y solved my 2 problems

    I someone had same problem see the link to the apphub forum abobe

  7. 9 Massimo
    March 3, 2011 at 11:22 am

    In the source code I downloaded there is a dynamic Panorama not a Pivot.

    Do you have the Pivot one? where I can find it?

    Thanks

  8. 11 Massimo
    March 3, 2011 at 10:27 pm

    Thank you for the help!

    I’m trying to convert it in VB but I miserably fail :(

    I have this structure
    Favorite
    – Collection of Bouquet

    Bouquet
    – Collection of Channel
    – Name

    Channel
    – ID
    – Name

    I would like to bind the collection of Bouquet to the Pages (showing the Bouquet.name) and the Collection of channels to the listbox showing on two lines Channel.ID and Channel.name

    All of this is (for now) just text

    I just and up with a black page :(

    Last but not least all the collections are dynamicaly loaded from a server with Async HTTPRequest (but this works :))

    Your structure should work but… If you have any hint is more than welcome
    Thank you

  9. 12 nani
    March 9, 2011 at 9:56 pm

    In this project, the datatemplate code is center aligned. Any idea of how to make it look normal i.e. when you actually have a list view in a pivot control.

  10. 13 Mthyrp
    September 2, 2011 at 11:35 am

    There is another way to achieve the same thing. Start with Pivot project, look at the ItemViewModel.cs, add your own ‘MyObject’ViewModel.cs class to the project. Add ‘NotifyPropertyChanged’ logic in this class. Add whatever member functions / parms you need. Update LoadData in the MainViewModel.cs class. Add the controls:PivotItem to the xaml in the MainPage… u are all set!

    Mthyrp.

  11. October 4, 2012 at 3:21 pm

    Question, If like in your example I use a listbox to view a list, the listbox doesnt fill the entire content of the pivotitem. Just the content it needs. How to get the template to fill the complete pivot item? This can most simply be tested by using a button instead of an image/listbox. You will see the button sitting in the middle of the page without using up anymore space then it needs…

    How to get (for example) the button to fill up the entire screen like it would if I put a button in a normal Pivotitem (without datatemplates or binding)


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: