WPF has a very powerful animation system built into it. But you can tell just by looking at the design that Microsoft created it before certain features became available in C#.
It works by storing a reference to an object that has the ability to modify the value of a property on another object. That's not very clear in abstract, so an example will be helpful. The "other object" might be a rectangle painted on the screen. The property on that object might be its opacity (the inverse of transparency). So an animation is an object that contains code that modifies the opacity of the rectangle, causing it to fade out of existence in a visually satisfying way.
This works very nicely for classes already provided by Microsoft. What if I have my own property on my own class, and I want to make an animation that varies the value of that property? I have two options:
- Make it an ordinary property. I will have to give the animation object a string containing the name of that property, so it can look it up using reflection. This is pretty nasty. If I rename the property, the compiler will not notice a problem. Reflection is "binding at runtime". Aside from the loss of static checking, there's the small matter of the extra work this involves at runtime, potentially slowing down my animation.
- Make it a dependency property. This is a concept introduced with WPF. It basically means that the value is stored in a sort of map, where the key is a special object that acts as the name of the property at runtime. This restores compile-time checking, but there is no direct language support in C# for dependency properties, and it shows; turning an ordinary field into a dependency property requires a lot of boilerplate code.
An added complexity is that there have to be animation objects for manipulating all the different possible datatypes.
Meanwhile, C# has a very nice feature in version 3.0 called "lambda expressions". Suppose an animation was defined as a delegate with this signature:
delegate void Animate(double progress);
The progress parameter indicates how far through the animation sequence we are, so at the beginning it has the value zero, and at the end it has the value one. The delegate is called repeatedly during the predefined time period, each time receiving an appropriate progress value.
Now suppose in order to run an animation, the system has a static class called AnimationSystem provided us with a function like this:
void Start(int duration, Animate animation);
To get animation to happen, you just call Start with a duration in milliseconds and an implementation of the Animate delegate. Thanks to lamda expressions, this can be done very neatly.
For example, when the user clicks on my canvas, I add a rectangle and I want it to "fade up" over one second. The rectangle r has an Opacity property. So I just do this:
Animation.Start(1000, p => { r.Opacity = p; });
If I wanted to do anything more complicated, delegates can have any code in them, so there are no limitations. And yet the simple (yet useful) case above is very succinct and clean.
As an example of something more complicated, and yet not very hard to figure out how to do with this system, consider making an object move diagonally, adjusting the X and Y coordinates simultaneously over a range of values from 0 to 100:
AnimationSystem.Start(1000, p => {
r.X = p * 100;
r.Y = p * 100;
});
Look up how to do that with the WPF framework and I think you'll agree this is a lot simpler! No need to write your own separate animation class.
Fortunately we don't need to cry about the fact that Microsoft didn't implement animation this way, because it's very easy to write that AnimationSystem.Start function. (Clue: WPF has a class called CompositionTarget with a static event called Rendering.)
No comments:
Post a Comment