Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MAUI - data template selector

Tags:

c#

xaml

maui

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>
like image 914
user2250152 Avatar asked Dec 08 '25 06:12

user2250152


2 Answers

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.

like image 128
Felix Shen Avatar answered Dec 09 '25 19:12

Felix Shen


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>
like image 24
Max Avatar answered Dec 09 '25 20:12

Max



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!