Next up in the saga of Xamarin Forms: DataTemplateSelector‘s.

For people coming into Xamarin Forms from Microsoft XAML, it’s easy to see many gaps. Xamarin Forms is still young, but there are some workarounds. I’ve already blogged about how to implement a ContentPresenter, next up is the DataTemplateSelector.

Supposed you want to put a list of items into a ListView but use a different DataTemplate for some of the items? In Microsoft XAML, you could set the ItemTemplateSelector property to a custom DataTemplateSelector and you’d be all set. Sadly, this is not yet available within Xamarin Forms, but we can create our own.

To see the specific usage/examples of how to implement a custom DataTemplateSelector instance, please refer to MSDN or your favorite search engine. Usage will be exactly the same*

There are a few classes we’ll need:

  1. DataTemplateSelector.cs
    public class DataTemplateSelector
    {
        public virtual DataTemplate SelectTemplate(object item, BindableObject container)
        {
            return null;
        }
    }
    
  2. ExtendedlistView.cs
    public class ExtendedListView : ListView
    {
        public static readonly BindableProperty ItemTemplateSelectorProperty = BindableProperty.Create("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(ExtendedListView), null, propertyChanged: OnDataTemplateSelectorChanged);
        private DataTemplateSelector currentItemSelector;
        private static void OnDataTemplateSelectorChanged(BindableObject bindable, object oldvalue, object newvalue)
        {
            ((ExtendedListView)bindable).OnDataTemplateSelectorChanged((DataTemplateSelector)oldvalue, (DataTemplateSelector)newvalue);
        }            
        protected virtual void OnDataTemplateSelectorChanged(DataTemplateSelector oldValue, DataTemplateSelector newValue)
        {
            // check to see we don't have an ItemTemplate set
            if (ItemTemplate != null && newValue != null)
                throw new ArgumentException("Cannot set both ItemTemplate and ItemTemplateSelector", "ItemTemplateSelector");
            currentItemSelector = newValue;
        }
        protected override Cell CreateDefault(object item)
        {
            if (currentItemSelector != null)
            {
                var template = currentItemSelector.SelectTemplate(item, this);
                if (template != null)
                {
                    var templateInstance = template.CreateContent();
                    // see if it's a view or a cell
                    var templateView = templateInstance as View;
                    var templateCell = templateInstance as Cell;
                    if (templateView == null && templateCell == null)
                        throw new InvalidOperationException("DataTemplate must be either a Cell or a View");
                    if (templateView != null) // we got a view, wrap in a cell
                        templateCell = new ViewCell { View = templateView };
                    return templateCell;
                }
            }
            return base.CreateDefault(item);
        }
        public DataTemplateSelector ItemTemplateSelector
        {
            get
            {
                return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty);
            }
            set
            {
                SetValue(ItemTemplateSelectorProperty, value);
            }
        }
    }
    

With these two pieces, you can now create your own selector. Suppose you have an RSS Feed that you want to display an alternate template every 4 items. For this example, we’ll assume that each item in the list has an index attached.

We’ll start with our custom selector:

public class NewsItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate NewsItem { get; set; }
    public DataTemplate NewsItemLarge { get; set; }

    public override DataTemplate SelectTemplate(object item, BindableObject container)
    {
        // must have IListItems
        var li = (IListItem)item;

        if (li.Index == 0 || li.Index%4 == 0)
        {
            return NewsItemLarge;
        }
        return NewsItem;
    }
}

Now in the App.xaml, lets add our templates. (Aside: See this example for how to use XAML for your App class allowing you to easily use XAML shared resources.)

  <DataTemplate x:Key="NewsItem">
    <Grid MinimumHeightRequest="320">
      <Grid.RowDefinitions>
        <RowDefinition Height="120"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="140"/>
        <ColumnDefinition Width="*"/>
      </Grid.ColumnDefinitions>
      <Image Grid.Row="0" Grid.Column="0" Source="{Binding ImageUrl}" HeightRequest="110" Aspect="AspectFill" VerticalOptions="Start" />
      <StackLayout Grid.Row="0" Grid.Column="1" Padding="16, 0, 10, 8">
        <Label Grid.Row="0" Grid.ColumnSpan="2" VerticalOptions="Center" Text="{Binding Title}" />
        <ContentView HeightRequest="4" /><!-- Hack for margin -->
        <Label Grid.Row="1" Grid.Column="1" VerticalOptions="Start" Text="{Binding Summary}" />
      </StackLayout>
    </Grid>
  </DataTemplate>

  <DataTemplate x:Key="NewsItemLarge">
    <Grid MinimumHeightRequest="520">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="120" />
      </Grid.RowDefinitions>
      <Image Grid.Row="0" Source="{Binding ImageUrl}" HeightRequest="400" Aspect="AspectFill" VerticalOptions="Start" />
      <StackLayout Grid.Row="1" Padding="16, 0, 10, 8">
        <Label Grid.Row="0" Grid.ColumnSpan="2" VerticalOptions="Center" Text="{Binding Title}" />
        <ContentView HeightRequest="4" />
        <!-- Hack for margin -->
        <Label Grid.Row="1" Grid.Column="1" Style="{StaticResource ItemSubheaderText}" VerticalOptions="Start" Text="{Binding Summary}" />
      </StackLayout>
    </Grid>
  </DataTemplate> 

Here’s the custom DataTemplateSelector:

public class NewsItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate NewsItem { get; set; }
    public DataTemplate NewsItemLarge { get; set; }

    public override DataTemplate SelectTemplate(object item, BindableObject container)
    {
        // must have IListItems
        var li = (IListItem)item;

        if (li.Index == 0 || li.Index%4 == 0)
        {
            return NewsItemLarge;
        }
        return NewsItem;
    }
}

Finally, we’ll add an instance of the DataTemplateSelector to the App.xaml:


<controls:NewsItemTemplateSelector 
  x:Key="NewsItemTemplateSelector"
  NewsItem="{StaticResource NewsItem}"
  NewsItemLarge="{StaticResource NewsItemLarge}" 
/>

Later on, when we want to use it, we can use it with our ExtendedListView:


<controls:ExtendedListView 
  ItemsSource="{Binding Items}"
  ItemTemplateSelector="{StaticResource NewsItemTemplateSelector}">
</controls:ExtendedListView>

When rendered, the first item will have the large image template and then every fourth item will too.

BONUS: You can use either View or Cell-derived types in your DataTemplates as the control will automatically wrap Views into a ViewCell.

Enjoy!

*probably.