Saturday 12 July 2008

Styling a ListBox with Silverlight 2 Beta 2 (Part 3) - ListBoxItem Style

This is the third and final part of my updated series on how to style a ListBox in Silverlight 2 Beta 2. Here are the links to part 1 and part 2.

So far we have customised the appearance of a ListBox, including the ScrollBars. The one remaining missing piece is some kind of indication of which items are selected, and visual feedback when the mouse hovers over a ListBox item.
To achieve this we need to add some Storyboards to the custom ListBoxItem we created in my first post. There are eight states you can specify Storyboards for. The most important for our purposes are "Normal", "Selected", and "MouseOver". Note that we need to use two Visual State Groups. One will define Normal and MouseOver, while the other will define Selected and Unselected. This gives us the flexibility to combine animations if we require to show combined states such as Selected and MouseOver.

Unfortunately it can also mean conflict of interests. With the beta 1 model, I quite easily specified one colour for Normal, one colour for Selected and one colour for MouseOver. However, with the new model, the MouseOver and Unselected states compete to set the background colour. The solution I eventually came up with was to have two borders and control their opacity (partly inspired by this forum post). One border is used to show MouseOver state, and the other to show Selected state. Here is the updated ListBoxItem style containing the new border elements (without Visual State Groups):

<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
  <Setter Property="Foreground"
      Value="{StaticResource ListBoxItemForegroundBrush}" />
  <Setter Property="Template">
  <Setter.Value>
  <ControlTemplate TargetType="ListBoxItem">
  <Grid x:Name="Root">
    <vsm:VisualStateManager.VisualStateGroups>
       ...
    </vsm:VisualStateManager.VisualStateGroups>
  
    <Border CornerRadius="5" Margin="1"
      x:Name="HoverBorder">
      <Border.Background>
        <SolidColorBrush
        x:Name="HoverBorderBackgroundBrush"
        Color="#51615B" />
      </Border.Background>
    </Border>
    <Border CornerRadius="5" Margin="1"
      x:Name="SelectedBorder">
      <Border.Background>
        <SolidColorBrush
        x:Name="SelectedBorderBackgroundBrush"
        Opacity="0"
        Color="#397F8F" />
      </Border.Background>
    </Border>
    <ContentPresenter
        Margin="4,1"                  
        Content="{TemplateBinding Content}"
        Foreground="{TemplateBinding Foreground}" />
    </Grid>                      
    </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

I have chosen to prioritise the Selected background above the MouseOver background, but you can easily change that by swapping the order of the borders.

The VisualStateGroups specify the two ColorAnimations I need - one for Selected and one for MouseOver. I also needed to add a DoubleAnimation for the opacity of the SelectedBorder.

<vsm:VisualStateManager.VisualStateGroups>

<vsm:VisualStateGroup x:Name="SelectionStates">
<vsm:VisualState x:Name="Unselected" />
<vsm:VisualState x:Name="Selected">
  <Storyboard>
    <DoubleAnimation
     Storyboard.TargetName="SelectedBorderBackgroundBrush"
     Storyboard.TargetProperty="Opacity"
     Duration="0"
     To="1.0"/>
   <ColorAnimation
     Storyboard.TargetName="SelectedBorderBackgroundBrush"
     Storyboard.TargetProperty="Color"
     Duration="0"
     To="#FF397F8F"/>
  </Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>

<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal" />
<vsm:VisualState x:Name="MouseOver">
  <Storyboard>
    <ColorAnimation
      Storyboard.TargetName="HoverBorderBackgroundBrush"
      Storyboard.TargetProperty="Color"
      Duration="0"
      To="#898A8A"/>
  </Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

Here's what it looks like (item 3 selected, item 5 mouse over):

Silverlight 2 ListBox Style with Mouse Over and Selected Item

OK, so my choice of colours is awful and there are lots of visual elements that could be improved (and we still await a fix for the vertical scrollbar bug), but hopefully I have demonstrated how it is possible to completely change every visual element of how your ListBox is rendered in Silverlight 2 beta 2.
I have uploaded my test project here which you can load in Expression Blend 2.5 or Visual Studio 2008.