Click here to Skip to main content
11,502,547 members (64,811 online)
Click here to Skip to main content

WPF Tutorial - Part 2 : Writing a custom animation class

, , 12 Apr 2007 CPOL 141.3K 4.8K 79
Rate this:
Please Sign up or sign in to vote.
This article covers how animations can be applied on properties that do not have an associated animation class

Introduction

When we (Christian and Nish) started our WPF series last July, we didn't exactly plan to have a 9 month gap between parts 1 and 2. We were both busy with various projects and we kept procrastinating writing the next part. Well, now we are ready with the 2nd part of our WPF series and as they say - better late than never. In this article, we will talk about how animations can be applied on properties that do not have an associated animation class, and we will specifically focus on animating the GridLength property with respect to a Grid control's columns and rows.

Trouble with animating a Grid's columns and rows

Before we get into animating the GridLength, we'll look at why it's different from animating a property such as Width or Opacity. And while we expect you to have a basic understanding of how animations work in WPF, let's briefly look at how regular animations work, where by regular we mean animating properties that have associated animation classes.

How do regular animations work?

Typical usages of animations involve changing a property (usually a dependency property) over a specific duration via linear interpolation. As an example, the following Xaml shows how a button's opacity can be animated from fully opaque to fully transparent.

<Button Name="button1">
One
<Button.Triggers>
  <EventTrigger RoutedEvent="Button.Click">
    <BeginStoryboard>
      <Storyboard>
        <DoubleAnimation
          Storyboard.TargetName="button2"
          Storyboard.TargetProperty="Opacity"
          From="1" To="0" Duration="0:0:2" />
      </Storyboard>
    </BeginStoryboard>
  </EventTrigger>
</Button.Triggers>
</Button>

We could specify double values 1 and 0 as start and stop values for the Opacity property. The DoubleAnimation class that we used is specialized to work with properties of type double (and Opacity, Width, Length etc. are all of type double). If you look at the System.Windows.Media.Animation namespace, you'll see that there are other specialized animation classes for handling values of other common types such as Boolean, Char, Byte, Color, Point etc. Next lets look at why the Grid's column and row dimensions cannot be animated this way.

Why can't regular animations work with a Grid?

The ColumnDefinition and RowDefinition classes have Width and Height properties (respectively) of type GridLength. The GridLength is a struct whose purpose is to support Star-based units in addition to pixel-based units. Star-units specify a dimension as a weighted proportion of the total available space. So if you have two columns, where the first has a width {*} and the second has a width of {3*}, the first column will take up 25% of the total width of the containing panel, while the second column will take up the remaining 75% of space. This offers us a lot of flexibility when using grids and we don't need to specify hard coded values - which has the added advantage that it's easier to add rows and columns in future.

The side-effect of the fact that the Grid uses GridLength for dimensions is that we cannot use any of the library's built-in animation classes with it since none of them were intended to support GridLength. But that does not mean there's nothing we can do about it. We can (and will) write an animation class specifically for handling units of type GridLength.

Writing a custom animation class for GridLength

