ListView and GridView Templates for Windows (UWP)

In my previous post I discussed Control Template in Windows development (UWP and Platform.Uno). I feel the topic of templates warrants at least one follow up post. So, in this post I’m going to walk through ListView Templates and GridView Templates. As both ListView and GridView inherit from ListViewBase, I’m actually going to focus my attention on the ListView. However, the GridView shares the same templates, just with a default horizontal layout.

You might be thinking that surely the ListView only has 1, maybe 2, templates. In this post you’ll see that there are all sorts of templates, that allow you to tailor the ListView. The flexibility offered by the XAML framework, whether you code in XAML or in C#, is truly amazing, as you’ll come to see.

Populating a ListView with Data

Let’s jump in and build out a basic page with some data. For the purpose of this post I’m going to define a static array of data that we’ll be working. I’ve exposed a property, Presenters, that lists a selection of presenters for the upcoming Xamarin Developer Summit. Each presenter has a Name, Title, Company and AvatarUrl.

public sealed partial class MainPage 
{
    public MainPage()
    {
        this.InitializeComponent();
    }
 
    public Presenter[] Presenters { get; } = 
        new Presenter[]
    {
        ("Dan Siegel", "Microsoft MVP & Owner","AvantiPoint", 
        "https://xamarindevelopersummit.com/wp-content/uploads/2019/04/[email protected]" ),
        ("David Ortinau", "Senior Program Manager","Microsoft", 
        "https://xamarindevelopersummit.com/wp-content/uploads/2019/04/david_spk.png" ),
        ("James Montemagno", "Principal Program Manager","Microsoft", 
        "https://xamarindevelopersummit.com/wp-content/uploads/2019/04/james_spk.png" ),
        ("Donovan Brown", "Principal DevOps Manager","Microsoft", 
        "https://xamarindevelopersummit.com/wp-content/uploads/2019/04/donovan_spk.png" ),
        ("Jonathan Peppers", "Senior Software Engineer","Microsoft", 
        "https://xamarindevelopersummit.com/wp-content/uploads/2019/05/[email protected]" ),
        ("Martijn van Dijk", "Microsoft and Xamarin MVP","Baseflow", 
        "https://xamarindevelopersummit.com/wp-content/uploads/2019/04/[email protected]" )
    };
}

public class Presenter
{
    public string AvatarUrl { get; set; }
    public string Name { get; set; }
    public string Title { get; set; }
    public string Company { get; set; }

    public static implicit operator Presenter((string Name, string Title, string Company, string AvatarUrl) info)
    {
        return new Presenter { Name = info.Name, Title=info.Title, Company = info.Company, AvatarUrl = info.AvatarUrl };
    }
}

In the XAML for the MainPage I’m going to add a ListView and set the ItemsSource property to the Presenters property. I’m using the x:Bind syntax to eliminate the need for reflection and improve performance of the app. However, if your data isn’t available when the page loads, you should consider setting the Mode to OneWay if using the x:Bind syntax.

<Grid>
    <ListView ItemsSource="{x:Bind Presenters}" />
</Grid>

At this point if I run the app, what I see is a blank window. However, on closer inspection you can see that there are indeed multiple rows. As the mouse moves over a row it gets a grey background. When I click on a row, that row gets a black border around it, to indicate it’s the selected row.

Clearly we need to adjust the layout of the ListView so that we can see information about the presenters.

Default Layout using ToString

We’ll start with the simplest way to get information to display in the ListView, which is to override the ToString method. Here I’m returning a string made up of the Name, Title and Company of the presenter.

public override string ToString()
{
    return $"{Name} - {Title} - {Company}";
}

Running the app again, without modifying the XAML, I can see that each row corresponds to a presenter in the Presenters array.

Display Single Property using DisplayMemberPath

The DisplayMemberPath property on the ListView can be used to specify a property on the data bound item. In this case we’re setting the path to be the Name property.

<ListView DisplayMemberPath="Name"
        ItemsSource="{x:Bind Presenters}" />

