0

Last year I made an app for Windows Phone 8, containing a LongListSelector with a unique first and last item template, as described here:

LongListSelector different item template for first and last item

I recently updated the app to Windows Phone 8.1 Store and this functionality broke. Here's my class (the only difference from the post being, that I'm using a ListView instead of a LongListSelector for Windows Phone 8.1 Store, since LongListSelector is not a part of the Windows Phone 8.1 Store framework):

public abstract class TemplateSelector : ContentControl
{
    public abstract DataTemplate SelectTemplate(object item, int index, int totalCount, DependencyObject container);

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);

        var parent = GetParentByType<ListView>(this);
        var index = (parent.ItemsSource as IList).IndexOf(newContent);
        var totalCount = (parent.ItemsSource as IList).Count;

        ContentTemplate = SelectTemplate(newContent, index, totalCount, this);
    }

    private static T GetParentByType<T>(DependencyObject element) where T : FrameworkElement
    {
        T result = null;
        DependencyObject parent = VisualTreeHelper.GetParent(element);

        while (parent != null)
        {
            result = parent as T;

            if (result != null)
            {
                return result;
            }

            parent = VisualTreeHelper.GetParent(parent);
        }

        return null;
    }
}

The problem is that this:

DependencyObject parent = VisualTreeHelper.GetParent(element);

of the GetParentByType function returns null for some reason. Anyone knows why or have an alternative solution?

Below is my XAML code(with a few xmlns's removed). The DataTrigger is there because sometimes a unique first item template is enough (controlled by the LoadMore property of the ViewModel, which is a bool).

<controls:WP81Page
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<controls:WP81Page.Resources>
    <DataTemplate x:Key="first">
        <Grid HorizontalAlignment="Left" Width="Auto" Height="200">
            <Border BorderThickness="1" BorderBrush="Black" Visibility="{Binding NoLargeImage, Converter={StaticResource BoolToVisibilityConverter}}" >
                <Image Source="/Images/default-image.png" Stretch="UniformToFill" />
            </Border>
            <Border BorderThickness="1" BorderBrush="Black" Visibility="{Binding NoLargeImage, Converter={StaticResource BoolToVisibilityConverterReverse}}" >
                <Image Source="{Binding LargeImageUrl}" Stretch="UniformToFill" />
            </Border>
            <StackPanel VerticalAlignment="Bottom" Background="#7F000000" >
                <TextBlock Text="{Binding Header}" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="White" FontWeight="Bold" Margin="15,0,15,15"/>
            </StackPanel>
        </Grid>
    </DataTemplate>
    <DataTemplate x:Key="default">
        <StackPanel Orientation="Horizontal" Margin="0,10,0,0" Height="105" Width="Auto">
            <Border BorderThickness="1" BorderBrush="Black" Margin="0,2" Visibility="{Binding NoSmallImage, Converter={StaticResource BoolToVisibilityConverter}}" >
                <Image Source="/Images/default-image.png" Width="130" Height="100" Stretch="UniformToFill" />
            </Border>
            <Border BorderThickness="1" BorderBrush="Black" Margin="0,2" Visibility="{Binding NoSmallImage, Converter={StaticResource BoolToVisibilityConverterReverse}}">
                <Image Source="{Binding SmallImageUrl}" Width="130" Height="100" Stretch="UniformToFill"/>
            </Border>
            <StackPanel Orientation="Vertical" Width="300" Margin="8,0,0,0">
                <TextBlock Text="{Binding Header}" TextWrapping="Wrap" FontWeight="Bold"  />
                <TextBlock Margin="0,3,0,0" Text="{Binding DisplayDate}" TextWrapping="Wrap" Foreground="#FFB9B9B9" FontSize="16" />
            </StackPanel>
        </StackPanel>
    </DataTemplate>
    <DataTemplate x:Key="last">
        <TextBlock Text="hent flere nyheder" FontSize="25" Margin="0,20" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" Height="75"  />
    </DataTemplate>


    <DataTemplate x:Key="UniqueFirstTemplateSelector">
        <common:UniqueFirstTemplateSelector Content="{Binding}" First="{StaticResource first}" Default="{StaticResource default}" HorizontalAlignment="Stretch"/>
    </DataTemplate>
    <DataTemplate x:Key="UniqueFirstAndLastTemplateSelector">
        <common:UniqueFirstAndLastTemplateSelector Content="{Binding}" First="{StaticResource first}" Default="{StaticResource default}" Last="{StaticResource last}" HorizontalAlignment="Stretch"/>
    </DataTemplate>
</controls:WP81Page.Resources>

<controls:WP81Page.DataContext>
    <viewModels:NewsViewModel/>
</controls:WP81Page.DataContext>

<Grid x:Name="LayoutRoot" Style="{Binding Source={StaticResource Background}}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ProgressBar Grid.Row="0" VerticalAlignment="Top" IsIndeterminate="True" Background="Transparent" Foreground="White" Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibilityConverter}}" />

    <StackPanel Grid.Row="0" Margin="12,17,0,28">
        <TextBlock Text="NYHEDER" FontSize="35" FontWeight="Bold" Style="{StaticResource PhoneTextNormalStyle}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1">
        <controls:WP81ListView x:Name="listSelector" Margin="22,0" Grid.Row="2" ItemsSource="{Binding News}" Command="{Binding NewsEntrySelectedCommand}" >
            <interactivity:Interaction.Behaviors>
                <core:DataTriggerBehavior Binding="{Binding LoadMore}" Value="True">
                    <core:ChangePropertyAction TargetObject="{Binding ElementName=listSelector}"
                        Value="{StaticResource UniqueFirstAndLastTemplateSelector}"
                        PropertyName="ItemTemplate" />
                </core:DataTriggerBehavior>
                <core:DataTriggerBehavior Binding="{Binding LoadMore}" Value="False">
                    <core:ChangePropertyAction TargetObject="{Binding ElementName=listSelector}"
                        Value="{StaticResource UniqueFirstTemplateSelector}"
                        PropertyName="ItemTemplate" />
                </core:DataTriggerBehavior>
            </interactivity:Interaction.Behaviors>
        </controls:WP81ListView>
    </Grid>
</Grid>

</controls:WP81Page>

Thank you in advance.

EDIT

Alright, so what I want is not for the first and last item to be unique all the time. I'm making a news feed where the last item is only unique when there are more news entries to load (when the last item are clicked, more news entries are added to the ListView). If however, the end of the news entries are reached, the last item must not be unique (hence the combination with interactivity).

Community
  • 1
  • 1
chaze
  • 365
  • 1
  • 3
  • 14
  • Can you show the ListView part of your XAML, where you set the ItemTemplateSelector? When GetParentByType is invoked, what is the type/value of "this" being passed in to the method? – Igor Ralic Mar 06 '15 at 16:25
  • So the ListView is at the bottom of the XAML code above and is applied via the DataTriggerBehaviors. The type of "this" when GetParentByType is invoked is UniqueFirstAndLastTemplateSelector, meaning the correct template selector type. So what I don't understand is why the parent of the UniqueFirstAndLastTemplateSelector is null, when it clearly is a child of the ListView? – chaze Mar 07 '15 at 15:00

1 Answers1

3

ListView has a property called ItemTemplateSelector which accepts objects based on DataTemplateSelector. So you need to change a couple of things to get it to work.

First of all, the definition of your template selector. It needs to be based on DataTemplateSelector and override method called SelectTemplateCore. It takes an object which is a ListViewItem. At that point you can go up the VisualTree to get the ListView and choose the DataTemplate based on index.

public class MyTemplateSelector : DataTemplateSelector
{
    public DataTemplate First { get; set; }
    public DataTemplate Default { get; set; }
    public DataTemplate Last { get; set; }

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        var listViewItem = container as ListViewItem;
        var listView = GetParentByType<ListView>(listViewItem); 

        var index = (listView.ItemsSource as IList).IndexOf(item);
        var totalCount = (listView.ItemsSource as IList).Count;

        if (index == 0)
            return First;
        else if (index == totalCount - 1)
            return Last;
        else
            return Default;
    }

    private T GetParentByType<T>(DependencyObject element) where T : FrameworkElement
    {
        T result = null;
        DependencyObject parent = VisualTreeHelper.GetParent(element);

        while (parent != null)
        {
            result = parent as T;

            if (result != null)
            {
                return result;
            }

            parent = VisualTreeHelper.GetParent(parent);
        }

        return null;
    }
}