Our aim is write a GridLengthAnimation class that will support animations based on the GridLength property. To keep the example simple and to the point, we will only support the From and To properties, and will not support properties such as By, which are supported by other classes such as DoubleAnimation. It would be trivial to add a By property and this is left as an exercise for the reader (shouldn't take you more than a few minutes).

We derive a class from AnimationTimeline which represents a time line over which values are produced (in our case we'll produce GridLength values between the From and To range).

namespace GridAnimationDemo
{
    internal class GridLengthAnimation : AnimationTimeline
    {

We have to override the TargetPropertyType property which is abstract in AnimationTimeline. This is a get-only property that returns the type of the property that will be animated across a range of supported values. Our implementation is simple and returns the type of the GridLength object.

public override Type TargetPropertyType
{
    get 
    {
        return typeof(GridLength);
    }
}

AnimationTimeLine has a protected constructor, and thus any animation object that derives from it has to be created indirectly. Animation classes indirectly derive from Freezable , which defines objects that have two states - mutable(unfrozen) and immutable(frozen). Such classes need to implement (override) a CreateInstanceCore method which will be used to construct the animation (freezable) object. CreateInstanceCore will be called by GetCurrentValueAsFrozenCore to return a freezable clone of the current object (which may or may not be in a frozen state at that moment). Again our implementation is very simple.

protected override System.Windows.Freezable CreateInstanceCore()
{
    return new GridLengthAnimation();
}

Next, we'll add two dependency properties to handle From and To properties. For an excellent write-up on dependency properties outside of MSDN, read WPF guru and MVP Josh Smith's blog entry on this topic : Dependency Properties by Josh Smith. The implementation is straightforward and there's nothing special to be done here.

static GridLengthAnimation()
{
    FromProperty = DependencyProperty.Register("From", typeof(GridLength),
        typeof(GridLengthAnimation));

    ToProperty = DependencyProperty.Register("To", typeof(GridLength), 
        typeof(GridLengthAnimation));
}
public static readonly DependencyProperty FromProperty;
public GridLength From
{
    get
    {
        return (GridLength)GetValue(GridLengthAnimation.FromProperty);
    }
    set
    {
        SetValue(GridLengthAnimation.FromProperty, value);
    }
}
public static readonly DependencyProperty ToProperty;
public GridLength To
{
    get
    {
        return (GridLength)GetValue(GridLengthAnimation.ToProperty);
    }
    set
    {
        SetValue(GridLengthAnimation.ToProperty, value);
    }
}

Now all that's left is to override GetCurrentValue and return the current animated value of the property that's being animated.

public override object GetCurrentValue(object defaultOriginValue, 
    object defaultDestinationValue, AnimationClock animationClock)
{
    double fromVal = ((GridLength)GetValue(GridLengthAnimation.FromProperty)).Value;
    double toVal = ((GridLength)GetValue(GridLengthAnimation.ToProperty)).Value;

    if (fromVal > toVal)
    {
        return new GridLength((1 - animationClock.CurrentProgress.Value) *
            (fromVal - toVal) + toVal, GridUnitType.Star);
    }
    else
    {
        return new GridLength(animationClock.CurrentProgress.Value *
            (toVal - fromVal) + fromVal, GridUnitType.Star);
    }
}

What we do is calculate and return a gradated value based on the current value of the AnimationClock object - which will be between 0 and 1. We create a GridLength object by using the constructor that accepts a GridUnitType as the second argument for which we specify Star. That's it - our GridLengthAnimation class is ready, and we'll now see how it can be put to use.

Class usage

Let's look at how the class can be used from both procedural code and from Xaml.

The sample app

The sample project has a grid with three rows and two columns and each cell has an image. Note that all six photos shown in the screen shot and available in the project zip were taken by Nish, and those images are royalty free and may be reused in whatever legitimate way the reader needs to. You can click on any of the six images and that cell will animate to fill the window, while the other cells will diminish in size till they vanish. And if you click on the maximized image, the reverse animation occurs - where the current image sizes back to its original dimensions, and the other cells will obviously increase at the same time, until they return to their starting positions. The GridLengthAnimation class is used from procedural code in the demo project as shown below.

void image_MouseDown(object sender, MouseButtonEventArgs e)
{
    Image image = sender as Image;
    if (image != null)
    {                
        int col = Grid.GetColumn(image);
        int row = Grid.GetRow(image);

        for (int indexRow = 0; indexRow < mainGrid.RowDefinitions.Count; 
            indexRow++)
        {
            if (indexRow != row)
            {
                GridLengthAnimation gla = new GridLengthAnimation();
                gla.From = new GridLength(bSingleImageMode 
                    ? 0 : 1, GridUnitType.Star);
                gla.To = new GridLength(bSingleImageMode 
                    ? 1 : 0, GridUnitType.Star); ;
                gla.Duration = new TimeSpan(0, 0, 2);
                mainGrid.RowDefinitions[indexRow].BeginAnimation(
                    RowDefinition.HeightProperty, gla);
            }      

        }

        for (int indexCol = 0; 
            indexCol < mainGrid.ColumnDefinitions.Count; indexCol++)
        {
            if (indexCol != col)
            {
                GridLengthAnimation gla = new GridLengthAnimation();
                gla.From = new GridLength(bSingleImageMode 
                    ? 0 : 1, GridUnitType.Star);
                gla.To = new GridLength(bSingleImageMode 
                    ? 1 : 0, GridUnitType.Star);
                gla.Duration = new TimeSpan(0, 0, 2);
                mainGrid.ColumnDefinitions[indexCol].BeginAnimation(
                    ColumnDefinition.WidthProperty, gla);
            }                  
        }
    }
    bSingleImageMode = !bSingleImageMode;
}

Note that while the demo uses procedural code (since it needs to dynamically apply the animation to the clicked on image cell), you can use it from Xaml too just as you would use any other animation class. Also note how in the sample code, we've iterated through the rows and columns and run animations one after the other. For a more complicated scenario, you would want to create a StoryBoard and have all the animations run in parallel, instead of one after the other as we've done above.

Using from Xaml

Here's some sample Xaml that shows how the GridLengthAnimation class can be used from Xaml to animate a grid's column width.

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Name="Col0" Width="*"/>
    <ColumnDefinition Name="Col1" Width="*"/>
  </Grid.ColumnDefinitions>

  <Button Name="button1">
    One
    <Button.Triggers>
      <EventTrigger RoutedEvent="Button.Click">
        <BeginStoryboard>
          <Storyboard>
            <proj:GridLengthAnimation
              Storyboard.TargetName="Col1"
              Storyboard.TargetProperty="Width"
              From="*" To="2*" Duration="0:0:2" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Button.Triggers>
  </Button>
  
  <Button Name="button2" Grid.Column="1">Two</Button>
</Grid>

History

  • Apr 12, 2007 - Article first published on The Code Project

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors

Christian Graus
Software Developer (Senior)
Australia Australia
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and asp.net in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and I worked for the owners for five years before leaving to get away from the travel, and spend more time with my family. I now work for a company here in Hobart, doing all sorts of Microsoft based stuff in C++ and C#, with a lot of T-SQL in the mix.

Nish Nishant

United States United States
Nish Nishant is a Software Architect/Consultant based out of Columbus, Ohio. He has over 15 years of software industry experience in various roles including Lead Software Architect, Principal Software Engineer, and Product Manager. Nish is a recipient of the annual Microsoft Visual C++ MVP Award since 2002 (13 consecutive awards as of 2014).

Nish is an industry acknowledged expert in the Microsoft technology stack. He authored
C++/CLI in Action for Manning Publications in 2005, and had previously co-authored
Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his
WordPress blog. Nish is vastly experienced in team management, mentoring teams, and directing all stages of software development.

Contact Nish : You can reach Nish on his google email id voidnish.

Website and Blog

Comments and Discussions

 
QuestionI have used it Pin
Petr Ivankov12-Mar-14 5:37
memberPetr Ivankov12-Mar-14 5:37 
QuestionGood!!! I shall use it soon Pin
Petr Ivankov30-Jan-14 18:51
memberPetr Ivankov30-Jan-14 18:51 
QuestionGreat work! Pin
Member 96117632-Jul-13 8:38
memberMember 96117632-Jul-13 8:38 
GeneralMy vote of 5 Pin
Nasenbaaer6-Mar-13 1:33
memberNasenbaaer6-Mar-13 1:33 
QuestionGetCurrentValue - If-Else Pin
Silent Winter5-Mar-13 9:27
memberSilent Winter5-Mar-13 9:27 
GeneralMy vote of 5 Pin
Mark H Peterson25-Oct-12 13:54
memberMark H Peterson25-Oct-12 13:54 
SuggestionCustom Generic Animations Pin
Yury Goltsman27-Feb-12 3:03
memberYury Goltsman27-Feb-12 3:03 
GeneralMy vote of 5 Pin
David Veeneman12-Aug-10 7:35
memberDavid Veeneman12-Aug-10 7:35 
Simple, to the point. Well done!
QuestionWhat About the Value Property? Pin
John Simmons / outlaw programmer2-Apr-10 8:56
memberJohn Simmons / outlaw programmer2-Apr-10 8:56 
AnswerRe: What About the Value Property? Pin
Nishant Sivakumar2-Apr-10 9:20
mvpNishant Sivakumar2-Apr-10 9:20 
QuestionEasing? Pin
Wavioli10-Nov-09 1:09
memberWavioli10-Nov-09 1:09 
AnswerRe: Easing? Pin
ChrisOswald9-Jan-10 11:21
memberChrisOswald9-Jan-10 11:21 
Generalthere is a problem in the class regarding GridUnitType of the output GridLenght Pin
mike10120030-Sep-09 7:55
membermike10120030-Sep-09 7:55 
GeneralRe: there is a problem in the class regarding GridUnitType of the output GridLenght Pin
stumpyfr12-May-10 3:53
memberstumpyfr12-May-10 3:53 
GeneralJust what I needed Pin
david davies30-Apr-09 4:27
memberdavid davies30-Apr-09 4:27 
GeneralGreat Work Pin
Thomas Stockwell3-Jul-08 3:27
memberThomas Stockwell3-Jul-08 3:27 
GeneralRe: Great Work Pin
Nishant Sivakumar3-Jul-08 7:47
mvpNishant Sivakumar3-Jul-08 7:47 
QuestionGridSplitter stops working after animating GridLength Pin
Drew Noakes18-Jul-07 22:02
memberDrew Noakes18-Jul-07 22:02 
AnswerRe: GridSplitter stops working after animating GridLength Pin
Drew Noakes18-Jul-07 22:38
memberDrew Noakes18-Jul-07 22:38 
GeneralRe: GridSplitter stops working after animating GridLength Pin
BrandonWaskiewicz1-Nov-07 3:50
memberBrandonWaskiewicz1-Nov-07 3:50 
GeneralRe: GridSplitter stops working after animating GridLength Pin
Max Palmer18-Jun-08 0:12
memberMax Palmer18-Jun-08 0:12 
GeneralRe: GridSplitter stops working after animating GridLength Pin
davyddevans21-Jan-09 5:00
memberdavyddevans21-Jan-09 5:00 
GeneralRe: GridSplitter stops working after animating GridLength Pin
_Mohammad_20-Apr-10 5:21
member_Mohammad_20-Apr-10 5:21 
GeneralRe: GridSplitter stops working after animating GridLength Pin
Randall Doser6-May-10 7:06
memberRandall Doser6-May-10 7:06 
GeneralGreat article dude Pin
marlongrech27-May-07 21:25
membermarlongrech27-May-07 21:25 
GeneralNice Part ll Pin
Bob Kaye16-Apr-07 13:45
memberBob Kaye16-Apr-07 13:45 
Generalnice work guys Pin
Aminkayyali14-Apr-07 22:13
memberAminkayyali14-Apr-07 22:13 
GeneralAwesome Pin
Josh Smith12-Apr-07 11:07
mvpJosh Smith12-Apr-07 11:07 
GeneralRe: Awesome Pin
Nishant Sivakumar12-Apr-07 15:05
mvpNishant Sivakumar12-Apr-07 15:05 
GeneralRe: Awesome Pin
Josh Smith22-Jun-07 3:07
mvpJosh Smith22-Jun-07 3:07 
GeneralCool Pin
norm .net12-Apr-07 4:55
membernorm .net12-Apr-07 4:55 
GeneralRe: Cool Pin
Nishant Sivakumar12-Apr-07 15:05
mvpNishant Sivakumar12-Apr-07 15:05 
GeneralRe: Cool Pin
norm .net12-Apr-07 20:37
membernorm .net12-Apr-07 20:37 
Generalvery nice Pin
Sacha Barber12-Apr-07 3:57
memberSacha Barber12-Apr-07 3:57 
GeneralRe: very nice Pin
Nishant Sivakumar12-Apr-07 15:04
mvpNishant Sivakumar12-Apr-07 15:04 
GeneralRe: very nice Pin
Sacha Barber12-Apr-07 21:31
memberSacha Barber12-Apr-07 21:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 12 Apr 2007
Article Copyright 2007 by Christian Graus, Nish Nishant
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid