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

ImageList-enabled Animation Control (ImAC) with Designer Support

0.00/5 (No votes)
7 Jul 2005 2  
An article on writing an ImageList-based animation control with designer support.

Introduction

In my other article, I have explained how you can write a SignalBar control with Designer Support. This article explains how you can write a simple ImageList-enabled Animation Control (ImAC) in .NET CF. There is another Animation control written in CodeProject that allows you to animate .GIF files. The control that I want to create here is slightly different. It can animate virtually any image that is supported by the .NET CF (.bmp, .gif, .png, .jpg) and allows a user to create custom animation during design time. This is possible because ImAC comes with the Designer Support facility.

Background

Unfortunately, writing custom controls in .NET CF is not as straightforward as in the full .NET Framework. There is no ‘Designer Support’ (DS) provided! Fortunately, there are articles to help you add the DS to .NET CF. One of them is written by Jim Wilson: 'Adding Designer Support to the .NET Compact Framework DateTimePicker Control’. Therefore, I strongly suggest you read his article as the pre-requisite to understand my article. In this article, therefore, I will concentrate more on writing the actual control itself. However, the source code will come complete with the DS and ready for use.

Using the code

You can download the demo ZIP file provided above. There are two assemblies, the run time and design time. Extract the run time assembly (HT.CustomControl.AnimationCtrl.dll) to \Program Files\Microsoft Visual Studio .NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\ and create a subfolder Designer if it dosn’t exist and copy over the design time assembly (HT.CustomControl.AnimationCtrl.Design.dll). Next, add it to your VS.NET Toolbox (View->Toolbox or Ctrl+Alt+X). You may want to create your own control tab by right-click and Add Tab. Then add the control assembly by selecting Add/Remove Items… menu item to the newly created tab. Then browse and find the HT.CustomControl.AnimationCtrl.Design.dll assembly from the {Installation Directory}\Designer\ folder, click OK, then you should be able to see and drag-&-drop the control to your form.

The code design

The main single important file in the solution is AnimationCtrl.cs under the runtime AnimationControl project. This file contains all the fields, properties, and methods that the control is using. The file starts with the class AnimationCtrl declaration which inherits from the Control class:

public class AnimationCtrl : System.Windows.Forms.Control
{
   ...
}

In the next block following the class declaration, the fields required in defining the class are specified.

...
#region fields
protected Bitmap    m_bmp;          // offscreen bitmap
protected Graphics  m_graphics;
protected int       iloop;
protected int       nLoop;
protected bool      bStarted;
protected int       ci; // index that point to current image in imglist
protected Timer     tmrAnm = null;  // timer controlling animation
private ImageList   imglist = null;
#endregion
...

Following the fields declaration, the following properties are defined. Please note that each property has a preamble to it. The preamble defines the attributes required to expose the property to the Visual Studio .NET 2003 (VSNET) Property window.

#region properties
#if NETCFDESIGNTIME
[System.ComponentModel.Browsable(true)]
[System.ComponentModel.Category("Behavior")]
[System.ComponentModel.DefaultValue(-1)]
[System.ComponentModel.Description("Determines the number" + 
                         " of loops, -1 will loop forever")]
#endif
public int Loop // number of looping
{
   get
   {
      return nLoop;
   }
   set
   {
      nLoop = value;
   }
}

#if NETCFDESIGNTIME
[System.ComponentModel.Browsable(true)]
[System.ComponentModel.Category("Behavior")]
[System.ComponentModel.DefaultValue(1000)]
[System.ComponentModel.Description("The animation" + 
                        " interval in miliseconds")]
#endif
public int Interval // in miliseconds
{
   get
   {
      return tmrAnm.Interval;
   }
   set
   {
      tmrAnm.Interval = value;
   }
}

#if NETCFDESIGNTIME
[System.ComponentModel.Browsable(true)]
[System.ComponentModel.Category("Appearance")]
[System.ComponentModel.DefaultValue(null)]
[System.ComponentModel.Description("The list of images to be animated")]
#endif
public ImageList ImageList
{
   get
   {
      return imglist;
   }
   set
   {
      imglist = value;
   }
}
#endregion

The following is a snapshot of the Property window in VS.NET of AnimationCtrl. We should see all the exposed properties defined above. Note how the preamble System.ComponentModel.Description value appear at the bottom of the property window.

Figure 1: Snapshot of AnimationCtrl property window

