Windows Store App Xaml GridView with Variable Templates

image

One of the useful controls to use when creating a Windows Store app for Windows 8 is the GridView which can use a VariableSizeWrapGrid to support item templates of varying size.  Using this in conjunction with an DataTemplateSelector makes it possible to vary both the size and data template of the items in a GridView.  Here is how you do it:

Setting the VariableSizeWrapGrid Item Height and Width

The VariableSizeWrapGrid works with a unit size and you can define whatever unit size you want – each template should be a multiple of that.  In your GridView.GroupStyle, you should define the ItemHeight and ItemWidth.  Add the spacing between the columns & rows; based on the Microsoft guidelines, the height and width should be layed out on a 20-pixel grid and the spacing should be 10 pixels between each item.  So in this case each item would be 200x100 with 10-pixel spacing, so ItemWidth=210 and ItemHeight=110.

 <GroupStyle.Panel>
    <ItemsPanelTemplate>
        <VariableSizedWrapGrid Orientation="Vertical" 
            Margin="0,0,80,0" 
            ItemHeight="110" 
            ItemWidth="210"/>
    </ItemsPanelTemplate>
</GroupStyle.Panel>

Create Data Templates for each item type

Create Data Templates in a resource file that would be used for each item type.  In this case, I have a ProjectItemTemplate and  FavoriteProjectItemTemplate in a Xaml resource file included in my App.xaml.  The FavoriteProjectItemTemplate shows a Gold Star (xE082 in the Segoe UI Symbol) and additional project information that I only want to load for favorite projects:

     <DataTemplate x:Key="ProjectItemTemplate">
        <Grid HorizontalAlignment="Left" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" Width="200" Height="100">
            <StackPanel Margin="5">
                <TextBlock Text="{Binding Name}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}"/>
                <TextBlock Text="{Binding Description}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
                <TextBlock Text="{Binding UpdatedAt}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
            </StackPanel>
        </Grid>
    </DataTemplate>
    <DataTemplate x:Key="FavoriteProjectItemTemplate">
        <Grid HorizontalAlignment="Left" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" Width="200" Height="210">
            <TextBlock HorizontalAlignment="Right" VerticalAlignment="Top" x:Name="Star" TextWrapping="Wrap" Text="&#xE082;" FontFamily="Segoe UI Symbol" Foreground="Gold" FontSize="24" Margin="0,5,10,0"/>
            <StackPanel Margin="5">
                <TextBlock Text="{Binding Name}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Margin="0,0,32,0"/>
                <TextBlock Text="{Binding Description}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
                <TextBlock Text="{Binding UpdatedAt}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Aspects[0].Items.Count}" Style="{StaticResource CaptionTextStyle}"/>
                    <TextBlock Text=" discussions" Style="{StaticResource CaptionTextStyle}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Aspects[1].Items.Count}" Style="{StaticResource CaptionTextStyle}"/>
                    <TextBlock Text=" todos" Style="{StaticResource CaptionTextStyle}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Aspects[2].Items.Count}" Style="{StaticResource CaptionTextStyle}"/>
                    <TextBlock Text=" files" Style="{StaticResource CaptionTextStyle}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Aspects[3].Items.Count}" Style="{StaticResource CaptionTextStyle}"/>
                    <TextBlock Text=" text documents" Style="{StaticResource CaptionTextStyle}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Aspects[4].Items.Count}" Style="{StaticResource CaptionTextStyle}"/>
                    <TextBlock Text=" dates" Style="{StaticResource CaptionTextStyle}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Aspects[5].Items.Count}" Style="{StaticResource CaptionTextStyle}"/>
                    <TextBlock Text=" people" Style="{StaticResource CaptionTextStyle}"/>
                </StackPanel>
            </StackPanel>
        </Grid>
    </DataTemplate>

Create an Item Template Selector

The Item Template Selector is called each time a new item template is needed.  In this, you should pick the item template and set the number of columns & rows the item should span.  In this case if the project.IsStarred is true, then the template would use the FavoriteProjectItemTemplate and set VariableSizedWrapGrid to span 2 rows instead of 1.

     public class ProjectDataTemplateSelector : DataTemplateSelector
    {
        protected override Windows.UI.Xaml.DataTemplate SelectTemplateCore(object item, Windows.UI.Xaml.DependencyObject container)
        {
            var project = item as Project;

            var uiElement = container as UIElement;

            if (project.IsStarred)
            {
                VariableSizedWrapGrid.SetColumnSpan(uiElement, 1);
                VariableSizedWrapGrid.SetRowSpan(uiElement, 2);

                return App.Current.Resources["FavoriteProjectItemTemplate"] as DataTemplate;
            }

            VariableSizedWrapGrid.SetColumnSpan(uiElement, 1);
            VariableSizedWrapGrid.SetRowSpan(uiElement, 1);

            return App.Current.Resources["ProjectItemTemplate"] as DataTemplate;
        }
    }

Adding the ItemTemplateSelector to the GridView

On the page resources, add an instance of the ItemTemplateSelector and then reference it in the GridView.ItemTemplateSelector=”{StaticResources ProjectTemplateSelector}”

 <support:ProjectDataTemplateSelector x:Key="ProjectTemplateSelector"/>

Responding to Changes in the Data Model

If the selection criteria changes and you need the selector logic to run again, just reset the selector.  Thanks to this StackOverflow answer for the assistance here.

 var projects = this.selector.SelectedItems.Cast<Project>();

foreach (var project in projects)
{
    project.IsStarred = !project.IsStarred;
}

var itemTemplateSelector = this.selector.ItemTemplateSelector;
this.selector.ItemTemplateSelector = null;
this.selector.ItemTemplateSelector = itemTemplateSelector;

Summary

Varying the item template and size of items increase the visual interest of your app and can help highlight important differences when standard DataTemplate binding won’t work.