Whilst the DisplayMemberPath might be useful for simple data sets, it does rely on reflection. The string literal set as the DisplayMemberPath needs to be converted into a get operation on the corresponding property.

Item Layout using ItemTemplate and ItemTemplateSelector

What do you do if you want to specify a more complex layout for the items in the array. One of the most common templates to be specified on a ListView is the ItemTemplate. By defining a DataTemplate that can be used by the ListView, you can control exactly how each item in the ListView is presented.

Using a DataTemplate for an ItemTemplate

If you haven’t worked with ListView templates before, it’s worth opening your solution in Visual Studio Blend. In the Objects and Timeline window, right-click on the ListView and select Edit Additional Templates, Edit Generated Items (ItemTemplate) and Create Empty.

Give the new template a name, PresenterItemTemplate, and add it as a resource on the page.

The XAML only contains the ItemTemplate and ItemsSource properties. The DisplayMemberPath shown earlier is no longer required

<ListView ItemTemplate="{StaticResource PresenterItemTemplate}"
            ItemsSource="{x:Bind Presenters}" />

Each presenter is displayed using two columns that you can see defined in the following XAML. In the first column is an Image using the AvatarUrl property. The second column has a StackPanel, allowing the Name and Company to be stacked.

<DataTemplate x:Key="PresenterItemTemplate"
                x:DataType="local:Presenter">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Image Margin="8"
                Source="{x:Bind AvatarUrl}" />
        <StackPanel Grid.Column="1"
                    VerticalAlignment="Center">
            <TextBlock Margin="0,0,0,4"
                        Text="{x:Bind Name}"
                        Style="{StaticResource TitleTextBlockStyle}" />
            <TextBlock Text="{x:Bind Company}"
                        Style="{StaticResource SubtitleTextBlockStyle}" />
        </StackPanel>
    </Grid>
</DataTemplate>

Our items are already looking much better. Note that the hexagonal frame around the presenter’s avatar is part of the image, not the ItemTemplate.

Multiple Templates using a DataTemplateSelector as the ItemTemplateSelector

In some cases it may be necessary that you need to modify the ItemTemplate based on some attribute of the data. By setting the ItemTemplateSelector property on the ListView, you can dynamically switch the ItemTemplate that’s used.

For this example we’re going to use whether the presenter works for Microsoft or not, based on their Company property. I’ve created a derived class, PresenterTemplateSelector, off of the DataTemplateSelector base class. In the SelectTemplateCore method I’m inspecting the Company property and returning one of two DataTemplate based on whether its equal to “Microsoft”.

public class PresenterTemplateSelector:DataTemplateSelector
{
    public DataTemplate RegularPresenter { get; set; }
    public DataTemplate MicrosoftPresenter { get; set; }

    protected override DataTemplate SelectTemplateCore(object item)
    {
        if(item is Presenter presenter)
        {
            return presenter.Company == "Microsoft" ? MicrosoftPresenter : RegularPresenter;
        }

        return base.SelectTemplateCore(item);
    }
}

In the following XAML I’ve defined two DataTemplates. The difference is that the MicrosoftPresenterItemTemplate uses a Microsoft Logo in place of the presenter’s avatar image.

<Page.Resources>
    <DataTemplate x:Key="PresenterItemTemplate"
                    x:DataType="local:Presenter">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Image Margin="8"
                    Source="{x:Bind AvatarUrl}" />
            <StackPanel Grid.Column="1"
                        VerticalAlignment="Center">
                <TextBlock Margin="0,0,0,4"
                            Text="{x:Bind Name}"
                            Style="{StaticResource TitleTextBlockStyle}" />
                <TextBlock Text="{x:Bind Company}"
                            Style="{StaticResource SubtitleTextBlockStyle}" />
            </StackPanel>
        </Grid>
    </DataTemplate>
    <DataTemplate x:Key="MicrosoftPresenterItemTemplate"
                    x:DataType="local:Presenter">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Image Margin="8,32"
                    Source="http://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE2qVsJ?ver=3f74" />
            <StackPanel Grid.Column="1"
                        VerticalAlignment="Center">
                <TextBlock Margin="0,0,0,4"
                            Text="{x:Bind Name}"
                            Style="{StaticResource TitleTextBlockStyle}" />
                <TextBlock Text="{x:Bind Company}"
                            Style="{StaticResource SubtitleTextBlockStyle}" />
            </StackPanel>
        </Grid>
    </DataTemplate>
    <local:PresenterTemplateSelector x:Key="PresenterTemplateSelector"
                                        RegularPresenter="{StaticResource PresenterItemTemplate}"
                                        MicrosoftPresenter="{StaticResource MicrosoftPresenterItemTemplate}" />