Figure 1: Snapshot of AnimationCtrl property window

To operate the control programmatically, you can call the Start() and Stop() methods. When you start the animation and stop and start again, the animation should resume from the last frame where it stopped. However, in other circumstances, you can stop animation and when you start it again, it starts from the beginning. The latter operation can be achieved easily by calling the Reset() method. The following describes the three methods.

public void Reset()
{
   Stop();
   ci = 0;
}

public void Start()
{
   if(!bStarted)
   {
      bStarted = true;
      tmrAnm.Enabled = true;
   }
}

public void Stop()
{
   bStarted = false;
   tmrAnm.Enabled = false;
}

Since the control will be driven by a timer object, tmrAnm, declared earlier, each time a tick event generated by the timer occurs, it will ask the control to repaint itself with the next image in the ImageList, imagelist.

Before it knows which handler will be called when the timer tick event occurs, you should bind the handler (tmrAnm_Tick()) with the timer object (tmr_Anm) when initializing the control. When it is done, the handler can call the Invalidate() function to generate the OnPaint message. You may want to consult the MSDN library to know more about Invalidate(), Refresh(), and Update(). When the OnPaint message is received by the control, by default the OnPaint function is called.

/// Timer
/// Binding the timer.Tick event to the handler
tmrAnm = new Timer();
tmrAnm.Interval = 1000;
tmrAnm.Tick += new EventHandler(tmrAnm_Tick);
    
private void tmrAnm_Tick(object sender, EventArgs e)
{    
   Invalidate();
}

The logic of the control lies in the OnPaint function. We can write a function to separate the logic and the UI for a good design practice. In this example however, I am not going to do so as the function calls create overhead. Although it may not be too much different in reality but for a rapid redrawing it could add some delays to the animation.

The OnPaint function will iterate the images in the ImageList starting from the image pointed by 'ci'. Normally 'ci' is initialized to zero. It then checks if it should loop forever until a Stop method is called or loop nLoop times. The OnPaint call is ended by blitting memory bitmap, m_bmp, to the screen.

#region overridden methods
protected override void OnPaint(PaintEventArgs e)
{
   // draw to memory bitmap
   CreateMemoryBitmap();

   // init background
   m_graphics.Clear(this.BackColor);
   if(imglist.Images.Count > 0)
   {
      Bitmap bmp = new Bitmap(imglist.Images[ci]);
      m_graphics.DrawImage(bmp, 0, 0);

      if(bStarted)
      {
         if(nLoop == -1)
         {
             ci = (ci + 1) % imglist.Images.Count;
         }
         else
         {
             if(iloop >= nLoop)
             {
                Stop();
                iloop = 0;
             }
             else
             {
                 iloop++;
             }
             ci = (ci + 1) % imglist.Images.Count;        
         }
      }
   }
   
   // blit memory bitmap to screen
   e.Graphics.DrawImage(m_bmp, 0, 0);
}

protected override void OnPaintBackground(PaintEventArgs e)
{
   // don't pass to base since we paint everything, avoid flashing
}

The last thing to do is the control should repaint itself by calling the Invalidate() function whenever it is resized. Therefore, we need to override the OnResize() function as described below:

protected override void OnResize(EventArgs e)
{
   base.OnResize (e);
   this.Invalidate();
}
#endregion

Finally, the initialization of the control is as provided below:

#region initialization
// default constructor
public AnimationCtrl()
{
   InitializeComponents();
}

private void InitializeComponents()
{
   ///
   /// Image List
   /// List of images to be animated
   imglist = new ImageList();

   /// Timer
   /// Binding the timer.Tick event to the handler
   tmrAnm = new Timer();
   tmrAnm.Interval = 1000;
   tmrAnm.Tick += new EventHandler(tmrAnm_Tick);

   ///
   /// ci
   ///
   ci = 0;

   ///
   /// flag
   /// 
   bStarted = false;

   nLoop = -1;
   iloop = 0;
}

Conclusion

Writing a custom control is an exciting experience, you can customize the control to behave as you want. Subclassing a control is normally quite straightforward. However, in .NET CF it is a little bit painful since you don't have Designer Support available automatically. Thanks to people who have written articles on how to add Designer Support to writing custom controls in .NET CF. This makes my life a lot easier when developing application with .NET CF.

History

  • Version 1.0 - Initial release of ImageList-enabled Animation Control (ImAC).

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