Article: Animation demo

Home Page


Consultancy

  • Service Vouchers
  • Escrow Service

Shop



Programming
  • Articles
  • Tools
  • Links

Search

 

Contact

 

Chess Puzzles




DWHS

Valid XHTML 1.0 Transitional
Valid CSS!
Mobile-friendly!

A simple app demonstrates animation of WinForms using GDI+, a Paint handler and a Forms.Timer

category 'KB', language C#, created 19-Nov-2009, version V1.2 (28-Feb-2010), by Luc Pattyn


License: The author hereby grants you a worldwide, non-exclusive license to use and redistribute the files and the source code in the article in any way you see fit, provided you keep the copyright notice in place; when code modifications are applied, the notice must reflect that. The author retains copyright to the article, you may not republish or otherwise make available the article, in whole or in part, without the prior written consent of the author.

Disclaimer: This work is provided “as is”, without any express or implied warranties or conditions or guarantees. You, the user, assume all risk in its use. In no event will the author be liable to you on any legal theory for any special, incidental, consequential, punitive or exemplary damages arising out of this license or the use of the work or otherwise.


This article describes the proper way to do WinForms animation, i.e. showing a Form that changes over time; it uses GDI+, a double-buffered panel, and a System.Windows.Forms.Timer; and it performs all painting/drawing inside the Paint handler.

Theory

There are several steps to correctly draw something; it does not matter how complex the paint job is: from a single line, to a complex drawing, or a real work of art.

To make sure it all becomes visible on the screen and gets repainted automatically when moving, resizing, minimizing/maximizing/restoring or uncovering your Form, one should follow these steps:

  1. Decide upon what object you want to draw; it normally is a Control (e.g. a Panel) or a Form itself. I prefer to add a Panel to a Form, then draw on the Panel. And I do not like PictureBoxes (in my opinion they are pretty useless; they are adequate for showing a picture, not for image operations, drawing, cropping, or any other operation.) For complex and evolving drawings, if flickering is to be avoided, consider using a double-buffered Panel (see example below).
  2. Create some variables (Rectangle, struct, class, whatever) that hold the parameters of your drawing. For a rectangle that could be top and left coordinate, and width+height, or just a Rectangle. etc. For a complex drawing, it could be a List of objects that derive of a common type, each having its own PaintMe() method.
  3. Create a Paint handler (either add your own paint handler to the Paint event, or override the OnPaint method) for that Panel, and do all your drawing in there, using the Graphics object inside the PaintEventArgs, and your variables. Do not call CreateGraphics(), as Graphics objects are big and expensive, and you got a Graphics object for free already inside the PaintEventArgs parameter, so why create a new one?
  4. If and when you want to change things, modify the variables and call Panel.Invalidate() or one of its overloads (for selective invalidation). That way Windows knows it should repaint all or part of your control, and it will do so in due time.
  5. If you want to animate things, perform the move (step 4) inside the Tick handler of a Windows.Forms.Timer which ticks on the GUI thread, so you are allowed to call Invalidate() from there too. If your animation engine is running on another thread, you need the Control.InvokeRequired/Control.Invoke pattern.

BTW: if you need to create some objects (Fonts, Pens, Brushes, ...) either create them inside the Paint handler and don't forget to call Dispose() on them (one must call dispose on instances of a class that offers such method), or better yet create them only once and keep them alive in class members, this avoids creating a lot of new objects, followed by disposing and collecting them.

The demo program

The environment used is the Microsoft .NET Framework (version 2.0 or above) and the C# programming language. The demo program shows a Form with some GUI Controls and a Panel onto which an image is drawn. When the "Animate" button is pressed, the image grows until it fills the Panel. The number of steps in the animation, and the duration of each step (the timer period), can be selected through TextBoxes.


Animation demo

Click the image to see the animation (uses a big GIF file, almost 2MB).



This is the bulk of the code:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace Animation1 {
	public partial class Form1 : Form {
		Bitmap image;
		Timer timer;
		int step;
		int steps;

		public Form1() {
			InitializeComponent();
			// get image
			image=new Bitmap("VisualStudio2008.png");
			steps=1;
			// now draw it
			panel1.Paint+=new PaintEventHandler(panel1_Paint);
			panel1.Invalidate();
		}

		void panel1_Paint(object sender, PaintEventArgs e) {
			// the one animation parameter here is "step"
			int wOrig=image.Width;
			int hOrig=image.Height;
			int wFinal=panel1.Width;
			int hFinal=panel1.Height;
			// interpolate between initial and final image
			int w=(wOrig*(steps-step)+wFinal*step)/steps;
			int h=(hOrig*(steps-step)+hFinal*step)/steps;
			int x=(wFinal-w)/2;
			int y=(hFinal-h)/2;
			// now paint image
			e.Graphics.DrawImage(image, x, y, w, h);
		}

		private void btnAnimate_Click(object sender, EventArgs e) {
			if (step!=0) {
				step=steps;	// cancel the animation
			} else {
				// get parameters and validate them
				if (!int.TryParse(tbSteps.Text, out steps) || steps<1 || steps>1000) steps=50;
				tbSteps.Text=steps.ToString();
				int delay;
				if (!int.TryParse(tbDelay.Text, out delay) || delay<10 || delay>1000) delay=17;
				tbDelay.Text=delay.ToString();
				// paint the image on the Panel, and start timer for animation
				panel1.Invalidate();
				timer=new Timer();
				timer.Interval=delay;
				timer.Tick+=new EventHandler(timer_Tick);
				timer.Start();
				btnAnimate.Text="Cancel";
			}
		}

		void timer_Tick(object sender, EventArgs e) {
			step++;
			if (step>steps) {
				// terminate the animation
				step=0;
				timer.Stop();
				btnAnimate.Text="Animate";
			}
			panel1.Invalidate();
		}
	}

	//####################################################################################
	// DoubleBufferedPanel is a panel that uses double buffering to avoid flicker.
	//####################################################################################
	public class DoubleBufferedPanel : Panel {
		public DoubleBufferedPanel() {
			DoubleBuffered=true;			
		}
	}
}

The project files are available here; they target Visual Studio 2008.

Conclusion

A few simple rules suffice to paint and even animate just about anything.

History

  • Version 1.0 (19-Nov-2009): Original version
  • Version 1.1 (20-Nov-2009): Improved double-buffering (using Control.DoubleBuffered, and splitting files for easier builds in Visual Studio)
  • Version 1.2 (28-Feb-2010): Added link to animated GIF file, showing actual results.


Perceler

Copyright © 2012, Luc Pattyn

Last Modified 02-Sep-2013