</Page.Resources>


<Grid>
    <ListView ItemTemplateSelector="{StaticResource PresenterTemplateSelector}"
                ItemsSource="{x:Bind Presenters}" />
</Grid>

The two DataTemplates are specified as the RegularPresenter and MicrosoftPresenter properties on the PresenterTemplateSelector instance. Subsequently the PresenterTemplateSelector is set as the ItemTemplateSelector on the ListView. Running the application replaces the presenter’s avatar with a Microsoft logo for when the presenter has Microsoft as their Company.

Modifying Selection, Focus and PointerOver (Hover) Style using an ItemContainerStyle

We’ve seen a number of ways to adjust the layout for each element in the ListView. However, despite the variations in layout, when you move the mouse over an item, or click on an item to select it, the change in appearance is always the same. This is because the border that you see when an item is selected, or the gray background when you move the mouse over an item, are controlled by the ItemContainerStyle for the ListView.

You can think of the ItemContainerStyle as being a wrapper around each element in the ListView. It can be used to add borders, background or margin to every item in the ListView. The ItemContainerStyle also has a number of visual states which can be used to adjust the appearance of the item based on how the user is interacting with it.

Default Styles in Generic.xaml

The ItemContainerStyle is moderately complex, so unless you’ve spent a bit of time working with the ListView, I would recommend using one of the defaults as a starting point. The defaults are all listed in the generic.xaml file which can be located at C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.18362.0\Generic (or similar) folder on your computer.

As its name suggests, the ItemContainerStyle is indeed a Style. It is used to style each ListViewItem that is displayed within a ListView. In order to find the default ItemContainerStyle we need to look through the generic.xaml for a Style that has a TargetType of ListViewItem but has no Key set (hence making it the implicit style for all ListViewItme). In the following XAML, taken from generic.xaml, we can see that the Style is based on another explicit Style, ListViewItemRevealStyle.

<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemRevealStyle}" />

In Blend we can use the Apply Resource option to explicitly set the ItemContainerStyle to be the ListViewItemRevealStyle.

Custom ItemContainerStyle

Once we’ve set the ItemContainerStyle of our ListView to be the ListViewItemRevealStyle we can then use the Edit a Copy option to take a copy of the ListViewItemRevealStyle that we can make changes to.

The new Style needs a name and can be added to the current page.

Once the new Style has been added to the page, it immediately gets focus in the Objects and Timeline view. With the ItemContainerStyle in focus, you can then make changes in the Properties window. Any changes will be created as a Setter on the Style.

The following XAML is the copy of the ListViewItemRevealStyle that was copied in. There are three main parts to this Style:

  • Setters – for default values to be applied to the ListViewItem. Since each ListViewItem is generated on demand by the ListView, this is actually your opportunity to set properties directly on the ListViewItem.
  • ListViewItemPresenter – this element makes up the entire ControlTemplate for the ListViewItem. It encapsulates all the styling that you see by default around the ListViewItem and includes a large number of properties that can be adjusted to control the appearance of the ListViewItem. A lot of these properties are already set to a theme resource, making them easy to customise.
  • VisualStateGroups – these are visual states that define changes or transitions for the ListViewItem in different states. This Style only defines the CommonStates and EnabledStates VisualStateGroups as these include common states such as Selected and Pressed states which are the most likely states you’ll want to customise. If you do modify the Selected state, you’ll probably want to customise PointerOverSelected and PressedSelected for a consistent appearance when the user is interacting with the selected item.
