"LoadTemplate Should Not Be Null" Error When Using DataTemplateSelector

Hi Fellas,

I have a custom control that I wrote called a PaginatedGrid. It includes ItemsSource and ItemTemplate bindable properties. Basically it consists of a carousel view of grids with a page indicator at the bottom. I then used this custom, reusable control to depict a screen of gaming devices that the user needs to monitor. Everything was working the way it should when I defined the ItemTemplate as a static value (for testing purposes) like so:

<utility:PaginatedGrid x:Name="paginatedGrid" ItemsSource="{Binding GamingDevices}" ColumnCount="3" RowCount="2">
    <utility:PaginatedGrid.ItemTemplate>
        <DataTemplate>
            <ContentView BackgroundColor="Red">
                <StackLayout VerticalOptions="Center" HorizontalOptions="Center">
                    <Label Text="{Binding Balance}"/>
                    <Label Text="{Binding DeviceStatus}"/>
                </StackLayout>
            </ContentView>
        </DataTemplate>
    </utility:PaginatedGrid.ItemTemplate>
</utility:PaginatedGrid>

However, when I defined a DataTemplateSelector:

<ContentView.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="activeTemplate">
            <ContentView BackgroundColor="Blue">
                <StackLayout VerticalOptions="Center" HorizontalOptions="Center">
                    <Label Text="{Binding Balance}"/>
                    <Label Text="{Binding DeviceStatus}"/>
                </StackLayout>
            </ContentView>
        </DataTemplate>

        <DataTemplate x:Key="attentionTemplate">
            ...
        </DataTemplate>

        <DataTemplate x:Key="errorTemplate">
            ...
        </DataTemplate>

        <DataTemplate x:Key="availableTemplate">
            ...
        </DataTemplate>

        <DataTemplate x:Key="disabledTemplate">
            ...
        </DataTemplate>

        <local:GamingDeviceTemplateSelector x:Key="gamingDeviceTemplateSelector" 
                                            ActiveTemplate="{StaticResource activeTemplate}" 
                                            AttentionTemplate="{StaticResource attentionTemplate}"
                                            ErrorTemplate="{StaticResource errorTemplate}"
                                            AvailableTemplate="{StaticResource availableTemplate}"
                                            DisabledTemplate="{StaticResource disabledTemplate}"/>
    </ResourceDictionary>
</ContentView.Resources>

public class GamingDeviceTemplateSelector : DataTemplateSelector
{
    public DataTemplate ActiveTemplate { get; set; }
    public DataTemplate AttentionTemplate { get; set; }
    public DataTemplate ErrorTemplate { get; set; }
    public DataTemplate AvailableTemplate { get; set; }
    public DataTemplate DisabledTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        GamingDevice gamingDevice = (GamingDevice)item;
        switch (gamingDevice.DeviceStatus)
        {
            case MachineStatus.Active:
                return ActiveTemplate;
            case MachineStatus.Available:
                return AvailableTemplate;
            case MachineStatus.CashIn:
            case MachineStatus.CashOut:
            case MachineStatus.Jackpot:
                return AttentionTemplate;
            case MachineStatus.Disabled:
                return DisabledTemplate;
            case MachineStatus.Error:
                return ErrorTemplate;
            default:
                return null;
        }
    }
}

and set it to the ItemTemplate of the PaginatedGrid like so:

<utility:PaginatedGrid x:Name="paginatedGrid" 
                       ColumnCount="3" RowCount="2"
                       ItemsSource="{Binding GamingDevices}" 
                       ItemTemplate="{StaticResource gamingDeviceTemplateSelector}"/>

I got a "LoadTemplate should not be null" when making a call to the DataTemplate's CreateContent method. I set a breakpoint in the OnSelectTemplate method, and the program never hits it. Could someone please help me debug this? I have no idea what I'm doing wrong...

Best Answer

