Ok this post took a bit of inspiration from the C# Markup support that’s available for the Uno Platform where it’s easy to build a template selector (for a ListView for example) using a set of Case statements. This got me thinking that there must be a way that we can use a similar technique in XAML. Most of the time in XAML when we want to use a template selector, we switch to C#, create a new class that inherits from DataTemplateSelector and would typically expose multiple properties for the different DataTemplates to switch between, and some logic in the SelectTemplateCore method. But what if we could build a generic conditional data template that would allow us to add any number of DataTemplates that would be used for a specific value of a property.
A simple implementation of the ConditionalDataTemplateSelector
would be as follows.
[ContentProperty(Name = nameof(Templates))]
public class ConditionalDataTemplateSelector : DataTemplateSelector
{
public string? ConditionalProperty { get; set; }
public IList<ConditionalTemplate> Templates { get; } = new List<ConditionalTemplate>();
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container)
{
if (item is MainViewModel.Item viewModelItem)
{
var value = viewModelItem.GetType().GetProperty(ConditionalProperty!)?.GetValue(viewModelItem);
var template = Templates.FirstOrDefault(t => t.Value is not null && ( t.Value.Equals(value) || t.Value.Equals(value+string.Empty)))?.ValueTemplate;
return template as DataTemplate;
}
return base.SelectTemplateCore(item, container);
}
}
[ContentProperty(Name = nameof(ValueTemplate))]
public partial class ConditionalTemplate
{
public DataTemplate? ValueTemplate { get; set; }
public object? Value { get; set; }
};
Here we can see that the ConditionalDataTemplateSelector
has two properties: ConditionalProperty
which is the name of the property on the source item to look up the current value, and Templates
which is a list of ConditionalTemplate
. As we can see from the class definition the ConditionalTemplate
has a ValueTemplate
property which represents the DataTemplate
and a Value property which is the value for which this DataTemplate
would be selected.
Ok, this probably sounds all very theoretical, so let’s see this in action in a simple example. Firstly, some C# that shows the MainViewModel
, which exposes an array of Item
, which is made up of a Name
and Status
, where Status
is an Enum with values None
, Loading
and Loaded
.
public class MainViewModel
{
public enum Status
{
None,
Loading,
Loaded
}
public class Item
{
public string? Name { get; set; }
public Status Status { get; set; }
}
public Item[] Items { get; } = [
new Item { Name = "Item 1", Status = Status.None },
new Item { Name = "Item 2", Status = Status.Loading },
new Item { Name = "Item 3", Status = Status.Loaded }
];
}
Now the XAML. Here we are defining three different DataTemplate
that are appropriately keyed as NoneTemplate
, LoadingTemplate
and LoadedTemplate
.
<Page x:Class="DataTemplateSelectorApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DataTemplateSelectorApp"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<DataTemplate x:Key="NoneTemplate">
<TextBlock Text="None"/>
</DataTemplate>
<DataTemplate x:Key="LoadingTemplate">
<TextBlock Text="Loading...."/>
</DataTemplate>
<DataTemplate x:Key="LoadedTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:ConditionalDataTemplateSelector
x:Key="ConditionalSelector"
ConditionalProperty="Status">
<local:ConditionalTemplate
ValueTemplate="{StaticResource NoneTemplate}"
Value="None" />
<local:ConditionalTemplate
ValueTemplate="{StaticResource LoadingTemplate}"
Value="Loading" />
<local:ConditionalTemplate
ValueTemplate="{StaticResource LoadedTemplate}"
Value="Loaded" />
</local:ConditionalDataTemplateSelector>
</Page.Resources>
<ListView ItemsSource="{Binding Items}"
ItemTemplateSelector="{StaticResource ConditionalSelector}"/>
</Page>
The ConditionalDataTemplateSelector
resource specifies the Status
property to be inspected on each element and then for each ConditionalTemplate
a corresponding Value
is specified. As the ListView
renders each item in the Items
array, the ConditionalSelector
will be invoked. The Status
property on the Item
will be inspected and the value compared to the Value
of each ConditionalTemplate
. If there’s a match, the corresponding DataTemplate will be returned.
Here’s a rather uninspiring picture of this sample app running, showing each of the three DataTemplate being returned.
As we’ve seen in this post it is possible to build a generic template selector that is able to pick between any number of templates based on some conditional value. The only downside with this implementation is that it does leverage reflection at runtime to retrieve the value on the bound object. I’m sure that it would be possible using source code generation to generate strongly typed code that would replace the reflection based implementation.
1 thought on “Conditional Template Selector for XAML Applications for Windows and Uno Platform”