Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Animating Windows Forms

0.00/5 (No votes)
15 May 2006 4  
A component to make Windows Forms controls more dynamic by adding animation capabilities.

Sample Image - animators.gif

Contents

Introduction

Normally, the content of forms and controls is relatively static. All elements are placed at a special location with a special appearance, and don't change much. And even if they do, this normally is more an immediate change than a fluent transition. Everything needed to have some cool animations is already provided in the framework. It has just to be put together. This is mostly done for every special case. This component gives the infrastructure to easily integrate animations of any thinkable type. It also tries to address the fact that the Visual Studio Designer only has a static view.

Besides the playground samples, I also included a reusable component Gradients. It is complex enough to form another article, but it also shows how the Animations component can be used to create reusable controls which internally encapsulate animations. Thus, both components are illustrated in this single article.

Background

To use this component, you should just have a bit of experience about working with Windows Forms. To understand it completely, a knowledge of the Timer class, designer attributes, and direct bitmap manipulation are recommended, but I will try to explain the key parts of the implementation as good as possible.

Using the code

To animate something, you first start designing a static form. When you are finished and decided what you want to animate, just drag the appropriate Animator component onto your form. If you want to animate - for example - the bounds of a control, drag the ControlBoundsAnimator onto your form. Now, change the Control property of it to the specific control you want to animate. You will see how the StartBounds and EndBounds properties switch to the current values of the control. Now, change the SynchronizationMode property to Start and move the control to the location it should have when the animation starts. After that, change the SynchronizationMode property to End and move the control to the location where the animation should end. If you switch this property back and forward, you will notice how the control hops between both the set locations. When you are done, you can set it back to None. All what is left is starting the animation. If you want the animation to start when the form loads, just add a Start() call to the animator component in the Load event of the form:

protected override void OnLoad(EventArgs e) {
   base.OnLoad(e);
   _myBoundsAnimator.Start();
}

Show the form and you will see the animation. With the Intervall and StepSize properties, you can adjust the speed of the animation (the default values are rather fast).

Samples

I have put much effort in supplying good samples in the downloads. They cover most of the functionality of this component. Keep in mind that this component can easily be extended to animate about anything. The animators provided are just samples of what could be done.

Another sample where the Animations component is being used can be seen in my other article: BarTender - Group your contents[^].

Architecture

The two included components are too big to describe everything in detail here. So, I will reduce it to a short description of every included class. I tried my best to document them well - a compiled help file is also included in the downloads. Thus, you will have to explore all available properties and tweaks yourself.

Component Animations

AnimatorBase

This is the base class for all classes implementing the animation of a special property. It holds a Timer whose Tick events act as a heart pacemaker for the animation. It provides properties for controlling the animation (like Start(), Stop()...), properties for defining the general behavior, events notifying the outside world on what is going on, and also the logic to easily bind several animators together. The class itself is abstract. Thus, some concrete implementations are needed before anything can happen. Every inheriting animator has to override the properties StartValue, EndValue, and CurrentValueInternal which merely inform the base class about what the current state of the property to animate is and the information about the start and end states of the animation. Besides those, the function GetValueForStep has to be overridden. It takes a value from 0% to 100%, and should return an interpolated value between StartValue and EndValue. To make life of the implementers easier, AnimatorBase holds several static functions to help interpolate different types of values.

A bit more work is also required to make your own component working well together with the designer. Just have a look at the provided implementations to discover all the details.

ControlBackColorAnimator

This AnimatorBase implementation animates the back color of a given control.

ControlForeColorAnimator

This AnimatorBase implementation animates the fore color of a given control.

ControlBoundsAnimator

This AnimatorBase implementation animates the bounds of a given control. This can be the location and/or size. Several properties are given to specify what part of the bounds should be animated and what parts are static.

FormOpacityAnimator

This AnimatorBase implementation animates the opacity of a Form. It can be used to fade forms in or out.

TrackBarValueAnimator

This AnimatorBase implementation animates the Value of a TrackBar. It's probably not very useful in practice.

Component Gradients

GradientBorder

This control is a container for other controls, and adds a border which fades out transparently to its edges. Thus, the more you come to the edge, the more you will see the background. It adjusts its DockPadding with the width of the border. Thus, you can insert controls and dock them, and they will adjust according to the border's width. The color of the border and its interior is set with the InnerColor property.

To achieve this effect, it overrides the OnPaint function. For greater speeds, it paints itself into a Bitmap whenever the properties change, and then just paints this bitmap. This technique is called double buffering. The painting itself is unsafe, and looks rather messy. It's greatly optimized for speed. Thanks to Christian Graus for his help regarding this. If you are interested in this special part, then have a look at his article series Image Processing for Dummies[^].

GradientBorderLabel

This control is derived from GradientBorder, and adds a text to it by extending OnPaint. It also has properties to adjust the location of the text. If you want to know how to work with the StringFormat class, you could have a look inside.

GradientBorderInnerColorAnimator

Now, we come to the classes where the Animations component comes into play. This class inherits AnimatorBase, and animates the InnerColor of a GradientBorder. Note that because GradientBorderLabel inherits GradientBorder, you can also animate it.

GradientBorderWidthAnimator

This class inherits AnimatorBase and animates the BorderWidth of a GradientBorder. Note that because GradientBorderLabel inherits GradientBorder, you can also animate it.

GradientBorderButton

