Share via


UWA: Simple ListViewItem Styling

This article explains a simple(r) way to style the look of ListViewItems in Universal Windows Store applications.


Introduction

Universal Windows Applications can be quite tricky to style in some ways. One thing that can catch out developers is that the default templates for controls do quite a lot. This is particularly noticeable for developers from a WPF or Silverlight background.  The approaches one is used to using run into some issues since Visual State Manager sets a lot of things which will change as the user mouses over, selects etc.
The "proper" way to go about some styling is to replace the default template, editing it to add/remove styling as suits the requirement.
With some templates there is quite a lot of work involved in doing this and a fair bit of knowledge will be required. This is particularly true with ListView and GridView Item.
There is a sample which goes with this article here.

Audience

This approach is best suited to the beginner/hobbyist/casual developer or for prototyping. You might not want to use this in a commercial setting - although it's not going to make your computer explode or anything particularly awful.

Overview

There are two complications to styling ListViewItems courtesy of the default template.

  • VisualStateManager based styling will interfere with any brushes or colours you set.
  • There's a mouse over border and a selected item check which will be a nuisance if you don't want them

The idea of this is to do some layout in the ItemTemplate and control most of the colours, hide effects you don't like by over-riding the standard theme brushes. In this approach all the complicated VSM changes are kept intact, you just don't notice what they are doing because they change things to the same colour.

Requirement

The required look for our example ListView is like this:

Each of the items is to have a central TextBlock, this will have text in Gray  There will be a Blue border. The selected item is to have a Blue background with the text in White.
This seems a simple enough request.

The Development Process

One approach to explaining this would be to show the end result and say this bit does x and that does Y. Here it is working - Taaa Daa!!
Let's instead travel the developer's path.
You first need a set of days of the week. This isn't terribly interesting part of the process but we need it.  It's provided by a ViewModel and will be explained later in the article so you can just take it for granted the strings just get there somehow.

ListView

This is a vertical list of things we're going to present to the user so the obvious choice of control is the ListView. A GridView would have been more appropriate if this was a horizontal list but let's not digress from this particular requirement.
We're going to need a TextBlock and a Border. Those will go in the ItemTemplate.

<ListView ItemsSource="{Binding Items}" Width="200">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Blue"
                    BorderThickness="2" Height="60">
                <TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

A quick look in the design window and we can see the list:

There's out list of days, the values are OK but we want them all to fill the width of that ListVIew. Let's fix that and make that text Gray by default. If we try setting the Border to Stretch:

<Border HorizontalAlignment="Stretch"

That doesn't work.
We therefore need to work with it's parent - which means the ItemContainerStyle.  As it's name suggests, the ItemContainer is the thing that the Item goes in.
Make that tell it's contents to stretch and have a Gray foreground. 
When we do that we get:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListView ItemsSource="{Binding Items}" Width="200">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                <Setter Property="Foreground" Value="Gray" />
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
            <DataTemplate>
                <Border BorderBrush="Blue"
                        BorderThickness="2" Height="60">
                    <TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

Which is looking much more promising:

As it turns out this is as far as we're going with markup in MainPage.

Outstanding Functionality and Problems

There is nothing in this so far which sets the background of the selected item. There are also a number of issues which become obvious when we try this out.

Ticked Off

If you''re more used to WPF or Silverlight the fact that the ListViewItem template has a tick in it which appears as you select an item can be quite a surprise. If you don't actually want that tick then that'll probably be rather an unpleasant "what the?"  moment.

Inside the default template for the ListViewItem lurks a check mark whose appearance is controlled by Visual State Manager states. The "proper" way to remove that check would be to delve into that template and rip the checkbox out, remove all the styling which references it and you're good.
We're going to take the short cut instead.
Like walking across the corner of the carefully tended grassed area this is a little naughty but it'll get you where you're going that bit faster. Just don't tell your Team Lead this is how you did it. He'll never know.

Theme Brushes

It turns out that the template uses a load of standardised brushes. We can over-ride these to change the colours used. We're doing this the quick and dirty way but no need to go crazy. There will be more than one brush we want to work with so let's put them all together in one place.
Create a new Resource Dictionary to hold these brushes.  In the sample this is called ListViewItemBrushes.xaml. Merge that in app.xaml:

<Application.Resources>
   <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="ListViewItemBrushes.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Now, we need to work out what this brush will be we're working with.
We can either look through the template or just read the list of brushes and guess from here - the names are fairly self explanatory.
One look at that and we can see ListViewItemCheckThemeBrush.
Before we add this in our resource dictionary, it's worth mentioning how themes work.  You have a default theme and others such as high contrast black or white.
When you change brushes for themes the proper way to do this is to add themedictionaries.
You can therefore add the following to your resource dictionary:

<ResourceDictionary.ThemeDictionaries>
        <ResourceDictionary x:Key="Default">
            <SolidColorBrush x:Key="ListViewItemCheckThemeBrush" Color="Blue" />
        </ResourceDictionary>
        <ResourceDictionary x:Key="HighContrastBlack">
            <SolidColorBrush x:Key="ListViewItemCheckThemeBrush" Color="Blue" />
        </ResourceDictionary>
        <ResourceDictionary x:Key="HighContrastWhite">
            <SolidColorBrush x:Key="ListViewItemCheckThemeBrush" Color="Blue" />
        </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>

And that white tick will sort of disappear.
Or you can just add the one line

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:uwa_Simple_ListViewItem_Styling">
     <SolidColorBrush x:Key="ListViewItemCheckThemeBrush" Color="Blue" />
</ResourceDictionary>

Selected Colour

When you make that change you will notice that you can still just make it out because the selected item is sort of a purple background.
We address that one in a similar way - look at the list of brushes and override a likely looking one.
Let's try

<SolidColorBrush x:Key="ListViewItemSelectedBackgroundThemeBrush" Color="Blue"/>

At first glance, that rather looks like it didn't work.

Then you move your cursor off, it turns blue and you realise there's some brushes applied on mouse over.
Ok, there are a couple more Background brushes there, let's add them and give that a go.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:uwa_Simple_ListViewItem_Styling">
    <SolidColorBrush x:Key="ListViewItemCheckThemeBrush" Color="Blue" />
    <SolidColorBrush x:Key="ListViewItemSelectedBackgroundThemeBrush" Color="Blue"/>
    <SolidColorBrush x:Key="ListViewItemPointerOverBackgroundThemeBrush" Color="Blue" />
    <SolidColorBrush x:Key="ListViewItemSelectedPointerOverBackgroundThemeBrush" Color="Blue"/>
</ResourceDictionary>

That appears to be it done, until you move the mouse and see.

There's still some sort of a border brush that is applied as you mouse over the border.
As before, run down the list of brushes and pick a likely one to try, giving.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:uwa_Simple_ListViewItem_Styling">
    <SolidColorBrush x:Key="ListViewItemCheckThemeBrush" Color="Blue" />
    <SolidColorBrush x:Key="ListViewItemSelectedBackgroundThemeBrush" Color="Blue"/>
    <SolidColorBrush x:Key="ListViewItemPointerOverBackgroundThemeBrush" Color="Blue" />
    <SolidColorBrush x:Key="ListViewItemSelectedPointerOverBackgroundThemeBrush" Color="Blue"/>
    <SolidColorBrush x:Key="ListViewItemSelectedPointerOverBorderThemeBrush" Color="Blue" />
</ResourceDictionary>

And when we try that, all significant issues are now resolved.
There is a teeny tiny issue in that as we mouse over the item it sort of grows since there's a background beyond what you would consider the border.
You can make that on the picture above - the outer piece of blue.
The result is near enough the requirement though.

Near Enough?

Near enough is often great as far as users are concerned. As developers we are often detail focussed since one stray semi colon or bracket is a disaster. This is a different world to that of many users.
Care should be taken whilst gathering requirements to test the strength of feeling the users have on each requirement.
All too often you will see a specification which is full of things the users do not care about.
Someone says "What colour would you like?" they shrug. Think. Randomly pick blue.
The requirement says it must be blue.
The user only expressed that preference because the Business Analyst appeared to want an answer. 
It's best to ask first before expending serious effort on minor details, especially when you have something is very nearly what the specification says.

ViewModel

For the purposes of this exercise, the viewmodel doesn't really have to do so much. All that is required is a collection of strings and the code to initialise that with some values. 

public class  MainPageViewModel
{
    public ObservableCollection<string> Items { get; set; }
    public string  MyString { get; set; }
    public MainPageViewModel()
    {
        Items = new  ObservableCollection<string>
        {
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Party Day",
            "Recovery Day"
        };
    }
}

It could instead  be a List<string> exposed rather than an ObservableCollection. Habit kicked in there.
As you can see a public collection is initialised with our list of days. This is the week from the binge drinker's perspective.

Conclusion

Now you have that list of brushes, you can do your own styling very quickly. So long as it suits the approach reasonably well.
Now you've walked through an explanation of how to approach this you could apply the same procedure to some other styling in your own Universal Windows App.
Just because it's quick and dirty doesn't make it totally bad.

See Also

Windows Store Portal