Then, you need to create an instance of it in static resources

<local:MyTemplateSelector x:Key="SelectingTemplate" 
                          First="{StaticResource first}"
                          Default="{StaticResource default}"
                          Last="{StaticResource last}" />

with the First, Default and Last DataTemplates, just like you did before.

The last step is to apply it to the ListView you're using.

<ListView ItemsSource="{Binding SomeItemsSource}" 
          ItemTemplateSelector="{StaticResource SelectingTemplate}" />

And that's it!

Igor Ralic
  • 14,975
  • 4
  • 43
  • 51
  • Thanks for the quick reply! Looks promising. I'll test it first thing tomorrow :) – chaze Mar 07 '15 at 18:55
  • Alright, so this works if you want the first and last item to be unique all the time. However I'm making a news feed where the last item is only unique when there a more news entries to load. If however, you reach the end of the news entries, the last item must not be unique (hence the combination with interactivity). So the problem with changing the ItemTemplateSelector using the DataTriggerBehavior is that, the ListView reloads when a new ItemTemplateSelector is applied, which means the ListView scrolls to the top. This is not desired. – chaze Mar 08 '15 at 09:07
  • @chaze do you know the total count in the beginning somehow? could you use that to *not* set the last item template until you're sure you're at the end? – Igor Ralic Mar 08 '15 at 11:57
  • Nope, but I could apply a string to the last element types and only show the last unique template when that string I present. But that seems like kind of a hack. I was just wandering if there were a more clean solution, that doesn't involve info about the list items. – chaze Mar 09 '15 at 12:43
  • @chaze how is the data loaded, incrementally as user scrolls down or all at once (in more than one service calls)? You may use a bool IsLoading somewhere that you can check, and if it is still loading, probably the last element is not there yet? – Igor Ralic Mar 09 '15 at 13:02
  • It's loaded when clicking the last unique listview item (like a button). So initially 6 items are loaded through a service call, then a 7th item is added, which gets the last unique itemtemplate and thus becomes a 'load more' clickable item with a command assigned to it. – chaze Mar 09 '15 at 17:07