Thursday 19 June 2008

Styling a ListBox With Silverlight 2 Beta 2 (Part 1)

This post is an updated version of an earlier series I wrote on how to style a ListBox with Silverlight. I hope to update all three parts over the coming days. This tutorial is not about how to use Expression Blend, as there are plenty of other blogs that will explain that. This is for those who like to go right to the XAML.

One of the challenges of styling a control like the ListBox in Silverlight is the sheer number of subcomponents it uses. A ListBox has its own template which includes a ScrollViewer and an ItemPresenter. The ScrollViewer contains Scrollbars which contain RepeatButtons, Thumbs etc.

My original idea was to make copies of the default styles and slowly change them to look how I wanted, but I soon realised that the sheer quantity of XAML would get very confusing. So I decided to take the opposite approach. Start with a very basic template and slowly add features until it looks right. So here is the most basic of all ListBox styles which contains just a simple template:

<Style x:Key="ListBoxStyle1" TargetType="ListBox">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBox">
                <Grid x:Name="Root">
                    <ItemsPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This simply presents the items, with no outline of any sort displayed. To test our style, let's add some items to a ListBox (notice that with Silverlight 2 Beta 2 I need to put TextBlocks inside my ListBoxItem instead of just text that worked fine in Beta 1)

<ListBox Margin="5" Width="160" Height="160"
 Style="{StaticResource ListBoxStyle1}">
    <ListBoxItem><TextBlock Text="Hello"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="World"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 3"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 4 with a really long name"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 5"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 6"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 7"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 8"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 9 out of bounds"/></ListBoxItem>
    <ListBoxItem><TextBlock Text="Item 10 out of bounds"/></ListBoxItem>
</ListBox>

This is what it looks like so far:

ListBox Style Step 1

Next step is the border for our ListBox. I want all the ListBox items to be drawn on top of a filled rounded rectangle. To do this, I have added a Border control. I have given it some padding so we can actually see it, because the ListBoxItem template is currently drawing white rectangles over the top of it.

<Grid x:Name="Root">
  <Border Padding="5" Background="#E6BB8A" CornerRadius="5">
    <ItemsPresenter />
  </Border>
</Grid>

This is what it looks like now:

Listbox Style Step 2

So before we go doing anything further to the ListBox template itself (like adding scrollbars), let's sort out the item templates.

For this we need to create a new style - ListBoxItemStyle1. Here is a minimal ListBoxItem style:

<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem">
    <Setter Property="Foreground" Value="#FF000000" />    
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBoxItem">
                <Grid x:Name="Root">
                    <ContentPresenter
                        Content="{TemplateBinding Content}"
                        Foreground="{TemplateBinding Foreground}"
                     />                    
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

As you can see, it is a little more involved than the basic ListBox template. We needed to have the Foreground property set to a sensible default value (Black) for anything to appear. And we have simply used one ContentPresenter control to show the contents of each ListBox item.

(n.b. Remember to define the ListBoxItemStyle before your ListBox style. Expression Blend will be OK with this, but when you come to run it, Silverlight will fail with one of its characteristically unhelpful errors.)

We can tell our ListBox to use it by modifying our ListBox Style as follows:

<Style x:Key="ListBoxStyle1" TargetType="ListBox">
    <Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItemStyle1}" />
    <Setter Property="Template">
        ...

Now we have got rid of those white backgrounds:

ListBox Style Step 3

Finally we will make the item template just a little more interesting. I've added a border to the item presenter, and modified some margins and colours:

<Border CornerRadius="5" Background="#51615B" Margin="1">
<ContentPresenter
    Margin="1"
    Content="{TemplateBinding Content}"
    Foreground="{TemplateBinding Foreground}"
 />                    
 </Border>

This leaves us with something looking like this:

ListBox Style Step 4

So that is at least a reasonable start. Our ListBox is missing at least three crucial features though, which I will tackle in a future blog-post:

  • Scrollbars
  • Selected Item Rendering
  • Mouse Over Rendering

Continue to Part 2...

No comments: