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.

17 comments:

Charles COLOMBEL said...

Hello,

Your article is really interesting. I have needed to know how customize my Listbox (and specially the scrollbar), and with your help i can do it!

However I have a problem with your solution. In fact, if I open it with Visual Studio 2008, it works (Designer and Test Page). But, when I open it with Blend, I can't view the design result, the exception is :

Error HRESULT E_FAIL has been returned from a call to a COM component.

After some search, I find the line which raise the problem :

ScrollBar
x:Name="HorizontalScrollBar" Style="{StaticResource ScrollBarStyle1}"
{...}
/>

If I delete the Style propertie
ScrollBar
x:Name="HorizontalScrollBar"
{...}
/>
It works but I lost the bar style ...!! I don't understand because the VerticalScrollbar Works fine while it uses the same style !!

I want to know if you are the same problem.

Thanks

Unknown said...

Hi Charles,
yes I have the same issue in Blend. Hopefully Microsoft will address this in a future preview release.

Charles COLOMBEL said...

Thanks for your answer. I hope too that the next Blend's version will fix some bugs.

However this bug is strange because we can change the Vertical Style but not the Horizontal without bug!

Charles COLOMBEL said...

I find a solution to bypass the problem :

We must create 2 style for the 2 scrollbar (even if the style must be the same)

In this case, Blend can draw your control.

Rick said...

Mark,

Thanks for your articles about styling a Silverlight ListBox. I am trying to incorporate your code into a demo that I am preparing. I took your styles from app.xaml and copied them into mine, and copied the ListBox from your app into mine. Everything works except that the list item foreground text is black, instead of the color that is specified in the style (NoodleLove, I believe). I've been beating my head on this for a couple of hours without success...Any thoughts?

Thanks,
Rick

tanios said...

Hello,
Very interesting article!
The sample "TestListBoxStyle.zip" is not available anymore for download. Could you re-post it?
Thank you,

Unknown said...

hi tanios,
I have posted a new link

Anonymous said...
This comment has been removed by a blog administrator.
Jason Grigely said...

I was wondering if you could help me. I'm trying specifically to isolate the color that appears as the result of mousing over a selectable item. I want it to be selectable, but I'd like for the color to not be displayed, Thanks!

Unknown said...

Hi Jason,
that's the HoverBorderBackgroundBrush. Simply get rid of the animation

Jason Grigely said...

Thanks Mark, so how would I go about applying this to every element in my page? Sorry I'm somewhat new to Silverlight.

Unknown said...

The style should be put in app.xaml
Then you can reference it as a StaticResource to set the Style on any ListBoxes you use

Jason Grigely said...
This comment has been removed by the author.
Jason Grigely said...

nevermind!

Anonymous said...

Hello
This link is not working i mean the download link

Unknown said...

Hi Anonymous, I've updated the download link

Ondrej "Deirh" Dobias said...

Awesome work, this is exactly what I was trying to figure out since WPF styles do not copy well due to SL restrictions.

Works with SL3 flawlessly!