<Style x:Key="CustomContainerStyle" TargetType="ListViewItem">
    <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
    <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
    <Setter Property="Background" Value="{ThemeResource ListViewItemBackground}" />
    <Setter Property="Foreground" Value="{ThemeResource ListViewItemForeground}" />
    <Setter Property="TabNavigation" Value="Local" />
    <Setter Property="IsHoldingEnabled" Value="True" />
    <Setter Property="Padding" Value="12,0,12,0" />
    <Setter Property="HorizontalContentAlignment" Value="Left" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="MinWidth" Value="{ThemeResource ListViewItemMinWidth}" />
    <Setter Property="MinHeight" Value="{ThemeResource ListViewItemMinHeight}" />
    <Setter Property="AllowDrop" Value="False" />
    <Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
    <Setter Property="FocusVisualMargin" Value="0" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <ListViewItemPresenter
                x:Name="Root"
                HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
                CheckBrush="{ThemeResource ListViewItemCheckBrush}"
                CheckMode="{ThemeResource ListViewItemCheckMode}"
                ContentMargin="{TemplateBinding Padding}"
                ContentTransitions="{TemplateBinding ContentTransitions}"
                Control.IsTemplateFocusTarget="True"
                DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
                DragBackground="{ThemeResource ListViewItemDragBackground}"
                DragForeground="{ThemeResource ListViewItemDragForeground}"
                DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
                FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
                FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
                FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
                PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
                PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
                PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}"
                PressedBackground="{ThemeResource ListViewItemBackgroundPressed}"
                ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
                RevealBackground="{ThemeResource ListViewItemRevealBackground}"
                RevealBorderBrush="{ThemeResource ListViewItemRevealBorderBrush}"
                RevealBorderThickness="{ThemeResource ListViewItemRevealBorderThemeThickness}"
                SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}"
                SelectedForeground="{ThemeResource ListViewItemForegroundSelected}"
                SelectedPointerOverBackground="{ThemeResource ListViewItemBackgroundSelectedPointerOver}"
                SelectedPressedBackground="{ThemeResource ListViewItemBackgroundSelectedPressed}"
                SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="Selected" />
                            <VisualState x:Name="PointerOver">
                                <VisualState.Setters>
                                    <Setter Target="Root.(RevealBrush.State)" Value="PointerOver" />
                                    <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPointerOver}" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="PointerOverSelected">
                                <VisualState.Setters>
                                    <Setter Target="Root.(RevealBrush.State)" Value="PointerOver" />
                                    <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPointerOver}" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="PointerOverPressed">
                                <VisualState.Setters>
                                    <Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
                                    <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <VisualState.Setters>
                                    <Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
                                    <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="PressedSelected">
                                <VisualState.Setters>
                                    <Setter Target="Root.(RevealBrush.State)" Value="Pressed" />
                                    <Setter Target="Root.RevealBorderBrush" Value="{ThemeResource ListViewItemRevealBorderBrushPressed}" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="DisabledStates">
                            <VisualState x:Name="Enabled" />
                            <VisualState x:Name="Disabled">
                                <VisualState.Setters>
                                    <Setter Target="Root.RevealBorderThickness" Value="0" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </ListViewItemPresenter>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The ListViewItemPresenter is the single element that makes up the ControlTemplate. It is responsible for hosting the ItemTemplate and does infact inherit from ContentPresenter. However, unlike the ContentPresenter which is a basic content host, the ListViewItemPresenter implements standard ListView behaviour such as selection borders and changing the background when you hover over the item etc.

Overriding a ThemeResource to Customise the ListViewItemPresenter

A lot of the behaviour of the ListViewItemPresenter is configurable on the ListViewItemPresenterOr by simply adjusting its properties in the ControlTemplate. Furthermore, a good percentage of these properties are already specified in the ControlTemplate using either a ThemeResource or via a TemplateBinding.

For example take the PointerOverBackground property, which determines the background colour of the ListViewItem when the mouse hovers over it. By default the PointerOverBackground property uses the ListViewItemBackgroundPointerOver theme resource.

<ListViewItemPresenter
    x:Name="Root" ...
    PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}" />

One way to change the PointerOverBackground property is to set it directly on the ListViewItemPresenter.

<ListViewItemPresenter
    x:Name="Root" ...
    PointerOverBackground="Red" />

Alternatively, I can leave the PointerOverBackground set to the ListViewItemBackgroundPointerOver theme resource. I can then override the default value of the ListViewItemBackgroundPointerOver resource. If adjusting properties on the ListViewItemPresenter is the only reason to override the default Style, you can do this by simply defining the matching static resource.

<SolidColorBrush x:Key="ListViewItemBackgroundPointerOver">Red</SolidColorBrush>

ListViewItemPresenter Properties that have a TemplateBinding

There’s a bit of inconsistency with how the properties on the ListViewItemPresenter are specified. As we’ve seen with the PointerOverBackground property, some properties are set using a theme resource. However, there are other properties that are set using a template binding. For example, the HorizontalContentAlignment property is set using a TemplateBinding.

<ListViewItemPresenter
    x:Name="Root" ...
    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />

This means that instead of overriding a theme resource, instead we have to look above the ContentTemplate to the Setters defined in the Style for the ListViewItem. In this case the HorizontalContentAlignment property is set to Left.

<Style x:Key="CustomContainerStyle" TargetType="ListViewItem">
    ...
    <Setter Property="HorizontalContentAlignment" Value="Left" />

If you’re not familiar with the HorizontalContentAlignment property it’s used to define how the contents of a ContentPresenter is aligned and sized. With the default value of Left, the content of the ListViewItem will be positioned adjacent to the left edge. Even if you specify the HorizontalAlignment to be Center or Stretch on the first element of the ItemTemplate, the contents will still be aligned to the left. This is a common issue that developers run into when using the ListView and is easily fixed by setting the HorizontalContentAlignment to Stretch.

Visual States on the ListViewItemPresenter

The ListViewItemPresenter encapsulates a lot of behaviour associated with how the ListViewItem is styled. However, in the ControlTemplate for the ListViewItem you can see that there are some visual states defined within the ListViewItemPresenter. These visual states can be used to further customise the appearance of the ListViewItem based on the state of the item in the list.

In the default Style for the ListViewItem, which we copied into our project earlier, there are two VisualStateGroups defined: CommonStates and DisabledStates. From an initial inspection this would appear to cover most states that you can imagine a ListViewItem would be in, such as Normal, Pressed, PointerOver, Enabled etc. However, there is a set of visual states that haven’t been defined here which is common to a lot of XAML element. The FocusStates group is typically made up of Focused and Unfocused states. As you can imagine, these define the changes to the appearance when an element gets or loses focus.

I’m going to adjust the ListViewItemPresenter to shrink the ListViewItem when it gets focus. I’ll do this by applying a render transform to reduce the ScaleX and ScaleY properties to 0.8. Before I can add the visual states, I first need to make sure that there is a render transform object that I can manipulate. In this case I’m going to use a CompositeTransform as this gives me the most flexibility to apply any render transform, not just a scale transform.

<ListViewItemPresenter
    x:Name="Root" ...
    RenderTransformOrigin="0.5,0.5">
        <ListViewItemPresenter.RenderTransform>
            <CompositeTransform x:Name="CustomTransform"/>
        </ListViewItemPresenter.RenderTransform>

Next I’m going to add a new VisualStateGroup to the ListViewItemPresenter. In this group I’ve defined two visual states. The Unfocused state doesn’t have any properties set, whereas the Focused state sets the ScaleX and ScaleY properties.