This class inherits GradientBorderLabel and integrates a GradientBorderInnerColorAnimator, a GradientBorderWidthAnimator, and a ControlForeColorAnimator. They are needed because this control has two states - one when the mouse is over it, and one when it is not. The control is animating between both states whenever the mouse enters or leaves it. It has many properties to affect this animation.

The whole animation part is hidden within the class. Thus, using it is relatively easy, because only some properties have to be set.

Designer support

I wanted good designer support for these components. As I think there is not much material out there describing the most important things to know, I will do it now.

Browsable

The Browsable attribute can be used to tell the designer whether it should show a specific property or not. By default, all public properties are listed in the properties window. Although not alterable, this also applies to readonly properties where I normally prefer that they are not shown. Also, sometimes a property might not be of real use in design time. When overriding properties, you can also override the Browsable attribute. For example, the Control has a Text property which is hidden in most inheriting classes within the designer, but for example, the Label class overrides it and makes it visible.

false)>
public int MySampleProperty {
   get { return _mySampleValue; }
   set { _mySampleValue = value; }
}

Description

Besides having code documentation, you also can define a description for any property shown by the designer. This can be done by using the Description attribute which just takes a simple string containing a describing text. This text will be shown at the bottom of the properties window for the selected property.

[Description("This is just a sample.")]
public int MySampleProperty {
   get { return _mySampleValue; }
   set { _mySampleValue = value; }
}

Category

The property window has a categorized view in which the properties are grouped together. Normally, properties are put into the Others group. You can define how your properties are grouped, by using the Category attribute. When using some of the default names, your properties will be put together with the ones coming from .NET. Even if you do not have an English version of Visual Studio, you should use the default English category names, because they get localized automatically. In a German Visual Studio, the category name Behavior will show in the properties window as Verhalten.

[Category("Appearance")]
public int MySampleProperty {
   get { return _mySampleValue; }
   set { _mySampleValue = value; }
}

DefaultValue

Have you noticed that normally all property values are shown normal, and become bold when you change them? When they are bold, it means they do not correspond to the default values. This also affects what properties are written into the designer generated code section. You can affect this behavior with the DefaultValue attribute, which takes a constant expression defining the default value. To make this clean, you should always provide a value from a constant field and also assign this to the specific field/element in the field declaration or constructor.

private const int DEFAULT_MY_SAMPLE_PROPERTY = 0;
private int _mySampleValue = DEFAULT_MY_SAMPLE_PROPERTY;

[DefaultValue(DEFAULT_MY_SAMPLE_PROPERTY)]
public int MySampleProperty {
   get { return _mySampleValue; }
   set { _mySampleValue = value; }
}

ShouldSerialize

A problem with the DefaultValue attribute is the fact that you need to provide a constant expression. This applies to all primitive types. But what can be done in more complex scenarios? For this, you just have to implement a function named ShouldSerializePropName. It should take no arguments, and return a boolean value. The designer searches for those properties with reflection, and calls them when they are present.

public Color MySampleProperty {
   get { return _mySampleValue; }
   set { _mySampleValue = value; }
   
protected virtual bool ShouldSerializeMySampleProperty() {
   return base.Parent != null && MySampleProperty.Equals(Color.Empty);
}

As you can see, any logic can now be implemented like in this case, that in order to serialize MySampleProperty, the set value as well as the contents of another property are checked.

TypeConverter

The TypeConverter attribute defines how values are edited in the designer. There are dozens of them built into the framework, and you can also specify your own.

[TypeConverter(typeof(OpacityConverter))]
public int MySampleProperty {
   get { return _mySampleValue; }
   set { _mySampleValue = value; }
}

This sample will show a value from 0 to 1 as 0% to 100% in the properties window.

RefreshProperties

If one property affects the contents of another, the property window will normally get confused and will not show the correct values. By using the RefreshProperties attribute, you can make it refresh completely when the value of a specific property gets changed.

public int MySampleProperty1 {
   get { return _mySampleValue1; }
   set {  _mySampleValue1 = value; 
      _mySampleValue2 = value * 2;  }
      
[RefreshProperties(RefreshProperties.Repaint)]
public int MySampleProperty2 {
   get { return _mySampleValue2; }
   set {  _mySampleValue2 = value; 
      _mySampleValue1 = value / 2; }
}

If you would have two properties like those, you would not see any change to MySampleProperty2 when changing MySampleProperty1. But because of the RefreshProprties attribute, you would see MySampleProperty1 when changing MySampleProperty2.

Designer

The Designer attribute is used to determine how a specific class is designed. A common example where this is needed is when your own UserControl should also function as a container control.

[Designer("System.Windows.Forms.Design.ParentControlDesigner, 
           System.Design", typeof(IDesigner))]
public class GradientBorder : System.Windows.Forms.UserControl

DesignMode

This one doesn't directly affect the designer, but in certain situations, you might need to check in your code if it is currently run in design mode. It is a protected property of the Component class, and can thus be accessed directly from any component including all controls.

ToDo's

  • Some more AnimatorBase implementations should be useful.
  • Currently, the animation won't run smoothly when compiled under the .NET 2.0 framework. This is due to changes in the System.Windows.Forms.Timer class.
  • Anything you like :). Please feel free to post requests.

History

  • March 2nd, 2006 - Version 1.0:
    • Initial release.
  • May 13th, 2006 - Version 1.1:
    • Changed the LoopAnimation property to LoopMode, now supporting three different states.
    • Added a new animator named DummyAnimator which itself doesn't animate anything but can act as a parent for other animators.
    • Several minor corrections and tweaks.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here