XAML

DataTemplateSelector for Xamarin.Forms

December 31, 2014 Coding 4 comments , ,

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.

ContentPresenter for Xamarin.Forms

December 31, 2014 Coding 4 comments , ,

While working on a new Xamarin Forms project, I came across the need for a ContentPresenter ala Microsoft XAML. The basic idea is to use a placeholder for a piece of content (usually a ViewModel) and then supply a DataTemplate to control how it should be displayed.

A simple example might look like this:


<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <controls:ContentPresenter Grid.Row="1"
               BindingContext="{Binding MyModel, Source={StaticResource Locator}}" 
               ItemTemplate="{StaticResource MyModelTemplate}"
               />

</Grid>

This goes back to some of the more “classic” MVVM patterns where we want to make our display more componentized.

In Xamarin Forms Today (v1.3.0), if you want to create a custom component, you can derive from ContentView and go to town. Add your widgets either with XAML or in code. Many times though, we don’t really need to create a new class/view directly. A DataTemplate is sufficient as we can bind to the ViewModel and use Commands to take action. These are the so-called “zero code-behind” views.

In this case, a ContentPresenter is all we need – set the BindingContext to your ViewModel and create/bind a DataTemplate. New to Forms 1.3, you can now put resources in the Application-level, so you can more easily share those instances.

Here’s the complete code, sans-usings, to implement your own. This can go either in a Shared code or PCL:

public class ContentPresenter : ContentView
{
    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ContentPresenter), null, propertyChanged: OnItemTemplateChanged);

    private static void OnItemTemplateChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        var cp = (ContentPresenter)bindable; 

        var template = cp.ItemTemplate;
        if (template != null)
        {
            var content = (View)template.CreateContent();
            cp.Content = content;
        }
        else
        {
            cp.Content = null;
        }
    }

    public DataTemplate ItemTemplate
    {
        get
        {
            return (DataTemplate)GetValue(ItemTemplateProperty);
        }
        set
        {
            SetValue(ItemTemplateProperty, value);
        }
    }
}

If you want, you can get fancy with Triggers and change the ItemTemplate to vary based on whatever conditions you want.