This article is compatible with the latest version of Silverlight.
Introduction
When we talk about rich internet application and have in mind that we can use Silverlight, we are able to make really good-looking applications full of animation and other effects.
Here, I will demonstrate how to apply some animations on the ListBox items when loading, selecting and unselecting them. You can also check the second part of this article - Animating ListBox Items - the VisualStateManager.
Download source code
Overview
ListBox control comes with the Silverlight 2 and it is used to display items and allow the user to select one or more of them. The ItemsSource property allows you to specify the items which should be displayed. It is of IEnumerable type.
For the purpose of the demo, I will create a simple class representing a client and I will initialize the ItemsSource with a list of clients. I will also make a private method that returns List<Clients> with 10 clients taken from Northwind database.
The Client class has the following properties:
public
class
Client
{
public
string
Thumbnail{
get
;
set
;}
public
int
Id{
get
;
set
;}
public
string
Name{
get
;
set
;}
public
string
Email{
get
;
set
;}
public
string
Phone{
get
;
set
;}
public
string
Address{
get
;
set
;}
public
string
PostalCode {
get
;
set
; }
public
string
Country{
get
;
set
; }
}
In a new project, in the page XAML I’ll add a ListBox control, and I’ll set DisplayMemberPath=”Name”. Thus the text will be taken from the Name property of my Client class when the items are displayed:
<
ListBox
x:Name
=
"lbClients"
Grid.Row
=
"0"
Width
=
"400"
HorizontalAlignment
=
"Center"
VerticalAlignment
=
"Stretch"
DisplayMemberPath
=
"Name"
>
</
ListBox
>
As you can see, each item is shown as a single line of text. But I would like to show the items in a more friendly way – the client’s image on the left followed by the client’s name. Of course some nice background would be a good idea. To do that, I will define a template which will be used for each template item. This template will contain a main StackPanel with horizontal orientation. This panel will have two child elements – Border and another StackPanel. The Border will contain the image of the client while the other StackPanel will have text blocks vertically oriented – for now only one will show the client’s name.
<
ListBox
x:Name
=
"lbClients"
Grid.Row
=
"0"
Width
=
"400"
HorizontalAlignment
=
"Center"
VerticalAlignment
=
"Stretch"
>
<
ListBox.ItemTemplate
>
<
DataTemplate
>
<
StackPanel
Orientation
=
"Horizontal"
Height
=
"50"
Width
=
"372"
Margin
=
"2"
>
<
StackPanel.Background
>
<
LinearGradientBrush
EndPoint
=
"0.5,1"
StartPoint
=
"0.5,0"
>
<
GradientStop
Color
=
"#FFFFFFFF"
Offset
=
"0"
/>
<
GradientStop
Color
=
"#FFC5D7F6"
Offset
=
"0.683"
/>
</
LinearGradientBrush
>
</
StackPanel.Background
>
<
Border
Background
=
"White"
CornerRadius
=
"5"
>
<
Image
Source
=
"{Binding Thumbnail}"
Width
=
"50"
Height
=
"50"
></
Image
>
</
Border
>
<
StackPanel
Orientation
=
"Vertical"
Margin
=
"2"
>
<
TextBlock
Text
=
"{Binding Name}"
FontSize
=
"32"
Height
=
"50"
></
TextBlock
>
</
StackPanel
>
</
StackPanel
>
</
DataTemplate
>
</
ListBox.ItemTemplate
>
</
ListBox
>
As a result the ListBox displays the items using the template:
Animating
Having a template I can control the way the data is shown, but it is still static. What I would like to see is some animation when something is happening with the ListBox items. I’ll start with the animation of the items when they are loaded. Here I need a Storyboard which to apply on the item in order to get it animated. I’ll add one Storyboard with name ClientItem_Loaded as a resource to the main StackPanel, whose purpose will be to change the Opacity value from 0 to 1:
<
StackPanel.Resources
>
<
Storyboard
x:Name
=
"ClientItem_Loaded"
BeginTime
=
"00:00:00"
>
<
DoubleAnimation
Storyboard.TargetProperty
=
"Opacity"
From
=
"0"
To
=
"1"
Duration
=
"00:00:01.0000000"
/>
</
Storyboard
>
</
StackPanel.Resources
>
To avoid the flick on first load, I will set the Opacity value of the main StackPanel to be 0 and I will also attach an event handler to the Loaded event – more details in a sec:
<
StackPanel
Orientation
=
"Horizontal"
Height
=
"50"
Width
=
"372"
Margin
=
"2"
Opacity
=
"0"
Loaded
=
"ClientItem_Loaded"
>
After the changes, the xaml looks like this:
<
ListBox
x:Name
=
"lbClients"
Grid.Row
=
"0"
Width
=
"400"
HorizontalAlignment
=
"Center"
VerticalAlignment
=
"Stretch"
>
<
ListBox.ItemTemplate
>
<
DataTemplate
>
<
StackPanel
Orientation
=
"Horizontal"
Height
=
"50"
Width
=
"371"
Margin
=
"2"
Opacity
=
"0"
Loaded
=
"ClientItem_Loaded"
>
<
StackPanel.Resources
>
<
Storyboard
x:Name
=
"ClientItem_Loaded"
BeginTime
=
"00:00:00"
>
<
DoubleAnimation
Storyboard.TargetProperty
=
"Opacity"
From
=
"0"
To
=
"1"
Duration
=
"00:00:01.0000000"
/>
</
Storyboard
>
</
StackPanel.Resources
>
<
StackPanel.Background
>
<
LinearGradientBrush
EndPoint
=
"0.5,1"
StartPoint
=
"0.5,0"
>
<
GradientStop
Color
=
"#FFFFFFFF"
Offset
=
"0"
/>
<
GradientStop
Color
=
"#FFC5D7F6"
Offset
=
"0.683"
/>
</
LinearGradientBrush
>
</
StackPanel.Background
>
<
Border
Background
=
"White"
CornerRadius
=
"5"
>
<
Image
Source
=
"{Binding Thumbnail}"
Width
=
"50"
Height
=
"50"
></
Image
>
</
Border
>
<
StackPanel
Orientation
=
"Vertical"
Margin
=
"2"
>
<
TextBlock
Text
=
"{Binding Name}"
FontSize
=
"32"
Height
=
"50"
></
TextBlock
>
</
StackPanel
>
</
StackPanel
>
</
DataTemplate
>
</
ListBox.ItemTemplate
>
</
ListBox
>
But how to start the animation automatically? I need ClientItem_Loaded event handler:
private
void
ClientItem_Loaded(
object
sender, RoutedEventArgs e )
{
this
.StartAnimationFromResource(
"ClientItem_Loaded"
, sender
as
FrameworkElement );
( ( StackPanel )sender ).Loaded -=
this
.ClientItem_Loaded;
}
When a ListBox item is loaded the event handler will be executed, which will execute a private method named StartAnimationFromResource:
private
void
StartAnimationFromResource(
string
animationName, FrameworkElement element )
{
Storyboard sb = element.Resources[ animationName ]
as
Storyboard;
Storyboard.SetTarget( sb, element );
sb.Begin();
}
StartAnimationFromResource takes two parameters – the name of the Storyboard and a FrameworkElement. Then, I search inside the element’s resources for a resource with animatedName. For the element I also initialize the Name property and after that I set the target name of the StoryBoard to the name of the element - otherwise I would get an exception. The last step is to run the animation. This event is called for each ListBox item, so all the items get animated.
If you run the application at this stage, you will see how the items will be shown. Still, I want to add more dynamic on the items. My wish is to have the client’s image slide from the left side of the item while the text comes from the right. To achieve this, I’ll add a Storyboard named ClientItemImage_Load, which will move the Border by X from -50 to 0:
<
Border
Background
=
"White"
CornerRadius
=
"5"
Loaded
=
"ClientItemImage_Loaded"
>
<
Border.Resources
>
<
Storyboard
x:Name
=
"ClientItemImage_Loaded"
BeginTime
=
"00:00:00"
>
<
DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty
=
"(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"
BeginTime
=
"00:00:00"
>
<
SplineDoubleKeyFrame
KeyTime
=
"00:00:00"
Value
=
"-50"
/>
<
SplineDoubleKeyFrame
KeyTime
=
"00:00:00.6000000"
Value
=
"0"
>
<
SplineDoubleKeyFrame.KeySpline
>
<
KeySpline
ControlPoint1
=
"0,1"
ControlPoint2
=
"0.495000004768372,1"
/>
</
SplineDoubleKeyFrame.KeySpline
>
</
SplineDoubleKeyFrame
>
</
DoubleAnimationUsingKeyFrames
>
</
Storyboard
>
</
Border.Resources
>
<
Border.RenderTransform
>
<
TransformGroup
>
<
ScaleTransform
/>
<
SkewTransform
/>
<
RotateTransform
/>
<
TranslateTransform
/>
</
TransformGroup
>
</
Border.RenderTransform
>
<
Image
Source
=
"{Binding Thumbnail}"
Width
=
"50"
Height
=
"50"
></
Image
>
</
Border
>
The animation needs some transformation to be applied and that’s why I added TransformGroup. Otherwise here again I would get an exception. For the StackPanel with the textual information I will follow the same logic: moving by X from 250 down to 1. You can see that in the Page.xaml in the demo project.
Again, I catch the Loaded event of the elements and apply the animation stored as resources for these elements by calling StartAnimationFromResource.
Selecting Item
Up to now we have put animations on the ListBox items which are started automatically when the item is loaded. Now I’ll demonstrate how to start different animations as a reaction of user’s activity. I would like, on the one hand, when the user selects an item, this item to show more from the client’s information. On the other hand, I would like when the item is unselected to show only the client’s name.
In order to show more from the client’s data, I’ll add a few more text blocks with a two-columns layout:
<
TextBlock
Text
=
"{Binding Name}"
FontSize
=
"32"
Height
=
"50"
></
TextBlock
>
<
Grid
HorizontalAlignment
=
"Stretch"
>
<
Grid.RowDefinitions
>
<
RowDefinition
/>
<
RowDefinition
/>
</
Grid.RowDefinitions
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
Width
=
"200"
/>
<
ColumnDefinition
Width
=
"*"
/>
</
Grid.ColumnDefinitions
>
<
StackPanel
Orientation
=
"Horizontal"
Grid.Column
=
"0"
Grid.Row
=
"0"
>
<
TextBlock
Text
=
"{Binding Country}"
FontSize
=
"9"
/>
<
TextBlock
Text
=
", "
FontSize
=
"9"
/>
<
TextBlock
Text
=
"{Binding PostalCode}"
FontSize
=
"9"
/>
</
StackPanel
>
<
TextBlock
Text
=
"{Binding Address}"
FontSize
=
"9"
Grid.Column
=
"0"
Grid.Row
=
"1"
/>
<
TextBlock
Text
=
"{Binding Phone}"
FontSize
=
"9"
Grid.Column
=
"1"
Grid.Row
=
"0"
/>
<
TextBlock
Text
=
"{Binding Email}"
FontSize
=
"9"
Grid.Column
=
"1"
Grid.Row
=
"1"
/>
</
Grid
>
Notice, that I set the height of the first text block – the client’s name – to be as height as the main panel height. Thus I’ll hide the second and the third text blocks.
Now I will add to the main StackPanel two more resources – two Storyboards containing the animation for selecting and unselecting item:
<
Storyboard
x:Name
=
"ClientsListAnimation_Select"
>
<
DoubleAnimation
Storyboard.TargetProperty
=
"(TextBlock.FontSize)"
From
=
"32"
To
=
"16"
Duration
=
"00:00:00.7000000"
/>
<
DoubleAnimation
Storyboard.TargetProperty
=
"(TextBlock.Height)"
From
=
"50"
To
=
"22"
Duration
=
"00:00:00.7000000"
/>
</
Storyboard
>
<
Storyboard
x:Name
=
"ClientsListAnimation_Unselect"
>
<
DoubleAnimation
Storyboard.TargetProperty
=
"(TextBlock.FontSize)"
From
=
"16"
To
=
"32"
Duration
=
"00:00:00.7000000"
/>
<
DoubleAnimation
Storyboard.TargetProperty
=
"(TextBlock.Height)"
From
=
"22"
To
=
"50"
Duration
=
"00:00:00.7000000"
/>
</
Storyboard
>
These animations simply deal with the size of the first text block by changing it from 50 to 22 when the item is selected and from 22 back to 50 - when the item is unselected. Additionally the font size is changed from 32 to 16 and vice versa. And now, I have to find a way to start these animations when needed. Because the ListBox doesn’t provide me with appropriate properties and events, I will attach an event handler on the main StackPanel for MouseLeftButtonDown:
<
StackPanel
Orientation
=
"Horizontal"
Height
=
"50"
Width
=
"400"
Margin
=
"2"
Opacity
=
"0"
Loaded
=
"ClientItem_Loaded"
MouseLeftButtonDown
=
"StackPanel_ ClientItem_Loaded_MouseLeftButtonDown"
>
The code behind for the event is:
private
FrameworkElement selectedItem;
private
void
ClientItem_Loaded_MouseLeftButtonDown(
object
sender, MouseButtonEventArgs e )
{
if
(
this
.selectedItem == sender )
return
;
Storyboard sb;
TextBlock target;
if
(
this
.selectedItem !=
null
)
{
target = ( ( System.Windows.Controls.Panel )( ( ( System.Windows.Controls.Panel )(
this
.selectedItem ) ).Children[ 1 ] ) ).Children[ 0 ]
as
TextBlock;
sb = selectedItem.Resources[
"ClientsListAnimation_Select"
]
as
Storyboard;
if
( sb.GetCurrentState() != ClockState.Stopped )
{
sb.Stop();
}
sb = selectedItem.Resources[
"ClientsListAnimation_Unselect"
]
as
Storyboard;
Storyboard.SetTarget( sb, target );
sb.Begin();
}
selectedItem = sender
as
FrameworkElement;
sb = selectedItem.Resources[
"ClientsListAnimation_Unselect"
]
as
Storyboard;
if
( sb.GetCurrentState() != ClockState.Stopped )
{
sb.Stop();
}
sb = selectedItem.Resources[
"ClientsListAnimation_Select"
]
as
Storyboard;
target = ( ( System.Windows.Controls.Panel )( ( ( System.Windows.Controls.Panel )( ( ( FrameworkElement )sender ) ) ).Children[ 1 ] ) ).Children[ 0 ]
as
TextBlock;
Storyboard.SetTarget( sb, target );
sb.Begin();
e.Handled =
false
;
}
I use the same idea of running animation as in StartAnimationFromResource(), but here I also keep the last selected item in order to run the unselected animation when new item is selected. Additionally, I set the Handled property of the MouseButtonEventArg to false because I need the ListBox control to do the real selection job for me. As a result, when an item is selected the user will see more details for him/her:
You can see now how easy is to apply the animation over ListBox item. You can play more with different data templates and animations just following the ideas shown in this article.
Conclusion
Silverlight provides us with plenty of new ways to build rich internet applications. Even with its beta version we can make some tricky things to work around the still missing functionality and thus making our applications looking cooler.
References
ListBox Class on MSDN
http://msdn.microsoft.com/en-us/library/system.windows.controls.listbox(VS.95).aspx
Using the ListBox and DataBinding to Display List Data
http://weblogs.asp.net/scottgu/pages/silverlight-tutorial-part-5-using-the-listbox-and-databinding-to-display-list-data.aspx
Changing Itemtemplate to show details in when selected
http://leeontech.wordpress.com/2008/05/11/changing-itemtemplate/
Summary and detail in listbox
http://leeontech.wordpress.com/2008/03/31/styling-listbox/