Every now and then, I receive questions about how to display different types of items in a ListView
, FlipView
, Xamarin.Forms CarouselView
, etc. For example, imagine a list of files the user can choose from. The list will mainly contain file names, but for image files we’d like to display the actual image file itself (or some kind of pre-rendered thumbnail image) instead of the file name.
As an easy solution we could just define a label and an image inside each list item, and hide one of them depending on whether there is an image to show or not. This will work perfectly fine for small amounts of list items, but for longer lists defining the double amount of visual controls than is actually needed (and will even be displayed) seems unnecessary and might leed to performance drawbacks. I don’t say this is a bad solution per se, as it is useful in some scenarios (e.g., it allows animating the fade in / fade out of the different types of controls within each list item), but I’d like to point you to the use of DataTemplateSelectors
as an alternative solution (by the way, this approach works similarly in all XAML dialects, meaning you can use it even in WPF and Xamarin.Forms, but for this example let’s focus on a UWP Windows App):
We all know that for binding an arbitrary number of items to a UWP ListView
(or any other ItemsControl
-like control), we can declare a DataTemplate
that defines the visual appearance of each list item. This way, the actual item objects don’t need to contain any display-related information (we can actually use simple POCOs):
<ListView ItemsSource="{Binding Files}"> <ListView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding FileName}"/> </DataTemplate> </ListView.ItemTemplate> </ListView>
One thing that is quite obvious but anybody hardly thinks about is that the DataTemplate
can even be specified outside the ListView
(e.g., within the page’s or the App’s global resources), and referenced as static resource:
<Page.Resource> <DataTemplate x:Key="FileListItemTemplate"> <TextBlock Text="{Binding FileName}"/> </DataTemplate> </Page.Resource> <ListView ItemsSource="{Binding Files}" ItemTemplate="{StaticResource FileListItemTemplate}" />
This didn’t change anything in the way the list is displayed (which is why hardly anyone does it in practice for such a simple list), but it is the first step towards our goal of declaring multiple different DataTemplates
to be used in the same list!
Let’s analyze this statement in detail: On the one hand, obviously nothing keeps us from defining several data templates within the page’s resources block – so if image files shall be displayed differently from all other files, we could rename our first DataTemplate
and define a second one containing an image instead of a textual label:
<Page.Resource> <DataTemplate x:Key="GeneralFileItemTemplate"> <TextBlock Text="{Binding FileName}"/> </DataTemplate> <DataTemplate x:Key="ImageFileItemTemplate"> <Image Source="{Binding FilePath}"/> </DataTemplate> </Page.Resource>
On the other hand, how would we make use of these data templates – the ListView
control (as well as any other ItemsControl
, by the way) only accepts one distinct DataTemplate
as input to its ItemTemplate
property…
This is where the Windows.UI.Xaml.Controls.DataTemplateSelector
class comes in: It acts as a middleware that is asked for every list item which DataTemplate
shall be used. To use it in our sample app, we need to declare our own custom selector class, e.g. called FileItemTemplateSelector
:
public class FileItemTemplateSelector : DataTemplateSelector { protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { var file = item as FileObject; switch (file.MimeType) { case "image/png": case "image/jpg": return ImageFileItemTemplate; default: return GeneralFileItemTemplate; } } public DataTemplate GeneralFileItemTemplate { get; set; } public DataTemplate ImageFileItemTemplate { get; set; } }
This snippet assumes that FileObject
is the type of our file item POCOs, and that it contains a MimeType
property to distinguish different types of files.
How does this template selector work? In general (for all DataTemplateSelector
implementations), for each list item the SelectTemplateCore
method is called, and the respective list item is passed to it. In out implementation, we cast the passed item to the FileObject
type, check whether it is an image or not, and tell the template selector to use a specific DataTemplate
to render the respective list item.
Where do these specific DataTemplates
come from? Well, let’s take a look at how a DataTemplateSelector
is referenced in the view:
<Page.Resource> <DataTemplate x:Key="GeneralFileItemTemplate"> <TextBlock Text="{Binding FileName}"/> </DataTemplate> <DataTemplate x:Key="ImageFileItemTemplate"> <Image Source="{Binding FilePath}"/> </DataTemplate> <helpers:FileItemTemplateSelector x:Key="FileItemTemplateSelector" GeneralFileItemTemplate="{StaticResource GeneralFileItemTemplate}" ImageFileItemTemplate="{StaticResource ImageFileItemTemplate}"/> </Page.Resource> <ListView ItemsSource="{Binding Files}" ItemTemplateSelector="{StaticResource FileItemTemplateSelector}" />
First: Declare the different template – nothing new so far. Second: Reference the template selector – I usually also do that in the Resources
section, to have it as near as possible to the DataTemplate
definitions – and pass the required DataTemplate
resources into its public properties. Third: In the ListView
, set the ItemTemplateSelector
property instead of the ItemTemplate
. Done!
Of course, this approach is not limited to using one of two different DataTemplates
. Instead, since we manually define both the template selector’s DataTemplate
properties and the SelectTemplateCore
logic, we are able to reference as many different templates as we want.
Pingback: Out-of-the-box PDF rendering in Windows Apps – www.mobilemotion.eu
Pingback: Individual XAML list item styling – www.mobilemotion.eu