Click here to Skip to main content
15,867,750 members
Articles / Desktop Programming / WPF

Using Lambdas for WPF or Silverlight Animation

Rate me:
Please Sign up or sign in to vote.
4.91/5 (84 votes)
17 Jun 2010CPOL4 min read 82.7K   913   108   26
Shows how to use lambdas and higher-order functions for WPF/Silverlight graphics work

Introduction

It’s fairly easy to create and animate a graphical primitive, say by moving it from point A to point B at constant speed. But what if you need to position several graphical objects in a particular arrangement and then animate them in a non-linear fashion? Neither Silverlight nor WPF has built-in functions for this. In this article, I’ll demonstrate ways in which one can create objects and animations dynamically using lambda-delegates and higher-order functions.

By the way, you really should check out the sample project - the animation is (IMHO) quite impressive, and is an illustration of how 2D animation can appear 3D-ish. People have been doing this for ages, of course, but I for one am surprised at how easy it is to do. 

Generation

Let us suppose that you have decided to create something like the following:

Theoretically, you could just use a for loop, but seeing how there’s a possibility of doing it in a much cleaner fashion, why not make use of it? Let’s start with something simple – a set of circles is clearly a collection, so let’s create a class that will hold references to these objects:

C#
public class LambdaCollection<T> : Collection<T> where T : DependencyObject, new()
{
  public LambdaCollection(int count) { while (count --> 0) Add(new T()); }
  ⋮
}

So far we’re keeping things simple – all we’ve done is define a collection which is constrained in terms of what it contains (only objects that derive from DependencyObject and have a default constructor). We have added a constructor that makes the needed number of objects. But here comes the interesting part: we now add a method which can initialize the properties of the contained T objects using lambda expressions:

C#
public class LambdaCollection<T> : Collection<T> where T : DependencyObject, new()
{
  ⋮
  public LambdaCollection<T> WithProperty<U>
	(DependencyProperty property, Func<int, U> generator)
  {
    for (int i = 0; i < Count; ++i)
      this[i].SetValue(property, generator(i));
    return this;
  }
}

Let’s pause and take a look at what is going on. Firstly, you’ll notice this is a fluent interface, seeing how the method ends with return this. The method itself takes two parameters. The first happens to be the property we want to change in all elements of the collection. The second is a reference to a value generator, i.e. to a function which takes the element index in the collection and returns a value of type U. This type can be anything – the only requirement is that it matches the property being set.

Attention: There is no automatic type conversion here, so if the property is of type double you cannot generate a value of type int – you’ll get an exception.

So how can we use this code? It happens to be remarkably easy. For example, to create ten circles of increasing sizes, we write the following:

C#
var circles = new LambdaCollection<Ellipse>(10)
  .WithProperty(WidthProperty, i => 1.5 * (i+1))
  .WithProperty(HeightProperty, i => 1.5 * (i+1));

Such an expression lets us make the diameter dependent on element position. In our case, it will be 1.5 pixels for the smallest element and 15 for the largest. And, as you can see in code, one can vary width and height independently.

Well, seeing how the manipulation of X and Y co-ordinates is such a common task, we can write a useful method that will simplify things even more:

C#
public class LambdaCollection<T> : Collection<T> where T : DependencyObject, new()
{
  ⋮
  public LambdaCollection<T> WithXY<U>(Func<int, U> xGenerator, Func<int, U> yGenerator)
  {
    for (int i = 0; i < Count; ++i)
    {
      this[i].SetValue(Canvas.LeftProperty, xGenerator(i));
      this[i].SetValue(Canvas.TopProperty, yGenerator(i));
    }
    return this;
  }
}

Now, let’s put it all together and create that image that we showed at the beginning of the article:

C#
int count = 20;
var circles = new LambdaCollection<Ellipse>(count)
  .WithXY(i => 100.0 + (4.0 * i * Math.Sin(i / 4.0 * (Math.PI))),
          i => 100.0 + (4.0 * i * Math.Cos(i / 4.0 * (Math.PI))))
  .WithProperty(WidthProperty, i => 1.5 * i)
  .WithProperty(HeightProperty, i => 1.5 * i)
  .WithProperty(Shape.FillProperty, i => new SolidColorBrush(
    Color.FromArgb(255, 0, 0, (byte)(255 - (byte)(12.5 * i)))));
foreach (var circle in circles)
  MyCanvas.Children.Add(circle);

That’s it – using a pair of methods, one can easily create various “constellations”. Now let’s look at animation.

Animation

Linear animation using DoubleAnimation is boring. It is much more interesting when we ourselves control element values. It’s actually quite easy – by taking an existing animation class, we can redefine its animated ‘tick’ value so that it is controlled by our own generator:

C#
public class LambdaDoubleAnimation : DoubleAnimation
{
  public Func<double, double> ValueGenerator { get; set; }
  protected override double GetCurrentValueCore
	(double origin, double dst, AnimationClock clock)
  {
    return ValueGenerator(base.GetCurrentValueCore(origin, dst, clock));
  }
}

Now we have a class that does linear interpolation for us, and we in turn can get a transformed value and do something with it.

Seeing how we’re working with collections, it would once again be useful to define a collection class for our purposes. Here’s such a class:

C#
public class LambdaDoubleAnimationCollection : Collection<LambdaDoubleAnimation>
{
  ⋮
  public LambdaDoubleAnimationCollection
	(int count, Func<int, double> from, Func<int, double> to,
    Func<int, Duration> duration, Func<int, Func<double, double>> valueGenerator)
  {
    for (int i = 0; i < count; ++i)
    {
      var lda = new LambdaDoubleAnimation
      {
        From = from(i), 
        To = to(i), 
        Duration = duration(i),
        ValueGenerator = valueGenerator(i)
      };
      Add(lda);
    }
  }
  public void BeginApplyAnimation(UIElement [] targets, DependencyProperty property)
  {
    for (int i = 0; i < Count; ++i)
      targets[i].BeginAnimation(property, Items[i]);
  }
}

