If you spend some time developing Windows Store apps, you will notice that item templates play a major role since we have to use them to display data in many controls (most used ones are GridView and ListView). They give us total control over how we should present information to user, and if designed right, they can single-handedly make our app quite beautiful.
However, as you progress, you will also notice that sometimes just creating an item template is not enough: You may need to customize them based on the data you have. For this purpose, we can do several things that I’ve mentioned in my blog, such as using IValueConverter to bind your data to nearly every property, or creating a custom user control and using it in the item template. Still, however, if the amount of customization you need to do in the item template is way too much, you can instead decide to create several different item templates, and then use them at the same time via the DataTemplateSelector interface.
If this looks interesting to you, read on and we will create an app that uses the DataTemplateSelector interface. 🙂
We make our humble beginning by creating a blank Windows Store app in Visual Studio. Our app will display a list of TV series, which will have differently sized item templates as seen in the screenshot above.
First, we’ll create a TVSeries class in our MainPage.xaml.cs:
public class TVSeries { public string Name { get; set; } public string Description { get; set; } public string Genre { get; set; } public string Image { get; set; } public string Seasons { get; set; } public int Width { get; set; } //Will be used for custom gridview, read next paragraph public int Height { get; set; } //Will be used for custom gridview, read next paragraph }
Now, there are two possibilities:
1) If the sizes of your item templates won’t change, you can just directly jump to the part where we define our template selector.
2) If the sizes of your item templates will change (it will, in our example), then we have a problem. If we create three item templates with different sizes, GridView will create the items using the biggest item template and the smaller items will look bad, like this:
To solve this, we need to create our own custom GridView class and add a few modifications. (I found this solution from here.)
To create our custom GridView class, we’ll add a new class called VariableSizedGridView.cs by right-clicking our project in Solution Explorer and selecting Add->Class…, and then fill it like this:
using Windows.UI.Xaml.Controls;
public class VariableSizedGridView : GridView { protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item) { TVSeries tvSeries = item as TVSeries; element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, tvSeries.Width); element.SetValue(VariableSizedWrapGrid.RowSpanProperty, tvSeries.Height); base.PrepareContainerForItemOverride(element, item); } }
Here, we read our TVSeries object and then change the RowSpan and ColumnSpan properties of the ItemsPanel template our custom GridView will have, which will create our item in the size we desire. Let me explain a little more.
Our app will have the three following item templates: Large (400×400), Medium (400×200) and Small(200×200). In the ItemsPanel template, which you will see a few steps later, we will define the item’s size by the number of rows and columns it will span. For simplicity, both our row and column sizes will be 200, which will result in:
Large template: Width = 2, Height = 2
Medium template: Width = 2, Height = 1
Small template: Width = 1, Height = 1
You’ll understand it better when you see how it works in the example.
Ok, let’s just keep this for now and continue. Open up App.xaml, and add the following item templates (notice that I’ve given them sizes 10 pixels lower, this is to give some space for itemspanel):
<DataTemplate x:Key="TVSeriesLargeSizeTemplate"> <Grid Background="#FF8E2929" Width="390" Height="390"> <Image Height="180" Margin="10,10,10,0" VerticalAlignment="Top" Source="{Binding Image}" Stretch="UniformToFill"/> <TextBlock HorizontalAlignment="Left" Margin="10,195,0,0" Text="{Binding Name}" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="22" FontWeight="Light" FontStyle="Italic"/> <TextBlock HorizontalAlignment="Left" Margin="10,229,0,0" Text="{Binding Seasons}" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="14" FontWeight="Light" FontStyle="Italic"/> <TextBlock HorizontalAlignment="Left" Margin="10,253,0,0" Text="{Binding Genre}" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="14" FontWeight="Light" FontStyle="Italic"/> <TextBlock Margin="10,277,10,0" TextWrapping="Wrap" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="14" Height="103" Text="{Binding Description}" TextTrimming="WordEllipsis"/> </Grid> </DataTemplate> <DataTemplate x:Key="TVSeriesMediumSizeTemplate"> <Grid Background="#FF8E2929" Width="390" Height="190"> <Image HorizontalAlignment="Left" Margin="10,10,0,10" Width="150" Source="{Binding Image}" Stretch="UniformToFill"/> <TextBlock HorizontalAlignment="Left" Margin="170,10,0,0" Text="{Binding Name}" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="18" FontWeight="Light" FontStyle="Italic"/> <TextBlock HorizontalAlignment="Left" Margin="170,39,0,0" Text="{Binding Seasons}" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="14" FontWeight="Light" FontStyle="Italic"/> <TextBlock HorizontalAlignment="Left" Margin="170,63,0,0" Text="{Binding Genre}" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="14" FontWeight="Light" FontStyle="Italic"/> </Grid> </DataTemplate> <DataTemplate x:Key="TVSeriesSmallSizeTemplate"> <Grid Background="#FF8E2929" Width="190" Height="190"> <Image HorizontalAlignment="Left" Height="140" Margin="10,10,0,0" VerticalAlignment="Top" Width="170" Source="{Binding Image}" Stretch="UniformToFill"/> <TextBlock Margin="10,156,10,0" Text="{Binding Name}" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="16" FontWeight="Light" FontStyle="Italic" TextTrimming="WordEllipsis"/> </Grid> </DataTemplate>
Now, it is time to create our template selector. Add another class to our project, called TVSeriesTemplateSelector.cs and fill it with the code below:
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml;
public class TVSeriesTemplateSelector : DataTemplateSelector { public DataTemplate TVSeriesSmallSizeTemplate { get; set; } public DataTemplate TVSeriesMediumSizeTemplate { get; set; } public DataTemplate TVSeriesLargeSizeTemplate { get; set; } protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { TVSeries tvSeries = item as TVSeries; if (tvSeries.Width == 1) { return TVSeriesSmallSizeTemplate; } else if (tvSeries.Width == 2 && tvSeries.Height == 1) { return TVSeriesMediumSizeTemplate; } else { return TVSeriesLargeSizeTemplate; } } }
As you can see, we return the desired template based on the Width and Height values of the TVSeries object. Next, in MainPage.xaml, we will use this template selector as shown below (build the project in order to see our template selector in MainPage.xaml file):
<Page x:Class="TemplateSelectorApp.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:TemplateSelectorApp" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:common="using:TemplateSelectorApp" mc:Ignorable="d"> <Page.Resources> <common:TVSeriesTemplateSelector x:Key="TVSeriesTemplateSelector" TVSeriesLargeSizeTemplate="{StaticResource TVSeriesLargeSizeTemplate}" TVSeriesMediumSizeTemplate="{StaticResource TVSeriesMediumSizeTemplate}" TVSeriesSmallSizeTemplate="{StaticResource TVSeriesSmallSizeTemplate}"/> </Page.Resources> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <local:VariableSizedGridView x:Name="TVSeriesGridView" Margin="100,100,0,50" ItemTemplateSelector="{StaticResource TVSeriesTemplateSelector}"> <GridView.ItemsPanel> <ItemsPanelTemplate> <VariableSizedWrapGrid Orientation="Vertical" ItemHeight="200" ItemWidth="200"/> </ItemsPanelTemplate> </GridView.ItemsPanel> </local:VariableSizedGridView> </Grid> </Page>
Notice that we’ve added a reference to our project at first, with “xmlns:common=”using:TemplateSelectorApp”. Then we’ve defined which templates the template selector should use, and finally also defined our VariableSizedGridView to use the template selector. The VariableSizedWrapGrid in the ItemsPanelTemplate will allow us to create our items with the correct sizes.
Lastly, we will create some example data in MainPage.xaml.cs and set them as the items source of our gridview:
public MainPage() { this.InitializeComponent(); this.Loaded += MainPage_Loaded; } void MainPage_Loaded(object sender, RoutedEventArgs e) { List<TVSeries> tvSeriesList = new List<TVSeries>(); tvSeriesList.Add(new TVSeries() { Name = "Supernatural", Seasons = "9 Seasons", Genre = "Supernatural Drama", Width = 2, Height = 2, Image = "http://images2.fanpop.com/images/photos/2700000/Supernatural-supernatural-2784812-1280-960.jpg", Description = "Supernatural is an American supernatural drama television series, created by Eric Kripke, which was first broadcast on September 13, 2005, on The WB, and is now part of The CW's lineup. Starring Jared Padalecki as Sam Winchester, and Jensen Ackles as Dean Winchester, the series follows these two brothers as they hunt demons, ghosts, monsters and other supernatural beings in the world. The series is produced by Warner Bros. Television, in association with Wonderland Sound and Vision. The current executive producers are Eric Kripke, McG, and Robert Singer." }); tvSeriesList.Add(new TVSeries() { Name = "Fringe", Seasons = "5 Seasons", Genre = "Science Fiction", Width = 2, Height = 1, Image = "http://tvafterdarkonline.com/wp-content/uploads/2012/06/FRINGE-Sunday-Hall-H-WBSDCC-2012.jpg" }); tvSeriesList.Add(new TVSeries() { Name = "Chuck", Seasons = "5 Seasons", Genre = "Action Comedy", Width = 2, Height = 1, Image = "http://zinemaniacos.files.wordpress.com/2012/06/chuck.jpg" }); tvSeriesList.Add(new TVSeries() { Name = "Falling Skies", Width = 1, Height = 1, Image = "http://2.bp.blogspot.com/-cVgdAr1EFQA/TgJpY5cciTI/AAAAAAAABDg/f4MetFkSeAo/s1600/tv_falling_skies04.jpg" }); tvSeriesList.Add(new TVSeries() { Name = "Revolution", Width = 1, Height = 1, Seasons = "2 Seasons", Image = "http://www.hdwallpapers.in/walls/revolution_tv_series-wide.jpg" }); tvSeriesList.Add(new TVSeries() { Name = "Battlestar Galactica", Seasons = "4 Seasons", Genre = "Science Fiction", Width = 2, Height = 1, Image = "http://image.allmusic.com/00/agg/cov200/drg200/g283/g28358peyd2.jpg" }); tvSeriesList.Add(new TVSeries() { Name = "House MD", Seasons = "8 Seasons", Genre = "Dramedy, Mystery", Width = 2, Height = 2, Image = "http://www.kotusozluk.com/img/2011/04/house-md_18373.jpg", Description = "House (also known as House, M.D.) was an American television medical drama that originally ran on the Fox network for eight seasons, from November 16, 2004 to May 21, 2012. The show's main character is Dr. Gregory House (Hugh Laurie), a drug-addicted, unconventional, misanthropic medical genius who leads a team of diagnosticians at the fictional Princeton–Plainsboro Teaching Hospital (PPTH) in New Jersey." }); tvSeriesList.Add(new TVSeries() { Name = "Sherlock", Seasons = "2 Seasons", Genre = "Crime Drama", Width = 2, Height = 1, Image = "http://skyethelimit.files.wordpress.com/2012/02/us-sherlock-dvd-cmyk.jpg" }); TVSeriesGridView.ItemsSource = tvSeriesList; }
That’s it. If you run the app now, we’ll see the following:
Keep in mind that the order of your items are important. GridView will try putting an item vertically first, and if there is not enough space then it will put horizontally. You can play with it to see how it affects our results, like these:
You can get the source code of the example here.
Thank you for reading, I hope it’s been helpful. 🙂
I have a requirement where I need to bind 3 different models to the same results grid view based on search criteria. The headers also need to be changed as the models have different properties. What would you suggest would be best approach to this?