Title: Perform Windows Forms property animation in C#
Making a control's properties change over time is called property animation. Keeping track of where a control's properties, for example its position as it moves, can be tricky, particularly if you need to animate multiple objects at the same time. This example uses the following ControlSprite class to keep track of a control while it moves in a straight line.
// Information about a move in progress.
public class ControlSprite
{
public System.Windows.Forms.Control MovingControl;
public int EndX, EndY;
public float CurrentX, CurrentY;
private float Dx, Dy;
private DateTime LastMoveTime;
private TimeSpan TotalElapsed, MoveUntil;
private Timer MoveTimer;
public delegate void DoneEventHandler(object sender);
public event DoneEventHandler Done;
// Prepare to move the control.
public ControlSprite(System.Windows.Forms.Control control)
{
MovingControl = control;
}
// Start moving.
public void Start(int end_x, int end_y, int pixels_per_second)
{
CurrentX = MovingControl.Location.X;
CurrentY = MovingControl.Location.Y;
EndX = end_x;
EndY = end_y;
// Calculate the total distance.
float dx = EndX - CurrentX;
float dy = EndY - CurrentY;
float dist = (float)Math.Sqrt(dx * dx + dy * dy);
// Calculate the X and Y amounts to move per second.
Dx = pixels_per_second * dx / dist;
Dy = pixels_per_second * dy / dist;
// See how long the total move will take.
int milliseconds = (int)(1000.0f * dist / pixels_per_second);
MoveUntil = new TimeSpan(0, 0, 0, 0, milliseconds);
TotalElapsed = new TimeSpan(0);
// Make the timer.
MoveTimer = new Timer();
MoveTimer.Interval = 10;
MoveTimer.Tick += MoveTimer_Tick;
// Start moving.
Start();
}
// Resume moving.
public void Start()
{
LastMoveTime = DateTime.Now;
MoveTimer.Enabled = true;
}
// Stop moving.
public void Stop()
{
MoveTimer.Enabled = false;
}
// Move the control.
private void MoveTimer_Tick(object sender, EventArgs e)
{
// See how long it's been since the last move.
DateTime now = DateTime.Now;
TimeSpan elapsed_since_last = now - LastMoveTime;
LastMoveTime = DateTime.Now;
// See if we should stop.
TotalElapsed += elapsed_since_last;
if (TotalElapsed >= MoveUntil)
{
// Stop.
MoveTimer.Enabled = false;
CurrentX = EndX;
CurrentY = EndY;
if (Done != null) Done(this);
}
else
{
// Continue.
CurrentX +=
(float)(Dx * elapsed_since_last.TotalSeconds);
CurrentY +=
(float)(Dy * elapsed_since_last.TotalSeconds);
}
// Move the control to its new location.
MovingControl.Location =
new Point((int)CurrentX, (int)CurrentY);
}
}
The class starts with some relatively straightforward declarations. The only really interesting part here is the declaration of the Done event, which the class raises when its control has finished moving.
The class's constructor takes as a parameter the control that it will move.
The first overloaded version of the Start method gets things moving. It saves the control's current and end locations, and calculates the total distance the control must travel. It then uses that distance and the pixels_per_second parameter to determine how much it must increment the control's X and Y coordinate per second to achieve the desired movement in pixels per second. The Start method calculates the total amount of time the movement will take, creates a Timer to control the movement, and then calls the second overloaded version of Start.
The second version of Start saves the current time in the LastMoveTime variable and enables the Timer.
The Stop method simply disables the sprite's Timer.
When the Timer raises its Tick event, the sprite determines how long it's been since the last time it updated the control's position and adds the elapsed time to the total elapsed time so far. If the control has been moving for long enough to reach its destination, the code disables the Timer, moves the control to its final position, and raises the Done event. If the control has not been moving long enough to reach its destination, the code increments the control's X and Y coordinates.
The example program uses two sprites to animate a Button and a Label moving at the same time.
The following code shows how the program prepares to animate its controls.
private ControlSprite ButtonSprite, LabelSprite;
// Make the control sprites.
private void Form1_Load(object sender, EventArgs e)
{
ButtonSprite = new ControlSprite(btnStart);
ButtonSprite.Done += ButtonSprite_Done;
LabelSprite = new ControlSprite(lblMessage);
}
This code declares two ControlSprite objects. The Load event handler initializes the objects and sets the Button's Done event handler (described shortly).
When you click the Button, the following code executes.
// Start or stop.
private void btnStart_Click(object sender, EventArgs e)
{
const int PixelsPerSecond = 200;
if (btnStart.Text == "Start")
{
// Start button. Change the caption to Stop.
btnStart.Text = "Stop";
// See where we are.
if (btnStart.Location.X == 12)
{
// Move the button down and right.
ButtonSprite.Start(197, 229, PixelsPerSecond);
LabelSprite.Start(12, 232, PixelsPerSecond);
}
else if (btnStart.Location.X == 197)
{
// Move the button up and left.
ButtonSprite.Start(12, 12, PixelsPerSecond);
LabelSprite.Start(186, 12, PixelsPerSecond);
}
else
{
// Continue the button's previous move.
ButtonSprite.Start();
LabelSprite.Start();
}
}
else
{
// Stop button. Change the caption to Start.
btnStart.Text = "Start";
// Stop moving.
ButtonSprite.Stop();
LabelSprite.Stop();
}
}
This code first determines whether the Button currently says Start or Stop. If the Button says Start, the code makes it say Stop. Then if the Button's X position is 12, the code starts both sprites, moving the controls to positions (197, 229) and (12, 232). If the Button's X position is 197, the code starts both sprites, moving the controls to positions (12, 12) and (186, 12).
If the Button's X position is neither 12 nor 197, the code simply calls the sprites' Start methods so they continue the previous move. (That way you can stop and restart the movement whenever you like.)
If the Button starts with caption Stop, then the code simply calls the sprites' Stop methods.
The following code shows the Button's Done event handler.
// The button is done moving.
public void ButtonSprite_Done(object sender)
{
btnStart.Text = "Start";
}
This code simply resets the Button's text to Start so you can restart the animation.
You can use similar techniques to do other kinds of property animation, although defining some animations may contain extra work. For example, you would need to figure out how to change a color to make it gradually change from red to green.
Download the example to experiment with it and to see additional details.
|