Answers

  • ColeXColeX Member, Xamarin Team Xamurai

    Can you try to create DataTemplateSelector in code behind ?

    Refer https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/selector#consuming-a-datatemplateselector-in-c

    If it still does not work , please provide a basic sample to us for reproducing .

  • hwangcyrushwangcyrus Member ✭✭
    edited November 2018

    Okay, so I've narrowed the issue down to my custom control. I subbed in a ListView with the DataTemplateSelector and it worked fine:

    <!--
        <ListView ItemsSource="{Binding GamingDevices}" ItemTemplate="{StaticResource gamingDeviceTemplateSelector}"/>
    -->
    

    Here is all the code for my PaginatedGrid control. Something specifically funky is going on in the OnSelectTemplate of the GridTemplateSelector. The error I get is still "LoadTemplate should not be null." Try to use the control with any DataTemplateSelector and you'll get the same error.

    xaml:

    <?xml version="1.0" encoding="UTF-8"?>
    <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
                           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                           xmlns:xforms="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.CarouselView"
                           xmlns:local="clr-namespace:MNPOS.View.Utility;assembly=MNPOS.View"
                           x:Class="MNPOS.View.Utility.PaginatedGrid">
    
        <StackLayout x:Name="stackLayout" Spacing="0">
    
            <xforms:CarouselView x:Name="carouselView" VerticalOptions="FillAndExpand"/>
    
            <!--
            <ContentView HeightRequest="120">
                <local:CarouselIndicator x:Name="carouselIndicator" 
                                           UnselectedIndicator="unselected_circle.png" SelectedIndicator="selected_circle.png" 
                                           IndicatorHeight="20" IndicatorWidth="20" 
                                           HorizontalOptions="Center" VerticalOptions="Center"/>
            </ContentView>
        -->
    
        </StackLayout>
    
    </ContentView>
    

    Code-behind:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Runtime.CompilerServices;
    using Xamarin.Forms;
    
    namespace MNPOS.View.Utility
    {
        public partial class PaginatedGrid : ContentView
        {
            public PaginatedGrid()
            {
                InitializeComponent();
                carouselView.PositionSelected += (object sender, SelectedPositionChangedEventArgs e) => 
                {
                    PageIndex = (int)e.SelectedPosition;
                };
            }
    
            static void OnColumnCountChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var control = (PaginatedGrid)bindable;
            }
    
            static void OnRowCountChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var control = (PaginatedGrid)bindable;
            }
    
            static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var control = (PaginatedGrid)bindable;
                var itemsSource = control.CarouselViewItemsSource;
                control.carouselView.ItemsSource = itemsSource;
                control.carouselIndicator.ItemsSource = itemsSource;
            }
    
            static void OnItemTemplateChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var control = (PaginatedGrid)bindable;
                control.carouselView.ItemTemplate = new GridTemplateSelector(control.RowCount, control.ColumnCount, control.ItemTemplate);
            }
    
            static void OnPageIndexChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var control = (PaginatedGrid)bindable;
                control.carouselIndicator.Position = control.PageIndex;
            }
    
            public static readonly BindableProperty ColumnCountProperty =
                BindableProperty.Create(propertyName: nameof(ColumnCount),
                                        returnType: typeof(int),
                                        declaringType: typeof(PaginatedGrid),
                                        propertyChanged: OnColumnCountChanged);
            public int ColumnCount
            {
                get { return (int)GetValue(ColumnCountProperty); }
                set { SetValue(ColumnCountProperty, value); }
            }
    
            public static readonly BindableProperty RowCountProperty =
                BindableProperty.Create(propertyName: nameof(RowCount),
                                        returnType: typeof(int),
                                        declaringType: typeof(PaginatedGrid),
                                        propertyChanged: OnRowCountChanged);
            public int RowCount
            {
                get { return (int)GetValue(RowCountProperty); }
                set { SetValue(RowCountProperty, value); }
            }
    
            public static readonly BindableProperty ItemsSourceProperty =
                BindableProperty.Create(propertyName: nameof(ItemsSource),
                                        returnType: typeof(IEnumerable),
                                        declaringType: typeof(PaginatedGrid),
                                        propertyChanged: OnItemsSourceChanged);
            public IEnumerable ItemsSource
            {
                get { return (IEnumerable)GetValue(ItemsSourceProperty); }
                set { SetValue(ItemsSourceProperty, value); }
            }
    
            public static readonly BindableProperty ItemTemplateProperty =
                BindableProperty.Create(propertyName: nameof(ItemTemplate),
                                        returnType: typeof(DataTemplate),
                                        declaringType: typeof(PaginatedGrid),
                                        propertyChanged: OnItemTemplateChanged);
            public DataTemplate ItemTemplate
            {
                get { return (DataTemplate)GetValue(ItemTemplateProperty); }
                set { SetValue(ItemTemplateProperty, value); }
            }
    
            public static readonly BindableProperty PageIndexProperty =
                BindableProperty.Create(propertyName: nameof(PageIndex),
                                        returnType: typeof(int),
                                        declaringType: typeof(PaginatedGrid),
                                        propertyChanged: OnPageIndexChanged);
            public int PageIndex
            {
                get { return (int)GetValue(PageIndexProperty); }
                set { SetValue(PageIndexProperty, value); }
            }
    
            private IEnumerable CarouselViewItemsSource
            {
                get
                {
                    List<ArrayList> result = new List<ArrayList>();
                    int i = 0;
                    foreach (var item in ItemsSource)
                    {
                        if (i % (ColumnCount * RowCount) == 0)
                        {
                            result.Add(new ArrayList());
                        }
                        result[result.Count - 1].Add(item);
                        ++i;
                    }
                    return result;
                }
            }
    
            private class GridTemplateSelector : DataTemplateSelector
            {
                public GridTemplateSelector(int rowCount, int columnCount, DataTemplate gridItemTemplate)
                {
                    _rowCount = rowCount;
                    _columnCount = columnCount;
                    _gridItemTemplate = gridItemTemplate;
                }
    
                protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
                {
                    ArrayList items = (ArrayList)item;
                    var grid = new Grid();
                    for (int i = 0; i < _rowCount; ++i)
                    {
                        grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                    }
                    for (int j = 0; j < _columnCount; ++j)
                    {
                        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
                    }
    
                    int k = 0;
                    foreach (var i in items)
                    {
                        var itemView = _gridItemTemplate.CreateContent() as Xamarin.Forms.View; //This is where I'm getting the error
                        itemView.BindingContext = i;
                        grid.Children.Add(itemView, k % _columnCount, k / _columnCount);
                        ++k;
                    }
    
                    return new DataTemplate(() => {
                        return new ViewCell { View = grid };
                    });
                }
    
                private readonly int _rowCount;
                private readonly int _columnCount;
                private DataTemplate _gridItemTemplate;
            }
        }
    }
    
  • hwangcyrushwangcyrus Member ✭✭

    @ColeX said:
    Can you try to create DataTemplateSelector in code behind ?

    Creating/assigning the DataTemplateSelector in the codebehind does not solve the issue.

  • have you confirmed that your data template selector is not returning null in that switch statement? Personally I would create a "null template" that indicates an error on the screen because if that switch ever returns null I think it will crash, which would make that dts unsafe.

  • JonathanCook.8900JonathanCook.8900 USMember ✭✭
    Accepted Answer
  • hwangcyrushwangcyrus Member ✭✭

    Brilliant! That fixed my problem entirely. I totally forgot to call the SelectTemplate method on the selector. But I wonder.. why the hell is a DataTemplateSelector also a DataTemplate itself? I think that caused me to believe that it would automatically call SelectTemplate on its own somehow... Anyway, thanks for the solution!

Sign In or Register to comment.