In actual fact, it is beneficial to have several constructors here (or a constructor with many optional parameters). The parameters here are value generators, i.e., these parameters can be derived from the element position in the collection. The valueGenerator parameter expects a 2nd-order function or a “function which is a function generator”, i.e., a generator which depends on the collection index and whose value depends on the interpolated double value during the animation. In the C# programming language, this implies the use of a “double lambda” such as e.g. i => j => f(j).

Here is a small example of an animation that unrolls our spiral into a sine wave:

C#
var c = new LambdaDoubleAnimationCollection(
  circles.Count, 
  i => Canvas.GetLeft(circles[i]),
  i => 10.0 * i, 
  i => new Duration(TimeSpan.FromSeconds(2)),
  i => j => 100.0 / j);
c.BeginApplyAnimation(circles.Cast<UIElement>().ToArray(), Canvas.LeftProperty);

I can’t show the animation itself, but here’s a view of the end result: 

Extensions

Extending this mini-framework is easy. For example, if you want elements to be animated in sequence instead of in parallel, you can just change the LambdaDoubleAnimationCollection to the following:

C#
public class LambdaDoubleAnimationCollection : Collection<LambdaDoubleAnimation>
{
  ⋮
  public void BeginApplyAnimation(UIElement [] targets, DependencyProperty property)
  {
    for (int i = 0; i < Count; ++i)
    {
      Items[i].BeginTime = new TimeSpan(0);
      targets[i].BeginAnimation(property, Items[i]);
    }
  }
  public void BeginSequentialAnimation(UIElement[] targets, DependencyProperty property)
  {
    TimeSpan acc = new TimeSpan(0);
    for (int i = 0; i < Items.Count; ++i)
    {
      Items[i].BeginTime = acc;
      acc += Items[i].Duration.TimeSpan;
    }
    for (int i = 0; i < Count; ++i)
    {
      targets[i].BeginAnimation(property, Items[i]);
    }
  }
}

Same goes for any other manipulation you might need. Good luck!

License

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


Written By
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and C++, though I'm open to suggestions.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey10-Apr-12 3:16
professionalManoj Kumar Choubey10-Apr-12 3:16 
GeneralMy vote of 5 Pin
Filip D'haene7-Sep-11 11:03
Filip D'haene7-Sep-11 11:03 
QuestionGood job! Pin
toantvo7-Jul-11 16:58
toantvo7-Jul-11 16:58 
GeneralMy vote of 5 Pin
Eric Xue (brokensnow)6-Sep-10 18:12
Eric Xue (brokensnow)6-Sep-10 18:12 
GeneralMy vote of 5 Pin
95ulisse7-Aug-10 9:15
95ulisse7-Aug-10 9:15 
GeneralGood Pin
balavasanth27-Jul-10 20:38
balavasanth27-Jul-10 20:38 
GeneralMy vote of 5 Pin
vytheese27-Jul-10 18:52
professionalvytheese27-Jul-10 18:52 
GeneralMy vote of 5 Pin
Diptesh_Mishra26-Jul-10 21:07
Diptesh_Mishra26-Jul-10 21:07 
GeneralMy vote of 5 Pin
El'Cachubrey20-Jul-10 3:43
El'Cachubrey20-Jul-10 3:43 
GeneralGreat! Pin
Marcelo Ricardo de Oliveira12-Jul-10 3:31
mvaMarcelo Ricardo de Oliveira12-Jul-10 3:31 
GeneralAnother great article Pin
Josh Fischer7-Jul-10 5:07
Josh Fischer7-Jul-10 5:07 
GeneralRe: Another great article Pin
Dmitri Nеstеruk7-Jul-10 6:51
Dmitri Nеstеruk7-Jul-10 6:51 
GeneralRe: Another great article Pin
Josh Fischer7-Jul-10 11:37
Josh Fischer7-Jul-10 11:37 
Generalyou forget a variable Pin
zhushaofeng17-Jun-10 1:34
zhushaofeng17-Jun-10 1:34 
GeneralRe: you forget a variable Pin
Dmitri Nеstеruk17-Jun-10 2:09
Dmitri Nеstеruk17-Jun-10 2:09 
GeneralVery nice work Pin
Moim Hossain17-Jun-10 1:01
Moim Hossain17-Jun-10 1:01 
Generalvery nice Pin
zhushaofeng16-Jun-10 22:50
zhushaofeng16-Jun-10 22:50 
GeneralRe: very nice Pin
Dmitri Nеstеruk17-Jun-10 0:21
Dmitri Nеstеruk17-Jun-10 0:21 
GeneralAwesome ... Pin
Jammer16-Jun-10 22:37
Jammer16-Jun-10 22:37 
GeneralThanks everyone! Pin
Dmitri Nеstеruk16-Jun-10 10:06
Dmitri Nеstеruk16-Jun-10 10:06 
GeneralCool! Pin
Ernest Laurentin10-Jun-10 16:54
Ernest Laurentin10-Jun-10 16:54 
GeneralVery clever Pin
hecUngravity8-Jun-10 9:22
hecUngravity8-Jun-10 9:22 
GeneralCool Pin
Alan Beasley6-Jun-10 7:41
Alan Beasley6-Jun-10 7:41 
Generalvery very nice Pin
Sacha Barber4-Jun-10 20:39
Sacha Barber4-Jun-10 20:39 
GeneralGreat! Pin
UserWhoCares4-Jun-10 16:57
UserWhoCares4-Jun-10 16:57 

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

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