<ListViewItemPresenter
    x:Name="Root" ... >
        <VisualStateManager.VisualStateGroups>
            ...
            <VisualStateGroup x:Name="FocusStates">
                <VisualState x:Name="Focused">
                    <VisualState.Setters>
                        <Setter Target="CustomTransform.ScaleX" Value="0.8" />
                        <Setter Target="CustomTransform.ScaleY" Value="0.8" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Unfocused" />
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </ListViewItemPresenter>

In order to see the impact of this I’ve set the SelectionMode on the ListView to Extended. This defines how many items can be selected, and how the ListView allows items to be selected. The Extended value means that on Windows you can hold down the Ctrl key whilst using the up and down arrow keys to move the focus between items without selecting them.

The changes we’ve applied to the ListViewItemPresenter (that you can see in the following image) are:

  • We’ve overridden the default value of the ListViewItemBackgroundPointerOver to set it to Red
  • We’ve applied a scale transform when an item is in the Focused state.

In the following image you can see three complete items that are in different states:

  • Selected – the first item in the list has been selected, which is highlighted by the green background
  • PointerOver – when the screenshot was taken the mouse pointer was hovering over the second item, hence the Red background thanks to the ListViewItemBackgroundPointerOver resource.
  • Focused – the keyboard has been used to move focus to the third item by holding the Ctrl key and using the arrow keys. The Focused visual state causes the item to be scaled down to 80%.

Expanded Style for ListViewItem

In the previous sections we’ve been working with a copy of the default Style for the ListViewItem. However, if you take a look in the generic.xaml file, you’ll see that there is another Style for the ListViewItem called ListViewItemExpanded. When building for Windows 8/8.1 this expanded Style used to be the default, before the introduction of the ListViewItemPresenter. The ListViewItemExpanded Style goes on for almost 500 lines in order to explicitly define similar behaviour to the ListViewItemPresenter. Unless you really can’t get the ListViewItemPresenter to do what you need, I would avoid attempting to extend the ListViewItemExpanded Style.

So now that I’ve warned you about the complexity of the ListViewItemExpanded Style, it does bode the question as to why I mentioned it. For a couple of reasons: Firstly, it’s important to know that this Style exists and where to find it, in case you really want to dig in and customise the ListViewItem. The second reason is so that I can point out that in place of the ListViewItemPresenter, the ListViewItemExpanded Style uses a vanilla ContentPresenter to host each item in the ListView.

Minimal Style for ListViewItem

Occasionally you may want to do away with all the visual enhancements that you get out of the box with the ListView. This may be to squeeze that last little bit of performance out of the ListView (often required on Xbox where memory management is abysmal), or it may be that you don’t want all the hover effects, or the animations when items are selected or add/removed from the ListView. As mentioned in the previous section where we discussed the ListViewItemExpanded Style, the key to hosting items in the ListView is the ContentPresenter. Thus, it’s easy for us to create a bare minimum Style that doesn’t do anything other than host the items in the ListView.

<Style x:Key="MinimalContainerStyle" TargetType="ListViewItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <ContentPresenter x:Name="ContentPresenter"
                    Grid.Column="1"
                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                    ContentTemplate="{TemplateBinding ContentTemplate}"
                    Content="{TemplateBinding Content}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

As you can see from the following image there’s no additional padding or margin, and there’s no selection, focus or mouse over states.

Note: If you get to the point where you strip out all the layout, animation and other behaviour from the ListViewItem (such as in the MinimalContainerSylte above), you should consider using an ItemsControl instead of the ListView. You can think of the ItemsControl as a raw repeater that allows you to data bind a collection of elements in order to have them presented in a list on the screen. However, it doesn’t support selection and other interactivity features that the ListView has.

ItemsPanel

So far we’ve looked at templates that govern how each item in the ListView appear. The ItemsPanel property on the ListView accepts an ItemsPanelTemplat which defines how items are placed relative to each other. For example the default ItemsPanelTemplate that is specified on the ListView template uses an ItemsStackPanel to efficiently layout items in a vertical stack

