(X) Hide this
    • Login
    • Join
      • Say No Bots Generate New Image
        By clicking 'Register' you accept the terms of use .

Animating ListBox items

(4 votes)
Ivan Dragoev
>
Ivan Dragoev
Joined Oct 25, 2007
Articles:   7
Comments:   9
More Articles
12 comments   /   posted on Jun 02, 2008
Categories:   Controls

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>

clients without template

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/


Subscribe

Comments

  • -_-

    RE: Animating ListBox items


    posted by Ruurd Boeke on Jun 03, 2008 15:35

    Nice! However, since you can select an item through keyboard as well, it might be better to react to some event like SelectedItemChanged if such an event exists. Now there is only reaction to the mousebutton.

  • idragoev

    RE: Animating ListBox items


    posted by idragoev on Jun 04, 2008 00:33

    Hi Ruurd,

    Thanks for the feedback! I plan to continue with another article covering this issues and some other also. So expect more soon.

    Ivan

  • -_-

    RE: Animating ListBox items


    posted by Michael Washington on Jun 04, 2008 08:52

    Oh this is nice :) Very good use of the power that Silverlight has to offer. I will try to make a module based on thsi for the next release of SilverlightDesktop.net

  • -_-

    RE: Animating ListBox items


    posted by lee on Jun 11, 2008 07:23

    Did you get a chance to convert this to work in Beta2. as per your comment on  my blog I was trying to convert this to use VSM and just want to see a working version. if I get it to work (atleast partly) do you mind if I post  the sample on my blog?

    Thanks

  • idragoev

    RE: Animating ListBox items


    posted by idragoev on Jun 12, 2008 00:14

    Hi, Im currently working on second article where I will show VSM and some limitations of it that I faced.

  • idragoev

    RE: Animating ListBox items


    posted by idragoev on Jun 13, 2008 01:17

    Update: Check the second part of this article - Animating ListBox Items - the VisualStateManager

  • -_-

    RE: Animating ListBox items


    posted by John "Z-Bo" Zabroski on Jun 30, 2008 06:49

    You may want to update this article or tag it with "Silverlight 2 Beta 1".

  • idragoev

    RE: Animating ListBox items


    posted by idragoev on Jun 30, 2008 08:42

    10x, John, the Introduction section was updated to reflect the Silverlight version needed to run this example.

  • lnikolov

    RE: Animating ListBox items


    posted by lnikolov on Jan 07, 2011 13:28
    The article has been updated to the latest version of Silverlight and Visual Studio.
  • MatthewBrown

    Re: Animating ListBox items


    posted by MatthewBrown on Oct 14, 2011 20:34

    I apologize for bringing this topic back up, but my "selectedItem" is always null when I click the second item.  It stores the selected item just fine, but the next time around it's null again.  This is causing the "unselect" animation not to work.  Any ideas why the private variable "selectedItem" is not keeping it's value?  TIA

  • MatthewBrown

    Re: Animating ListBox items


    posted by MatthewBrown on Oct 14, 2011 20:36

    Well, two seconds after I posted my last comment I figured it out.  I declare selectedItem as "private static FrameworkElement selectedItem" and replace any reference of "this.selectedItem" with "selectedItem".

  • Fensterbank

    Re: Animating ListBox items


    posted by Fensterbank on May 30, 2012 00:10

    Hi,

    thanks for this nice tutorial.
    But I have following problem:

    I have a ListBox, bound to an ObservableCollection<>.
    Each time, an Item is added, the Content-StackPanel in the ListBox should grow from Height 0 to Height 150.

    So the ListBox should grow with new items "driving in".

    But the ClientItem_Loaded(object sender, RoutedEventArgs e) gets only called for the first 20 Items (when bind the Listbox.ItemSource to the Collection). All other added items dont fire the event and appears with Height 150.

    The essential stuff of my code looks like yours, do you know, whats the problem?

    Thanks,

    Fensterbank

Add Comment

Login to comment:
  *      *