Styling a ListBox in Silverlight (Part 3 - Item Style)
This post describes how to style a ListBox in Silverlight 2 beta 1. For an updated version for beta 2, please look here
In my last post, I demonstrated how to completely customise the appearance of a ListBox
in Silverlight, including the appearance of the ScrollBars
. The missing piece though is any kind of indication of which items are selected, or any 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 six states you can specify Storyboards
for. The most important for our purposes are "Normal State", "Normal Selected State", "MouseOver State" and "MouseOver Selected State".
Obviously you can do whatever you like in these Storyboards. I have gone for the simplest option of changing the background. But if you wished to animate some properties on the ContentPresenter
itself, then that also would be possible.
Here's the updated ListBoxItem
style:
<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem">
<Setter Property="Foreground" Value="#BDBC97" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid x:Name="RootElement">
<Grid.Resources>
<Storyboard x:Key="Normal State" />
<!-- <Storyboard x:Key="Unfocused Selected State"/> -->
<Storyboard x:Key="Normal Selected State">
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" Duration="0" To="#FF397F8F"/>
</Storyboard>
<Storyboard x:Key="MouseOver State">
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" Duration="0" To="#FF898A8A"/>
</Storyboard>
<!-- <Storyboard x:Key="MouseOver Unfocused Selected State"/> -->
<Storyboard x:Key="MouseOver Selected State">
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" Duration="0" To="#FF898A8A"/>
</Storyboard>
</Grid.Resources>
<Border CornerRadius="5" Margin="1">
<Border.Background>
<SolidColorBrush x:Name="BackgroundBrush" Color="#51615B" />
</Border.Background>
<ContentPresenter
Margin="5,1"
Content="{TemplateBinding Content}"
Foreground="{TemplateBinding Foreground}"
/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's what it looks like (item 3 selected, item 5 mouse over):
OK, so my choice of colours is awful and there are lots of visual elements that could be improved (and also we await a fix in beta 2 to 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.
Here's the complete XAML for my test application including all the custom styles:
<UserControl
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Silverlight2BlendApplication.Page"
Width="640" Height="480"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:Silverlight2BlendApplication="clr-namespace:Silverlight2BlendApplication">
<UserControl.Resources>
<Style TargetType="ScrollBar" x:Key="ScrollBarStyle1">
<!-- Any other properties you want to set -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollBar">
<Grid x:Name="RootElement">
<!-- States -->
<Grid.Resources>
<!-- Transition to the slider's default state -->
<Storyboard x:Key="Normal State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="RootElement" Storyboard.TargetProperty="Opacity" To="1" />
</Storyboard>
<!-- Transition to the slider's mouseover state -->
<Storyboard x:Key="MouseOver State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="RootElement" Storyboard.TargetProperty="Opacity" To="1" />
</Storyboard>
<!-- Transition to the slider's disabled state -->
<Storyboard x:Key="Disabled State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="RootElement" Storyboard.TargetProperty="Opacity" To="0.5" />
</Storyboard>
<!-- RepeatButton Templates -->
<ControlTemplate x:Key="RepeatButtonTemplate">
<Grid x:Name="RootElement" Background="Transparent" />
</ControlTemplate>
<ControlTemplate x:Key="HorizontalIncrementTemplate">
<Grid x:Name="RootElement" Background="#00000000">
<Grid.Resources>
<Storyboard x:Key="Normal State" />
<Storyboard x:Key="MouseOver State">
<ColorAnimation Duration="0:0:0.2" Storyboard.TargetName="ButtonColor" Storyboard.TargetProperty="Color" To="#FF557E9A" />
</Storyboard>
<Storyboard x:Key="Disabled State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="ButtonVisual" Storyboard.TargetProperty="Opacity" To=".6" />
</Storyboard>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35*"/>
<ColumnDefinition Width="30*"/>
<ColumnDefinition Width="35*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25*"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="25*"/>
</Grid.RowDefinitions>
<Path x:Name="ButtonVisual" Grid.Column="1" Grid.Row="1" Stretch="Fill" Data="F1 M 511.047,352.682L 511.047,342.252L 517.145,347.467L 511.047,352.682 Z ">
<Path.Fill>
<SolidColorBrush x:Name="ButtonColor" Color="#FF313131" />
</Path.Fill>
</Path>
<Rectangle x:Name="FocusVisualElement" Grid.ColumnSpan="3" Grid.RowSpan="3" Stroke="#666666" Fill="#00000000" StrokeDashArray=".2 5" StrokeDashCap="Round" IsHitTestVisible="false" Opacity="0" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="HorizontalDecrementTemplate">
<Grid x:Name="RootElement" Background="#00000000">
<Grid.Resources>
<Storyboard x:Key="Normal State" />
<Storyboard x:Key="MouseOver State">
<ColorAnimation Duration="0:0:0.2" Storyboard.TargetName="ButtonColor" Storyboard.TargetProperty="Color" To="#FF557E9A" />
</Storyboard>
<Storyboard x:Key="Disabled State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="ButtonVisual" Storyboard.TargetProperty="Opacity" To=".6" />
</Storyboard>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35*"/>
<ColumnDefinition Width="30*"/>
<ColumnDefinition Width="35*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25*"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="25*"/>
</Grid.RowDefinitions>
<Path Grid.Column="1" Grid.Row="1" Stretch="Fill" Data="F1 M 110.692,342.252L 110.692,352.682L 104.594,347.467L 110.692,342.252 Z ">
<Path.Fill>
<SolidColorBrush x:Name="ButtonColor" Color="#FF313131" />
</Path.Fill>
</Path>
<Rectangle x:Name="FocusVisualElement" Grid.ColumnSpan="3" Grid.RowSpan="3" Stroke="#666666" Fill="#00000000" StrokeDashArray=".2 5" StrokeDashCap="Round" IsHitTestVisible="false" Opacity="0" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="VerticalIncrementTemplate">
<Grid x:Name="RootElement" Background="#00000000">
<Grid.Resources>
<Storyboard x:Key="Normal State" />
<Storyboard x:Key="MouseOver State">
<ColorAnimation Duration="0:0:0.2" Storyboard.TargetName="ButtonColor" Storyboard.TargetProperty="Color" To="#FF557E9A" />
</Storyboard>
<Storyboard x:Key="Disabled State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="ButtonVisual" Storyboard.TargetProperty="Opacity" To=".6" />
</Storyboard>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="35*"/>
<RowDefinition Height="30*"/>
<RowDefinition Height="35*"/>
</Grid.RowDefinitions>
<Path Grid.Column="1" Grid.Row="1" Stretch="Fill" Data="F1 M 531.107,321.943L 541.537,321.943L 536.322,328.042L 531.107,321.943 Z ">
<Path.Fill>
<SolidColorBrush x:Name="ButtonColor" Color="#FF313131" />
</Path.Fill>
</Path>
<Rectangle x:Name="FocusVisualElement" Grid.ColumnSpan="3" Grid.RowSpan="3" Stroke="#666666" Fill="#00000000" StrokeDashArray=".2 5" StrokeDashCap="Round" IsHitTestVisible="false" Opacity="0" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="VerticalDecrementTemplate">
<Grid x:Name="RootElement" Background="#00000000">
<Grid.Resources>
<Storyboard x:Key="Normal State" />
<Storyboard x:Key="MouseOver State">
<ColorAnimation Duration="0:0:0.2" Storyboard.TargetName="ButtonColor" Storyboard.TargetProperty="Color" To="#FF557E9A" />
</Storyboard>
<Storyboard x:Key="Disabled State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="ButtonVisual" Storyboard.TargetProperty="Opacity" To=".6" />
</Storyboard>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="25*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="35*"/>
<RowDefinition Height="30*"/>
<RowDefinition Height="35*"/>
</Grid.RowDefinitions>
<Path Grid.Column="1" Grid.Row="1" Stretch="Fill" Data="F1 M 541.537,173.589L 531.107,173.589L 536.322,167.49L 541.537,173.589 Z ">
<Path.Fill>
<SolidColorBrush x:Name="ButtonColor" Color="#FF313131" />
</Path.Fill>
</Path>
<Rectangle x:Name="FocusVisualElement" Grid.ColumnSpan="3" Grid.RowSpan="3" Stroke="#666666" Fill="#00000000" StrokeDashArray=".2 5" StrokeDashCap="Round" IsHitTestVisible="false" Opacity="0" />
</Grid>
</ControlTemplate>
<!-- Thumb Templates -->
<ControlTemplate x:Key="VerticalThumbTemplate">
<Grid x:Name="RootElement">
<Grid.Resources>
<!--Colors -->
<Color x:Key="ThumbForegroundColor">#ACAC39</Color>
<Color x:Key="ThumbHoverColor">#73AC39</Color>
<!--Storyboards-->
<Storyboard x:Key="Normal State" />
<Storyboard x:Key="MouseOver State">
<ColorAnimation Duration="0:0:0.1" Storyboard.TargetName="ThumbForeground" Storyboard.TargetProperty="Color" To="{StaticResource ThumbHoverColor}" />
</Storyboard>
<Storyboard x:Key="Disabled State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="ThumbForeground" Storyboard.TargetProperty="Opacity" To="0" />
</Storyboard>
</Grid.Resources>
<Grid x:Name="ThumbVisual">
<Rectangle x:Name="Background" Margin="3.5,.5,3.5,.5" RadiusX="5" RadiusY="5" >
<Rectangle.Fill>
<SolidColorBrush x:Name="ThumbForeground" Color="{StaticResource ThumbForegroundColor}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="HorizontalThumbTemplate">
<Grid x:Name="RootElement">
<Grid.Resources>
<!--Colors-->
<Color x:Key="ThumbForegroundColor">#ACAC39</Color>
<Color x:Key="ThumbHoverColor">#73AC39</Color>
<!--Storyboards-->
<Storyboard x:Key="Normal State" />
<Storyboard x:Key="MouseOver State">
<ColorAnimation Duration="0:0:0.1" Storyboard.TargetName="ThumbForeground" Storyboard.TargetProperty="Color" To="{StaticResource ThumbHoverColor}" />
</Storyboard>
<Storyboard x:Key="Disabled State">
<DoubleAnimation Duration="0:0:0" Storyboard.TargetName="ThumbForeground" Storyboard.TargetProperty="Opacity" To="0" />
</Storyboard>
</Grid.Resources>
<Grid x:Name="ThumbVisual">
<Rectangle x:Name="Background" Margin=".5,3.5,.5,3.5" RadiusX="5" RadiusY="5" >
<Rectangle.Fill>
<SolidColorBrush x:Name="ThumbForeground" Color="{StaticResource ThumbForegroundColor}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</Grid>
</ControlTemplate>
</Grid.Resources>
<!-- Horizontal Template -->
<Grid x:Name="HorizontalRootElement" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<!-- Track Layer -->
<Rectangle Margin="0,6,0,6" Grid.RowSpan="2" Grid.Column="1" Grid.ColumnSpan="3" Fill="#FF404040" RadiusX="3" RadiusY="3" />
<!-- Repeat Buttons + Thumb -->
<RepeatButton x:Name="HorizontalSmallDecreaseElement" Grid.Column="0" Grid.RowSpan="2" Width="16" IsTabStop="False" Interval="50" Template="{StaticResource HorizontalDecrementTemplate}" />
<RepeatButton x:Name="HorizontalLargeDecreaseElement" Grid.Column="1" Grid.RowSpan="2" Width="0" Template="{StaticResource RepeatButtonTemplate}" Interval="50" IsTabStop="False" />
<Thumb x:Name="HorizontalThumbElement" MinWidth="10" Width="20" Grid.Column="2" Grid.RowSpan="2" Template="{StaticResource HorizontalThumbTemplate}" />
<RepeatButton x:Name="HorizontalLargeIncreaseElement" Grid.Column="3" Grid.RowSpan="2" Template="{StaticResource RepeatButtonTemplate}" Interval="50" IsTabStop="False" />
<RepeatButton x:Name="HorizontalSmallIncreaseElement" Grid.Column="4" Grid.RowSpan="2" Width="16" IsTabStop="False" Interval="50" Template="{StaticResource HorizontalIncrementTemplate}" />
</Grid>
<!-- Vertical Template -->
<Grid x:Name="VerticalRootElement" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Track Layer -->
<Rectangle Margin="6,0,6,0" Grid.ColumnSpan="2" Grid.Row="1" Grid.RowSpan="3" Fill="#FF404040" RadiusX="3" RadiusY="3" />
<!-- Repeat Buttons + Thumb -->
<RepeatButton x:Name="VerticalSmallDecreaseElement" Grid.Row="0" Grid.ColumnSpan="2" Height="16" IsTabStop="False" Interval="50" Template="{StaticResource VerticalDecrementTemplate}" />
<RepeatButton x:Name="VerticalLargeDecreaseElement" Grid.Row="1" Grid.ColumnSpan="2" Height="0" Template="{StaticResource RepeatButtonTemplate}" Interval="50" IsTabStop="False" />
<Thumb x:Name="VerticalThumbElement" MinHeight="10" Height="20" Grid.Row="2" Grid.ColumnSpan="2" Template="{StaticResource VerticalThumbTemplate}" />
<RepeatButton x:Name="VerticalLargeIncreaseElement" Grid.Row="3" Grid.ColumnSpan="2" Template="{StaticResource RepeatButtonTemplate}" Interval="50" IsTabStop="False" />
<RepeatButton x:Name="VerticalSmallIncreaseElement" Grid.Row="4" Grid.ColumnSpan="2" Height="16" IsTabStop="False" Interval="50" Template="{StaticResource VerticalIncrementTemplate}" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="ScrollViewer" x:Key="ScrollViewerStyle1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Border>
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollContentPresenter
x:Name="ScrollContentPresenterElement"
Grid.Column="0"
Grid.Row="0"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Cursor="{TemplateBinding Cursor}"
Background="{TemplateBinding Background}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontStretch="{TemplateBinding FontStretch}"
FontStyle="{TemplateBinding FontStyle}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
TextAlignment="{TemplateBinding TextAlignment}"
TextDecorations="{TemplateBinding TextDecorations}"
TextWrapping="{TemplateBinding TextWrapping}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
/>
<ScrollBar
x:Name="VerticalScrollBarElement"
Grid.Column="1"
Grid.Row="0"
Orientation="Vertical"
Cursor="Arrow"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
ViewportSize="{TemplateBinding ViewportHeight}"
Minimum="0"
Maximum="{TemplateBinding ScrollableHeight}"
Value="{TemplateBinding VerticalOffset}"
Style="{StaticResource ScrollBarStyle1}"
Width="18"/>
<ScrollBar
x:Name="HorizontalScrollBarElement"
Grid.Column="0"
Grid.Row="1"
Orientation="Horizontal"
Cursor="Arrow"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
ViewportSize="{TemplateBinding ViewportWidth}"
Minimum="0"
Maximum="{TemplateBinding ScrollableWidth}"
Value="{TemplateBinding HorizontalOffset}"
Style="{StaticResource ScrollBarStyle1}"
Height="18"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem">
<Setter Property="Foreground" Value="#BDBC97" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid x:Name="RootElement">
<Grid.Resources>
<Storyboard x:Key="Normal State" />
<!-- <Storyboard x:Key="Unfocused Selected State"/> -->
<Storyboard x:Key="Normal Selected State">
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" Duration="0" To="#FF397F8F"/>
</Storyboard>
<Storyboard x:Key="MouseOver State">
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" Duration="0" To="#FF898A8A"/>
</Storyboard>
<!-- <Storyboard x:Key="MouseOver Unfocused Selected State"/> -->
<Storyboard x:Key="MouseOver Selected State">
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color" Duration="0" To="#FF898A8A"/>
</Storyboard>
</Grid.Resources>
<Border CornerRadius="5" Margin="1">
<Border.Background>
<SolidColorBrush x:Name="BackgroundBrush" Color="#51615B" />
</Border.Background>
<ContentPresenter
Margin="5,1"
Content="{TemplateBinding Content}"
Foreground="{TemplateBinding Foreground}"
/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ListBoxStyle1" TargetType="ListBox">
<Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItemStyle1}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Grid x:Name="LayoutRoot">
<Border Padding="3" Background="#E6BB8A" CornerRadius="5">
<ScrollViewer x:Name="ScrollViewerElement"
Style="{StaticResource ScrollViewerStyle1}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter />
</ScrollViewer>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" >
<ListBox
HorizontalAlignment="Left"
Margin="8,8,0,0"
VerticalAlignment="Top"
Style="{StaticResource ListBoxStyle1}" Width="162" Height="161"
>
<ListBoxItem Content="Hello"/>
<ListBoxItem Content="World"/>
<ListBoxItem Content="Item 3"/>
<ListBoxItem Content="Item 4 with a really long name"/>
<ListBoxItem Content="Item 5"/>
<ListBoxItem Content="Item 6"/>
<ListBoxItem Content="Item 7"/>
<ListBoxItem Content="Item 8"/>
<ListBoxItem Content="Item 9 out of bounds"/>
<ListBoxItem Content="Item 10 out of bounds"/>
</ListBox>
</Grid>
</UserControl>
Comments
The mouseover / selected etc events as explained above don't seem to work on silverlight 2 Beta 2. I'm guessing now they are using the visual state manager, but how does it hook in to the ListBoxItem...
JamesHi James,
Mark HI'm updating this series for beta 2, so check back in a week or so for a working version