<ItemsPanelTemplate>
    <ItemsStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>

What’s interesting is that you can easily switch between laying items out in a vertical list, to having them presented horizontally, by changing the Orientation to Horizontal on the ItemsStackPanel. You will also have to adjust the default Setters on the ListView template to enable horizontal scrolling and disable vertical scrolling.

Most XAML developers will be familiar with a regular StackPanel that’s used to present items in either a vertical or horizontal list. The ItemsStackPanel is similar in that it presents items by stacking them. However, it has been specifically designed for use with a ListView, GridView or even an ItemsControl. It is capable of virtualising the items in the ListView making it particularly efficient for large lists.

It is worth pointing out that virtualisation isn’t always a good thing. If you have a small number of items in the list, you may get a better experience by disabling virtualisation. Using a regular StackPanel instead of an ItemsStackPanel will prevent any virtualisation meaning that scrolling won’t have any ghost cells (cells where the contents haven’t completely rendered). However, be aware that as the number of items increases, so will both the load time and the memory usage.

GridView v ListView

Whilst on the topic of the ItemsPanel property, it’s worth looking at the default value for the GridView. Instead of using an ItemsStackPanel, the GridView uses an ItemsWrapGrid, allowing items to wrap in either a horizontal or vertical direction.

Both the GridView and ListView inherit from the ListViewBase class. If you inspect the default Style for both GridView and ListView you’ll see that the main variations are in the direction of scrolling and the ItemsPanel. As an experiment to see how well you understand the various templates, try converting a GridView into a ListView and vice versa – you can do this just by adjusting the attributes of the various templates.

Header and Footer Templates

We’re getting towards the end of the templates for the ListView but I’d be remiss if I didn’t point out the Header and Footer templates. One basic usage for these is to add space to the beginning or the end of the list of items. However, if you just want to do this, you may want to consider just adding a margin to the ItemsStackPanel in the ItemsPanel.

The header and footers of a ListView are pairs of properties. There are a Header and HeaderTemplate properties, and there are Footer and FooterTemplate properties. These work very similar to Content and ContentTemplate properties on a ContentControl. The Header and Footer represent the data that you want to display. Then of course the HeaderTemplate and FooterTemplate define the template for that data.

<ListView ...
    Header="This is the header">
    <ListView.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" Style="{StaticResource TitleTextBlockStyle}"/>
        </DataTemplate>
    </ListView.HeaderTemplate>
</ListView>

The following image illustrates the Header being applied via the HeaderTemplate as the beginning of the ListView. In this case I’ve added a Margin of 50 to the top of the ItemsStackPanel, which as you can see appears after the Header but before the list of items.

ListView Template

So the last template I’m going to cover in this post is of course the template for the ListView itself. If you look in the generic.xaml file you’ll find implicit styles for both the ListView and the GridView. The default styles define things like the transitions to be applied to items (i.e. the ItemContainerTransitions), the ItemsPanel (i.e. ItemsStackPanel for ListView, ItemsWrapGrid for GridView) and of course the ControlTemplate for the ListView and GridView themselves.

The ControlTemplate for both ListView and GridView are relatively simple and are made up of a Border, a nested ScrollViewer and a nested ItemsPresenter.

Scroll Events

A scenario that comes up from time to time is that you want to trigger some behaviour based upon the ListView being scrolled. This might be the first time its scrolled, or perhaps it’s when the scrolling gets to a certain point. Either way, you can easily attach an event to the ScrollViewer that’s part of the ControlTemplate for the ListView.

Templates for ListView and GridView

In this post we’ve walked through a variety of different ways you can customise the appearance of the ListView and GridView controls. There are templates for controlling how items appear and their behaviour, and there are templates for styling the ListView and GridView themselves.

One set of templates we didn’t touch on in this post are those that are used when grouping data. Whilst grouped data typically uses the ItemTemplate to render each item in the list, there are separate templates for the group header and footers. We’ll leave the topic of working with grouped data for another post.

Leave a comment