Let's say I have a view model with a collection of items and a selected item.
public interface IFoo {..}
public interface IFooA : IFoo {..}
public interface IFooB : IFoo {..}
public class MyViewModel : ViewModelBase
{
private IFoo _selectedItem;
public IFoo SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
private List<IFoo> _items;
public List<IFoo> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
}
In XAML I have a Picker and I want to show a different template based on the type of selected item. One template for IFooA and another one for IFooB.
I couldn't find out what is the best way to achieve this in MAUI XAML. I don't see any template selector.
<Grid>
<Picker ItemsSource="{Binding Items}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding SelectedItem}"/>
<ContentPresenter>
<!--Probably not possible with content presenter-->
</ContentPresenter>
</Grid>
I think you could use Triggers.
Suppose we have a Picker with two values:
<Picker x:Name="picker" >
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>one</x:String>
<x:String>two</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
Then for any control which uses datatriggers
<Label Text="CollectionView with a DataTemplateSelector"
FontAttributes="Bold"
HorizontalOptions="Center" >
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding Source={x:Reference picker},Path=SelectedItem}" Value="one">
<Setter Property="BackgroundColor" Value="Blue"/>
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding Source={x:Reference picker},Path=SelectedItem}" Value="two">
<Setter Property="BackgroundColor" Value="Green"/>
</DataTrigger>
</Label.Triggers>
</Label>
For more info, you could refer to Triggers
Hope it works for you.
Can't remember where I got this from but it should work if you really want to use DataTemplates:
namespace mynamespace {
public class ASItemControl : ContentView
{
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(ASItemControl), propertyChanged: ItemTemplateChanged);
public object Item
{
get => (object)GetValue(ItemProperty);
set => SetValue(ItemProperty, value);
}
public static readonly BindableProperty ItemProperty = BindableProperty.Create(nameof(Item), typeof(object), typeof(ASItemControl), null, propertyChanged: SourceChanged);
private static void ItemTemplateChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = bindable as ASItemControl;
control.BuildItem();
}
private static void SourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = bindable as ASItemControl;
control.BuildItem();
}
public bool HideOnNullContent { get; set; } = false;
protected void BuildItem()
{
if (Item == null)
{
Content = null;
return;
}
//Create the content
try
{
Content = CreateTemplateForItem(Item, ItemTemplate, false);
}
catch
{
Content = null;
}
finally
{
if (HideOnNullContent)
IsVisible = Content != null;
}
}
public static View CreateTemplateForItem(object item, DataTemplate itemTemplate, bool createDefaultIfNoTemplate = true)
{
//Check to see if we have a template selector or just a template
var templateToUse = itemTemplate is DataTemplateSelector templateSelector ? templateSelector.SelectTemplate(item, null) : itemTemplate;
//If we still don't have a template, create a label
if (templateToUse == null)
return createDefaultIfNoTemplate ? new Label() { Text = item.ToString() } : null;
//Create the content
//If a view wasn't created, we can't use it, exit
if (!(templateToUse.CreateContent() is View view)) return new Label() { Text = item.ToString() }; ;
//Set the binding
view.BindingContext = item;
return view;
}
}
}
then use like this in xaml:
<Grid>
<Picker ItemsSource="{Binding Items}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding SelectedItem}"/>
<mynamespace:ASItemControl VerticalOptions="Center"
Item="{Binding SelectedItem}"
ItemTemplate="{StaticResource LocalFooTemplateSelector }" />
</Grid>
where your binding is the ViewModel or whatever data Item you have and MyTemplateSelector is a regular DataTemplateSelector as you would use for a CollectionView. dont forget to add your namespace
xmlns:mynamespace="clr-namespace:mynamespace"
so looking at your class example your data template selector could look maybe like this
public abstract class Foo
{
public enum Types { FooA, FooB }
public abstract Types Type { get; }
public string TypeStr => Type.ToString();
}
public class FooA : Foo
{
public override Types Type => Types.FooA;
public string MyFooAProperty => "Hello from FooA";
}
public class FooB : Foo
{
public override Types Type => Types.FooB;
public string MyFooBProperty => "Hello from FooB";
}
public class FooTemplateSelector : DataTemplateSelector
{
public DataTemplate FooA { get; set; }
public DataTemplate FooB { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
var obj = (Foo)item;
if(obj.Type == Foo.Types.FooA)
{
return FooA;
}
else
{
return FooB;
}
}
}
and then finally define your templates in a dictionary on your page (or wherever)
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="FooBTemplate"
x:DataType="mynamespace:FooB">
<VerticalStackLayout>
<Label Text="FooB Hooray"/>
<Label Text="{Binding Type}"/>
<Label Text="{Binding MyFooBProperty}"/>
</VerticalStackLayout>
</DataTemplate>
<DataTemplate x:Key="FooATemplate"
x:DataType="mynamespace:FooA">
<VerticalStackLayout>
<Label Text="FooA Hooray"/>
<Label Text="{Binding TypeStr}"/>
<Label Text="{Binding MyFooAProperty}"/>
</VerticalStackLayout>
</DataTemplate>
<mynamespace:FooTemplateSelector x:Key="LocalFooTemplateSelector"
FooA="{StaticResource FooATemplate}"
FooB="{StaticResource FooBTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With