Animation with picturebox

BackinBlack

Newcomer
Joined
Nov 19, 2011
Messages
2
Hi,
I am creating a program that moves a picture of a ball 750 pixels across the screen in 750ms for the university project of a friend of mine. I can't post the code as it is far too long but it works as follows. When a key is clicked a stopwatch timer begins and for the next 750 ms a new thread runs checking whether the timer has reaching 750 and (if not) moving the .left value of the picturebox until it reaches 750.

This all works fine, the box moves in 750ms and I'm happy with the resolution of the timer. The only problem, I have found, is that, when moving, the edge of the picturebox (and thus the ball) gets cut off to about a quarter of the way along its length leaving a ball with one flat edge. Obviously, due to the requirement of this program for high resolution / accuracy testing there cannot be this issue.

Is there any other way of making a ball move across the form without using pictureboxes. Can I use directx and just draw it. I know a circle is pretty hard to just "draw" in directx, but hey.

If anybody could help, I would be very appreciative.
 
I'm not quite sure what are you trying to accomplish. Whatever it is, I'm sure it can be done with GDI+. While GDI+ is not known for its speed it is ideal simple animations, plus, it is also VERY easy to use.

Are you familiar with GDI+?
 
Last edited:
Sorry if I was confusing with my initial post. I am wanting to create a circle about 75pixels squared and then move it across the form by 750 pixels in 750 milliseconds (so at a rate of 1pixel/ms). My problem is that as I am using a picturebox the edge of the circle, on the side moving forwards, is cut off leaving it with a flat edge. So I'm looking for a method of achieving this where the circle remains intact.

I am aware of GDI+, I was just unsure as to whether I could draw a circle such that it moves 1 pixel to the right every 1millisecond without being significantly delayed.
Would I have to redraw a circle every millisecond with the x coordinate += 1? Or can I move an already drawn object?
 
The approach you're using is bound to have problems. You might want to do away with the PictureBox. You can use the PictureBox as a drawing container, but it won't really help unless you have a complicated user interface and need the docking/anchoring behavior for proper layout and positioning. I'll explain how you can do the drawing directly on the Form.

For starters, things will look their best if the form is double-buffered, so I recommend you set your form's DoubleBuffered property to true. As a general rule, and especially when double-buffering, you want to do all of your drawing in the form's Paint event. This is simple enough to set up:
Code:
[COLOR="Red"]Private Sub Form_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
    ' Draw here, using the e.Graphics object
End Sub[/COLOR]
Now, when you draw, the drawing surface may or may not be cleared automatically. This depends on whether or not the form has it's opaque style flag set. I think by default, the drawing surface will be cleared automatically, but you'll want to double-check (or, you'll find anyways out when you try to draw your moving circle).

I gather that you know how to draw a circle, but I'll throw the code here for completeness.
Code:
[COLOR="Red"]Dim CircleLocation As Point
Const CircleDiameter As Integer = 750[/COLOR]

Private Sub Form_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
   [COLOR="Red"] Dim circleBounds As New Rectangle(CircleLocation.X, CircleLocation.Y, CircleDiameter, CircleDiameter)
    e.Graphics.FillEllipse(Color.HotPink, circleBounds)[/COLOR]
End Sub

The location the circle will be drawn at is specified by CircleLocation. To move the circle, we need to change the value of CircleLocation and re-draw the circle. We would do this in a loop or with a timer in your case.

Updating CircleLocation is easy enough:
Code:
[COLOR="Red"]' Move right one pixel
CircleLocation.Offset(1, 0)
' Move up two pixels
CircleLocation.Offset(0, -2)[/COLOR]

The process of redrawing requires some explanation. When you do your drawing in the Paint event, you don't directly call the drawing code. Instead, you invalidate the form or control that needs to be drawn. Invalidation tells Windows that something needs to be drawn, and Windows calls your Paint event for you.

You can invalidate the entire form, to redraw the whole thing...
Code:
' Move up two pixels
CircleLocation.Offset(0, -2)
[COLOR="Red"]Me.Invalidate()[/COLOR]
Or, you can specify a rectangle to redraw, which requires more effort, but gives better performance
Code:
' Move up two pixels
[COLOR="Red"]Dim oldRect As New Rectangle _
    (CircleLocation.X, CircleLocation.Y, CircleDiameter, CircleDiameter)
[/COLOR]CircleLocation.Offset(0, -2)
[COLOR="Red"]Dim newRect As New Rectangle _
    (CircleLocation.X, CircleLocation.Y, CircleDiameter, CircleDiameter)

' We will redraw the area containing both the old and new locations of the circle
Dim invalidRect As Rectangle = Rectangle.Union(oldRect, newRect)
Me.Invalidate(invalidRect)[/COLOR]
There is still one problem, though. When you invalidate a control, there is a tiny delay before it is redrawn. The reason for this is to let Windows make things more efficient. If you invalidate more than one thing in a small amount of time, waiting a tiny bit gives Windows the chance to combine all the drawing into a single Paint event. Normally this is a good thing, but you don't want to combine your Paint events, because that would ruin your smooth animation.

The solution is easy. If you call the Update method on a control or a form, it will be drawn immediately without delay.

Code:
' Move up two pixels
Dim oldRect As New Rectangle _
    (CircleLocation.X, CircleLocation.Y, CircleDiameter, CircleDiameter)
CircleLocation.Offset(0, -2)
Dim newRect As New Rectangle _
    (CircleLocation.X, CircleLocation.Y, CircleDiameter, CircleDiameter)

' We will redraw the area containing both the old and new locations of the circle
Dim invalidRect As Rectangle = Rectangle.Union(oldRect, newRect)
Me.Invalidate(invalidRect)

[COLOR="Red"]Me.Update()[/COLOR]

That should do the trick. I took a wild guess and hoped you wanted VB, but if you program in C#, you should still be able to understand it, and if you do program in VB, be warned that there are probably syntax or other minor errors in the code; I did not type any of it into the IDE. As long as you understand the code, you should have no problem getting it to work.
 
Sorry if I was confusing with my initial post. I am wanting to create a circle about 75pixels squared and then move it across the form by 750 pixels in 750 milliseconds (so at a rate of 1pixel/ms). My problem is that as I am using a picturebox the edge of the circle, on the side moving forwards, is cut off leaving it with a flat edge. So I'm looking for a method of achieving this where the circle remains intact.

I am aware of GDI+, I was just unsure as to whether I could draw a circle such that it moves 1 pixel to the right every 1millisecond without being significantly delayed.
Would I have to redraw a circle every millisecond with the x coordinate += 1? Or can I move an already drawn object?

You can draw circles using GDI+. No need to load images.

Maybe this will help:
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace BackinBlack
{
    public partial class Form1 : Form
    {
        //Initializes the circle position at x100 and y100
        Point ptCirclePos = new Point(100,100);

        System.Windows.Forms.Timer tmrTimer = new Timer();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //I'm using a timer programatically. Feel free to use one through the control panel.
            this.tmrTimer.Interval = 1;
            this.tmrTimer.Tick += new EventHandler(tmrTimer_Tick);
            this.tmrTimer.Enabled = true;
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            /*We use the form itself as the painting object for the GDI+ output*/
            
            //Sets the quality of GDI+ to antialias
            e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            
            //We first clear the form to a black color
            e.Graphics.Clear(Color.Black);

            //Now we draw a circle with the size of 75 pixels width and height
            //We draw it using the ptCirclePos point coordinates
            e.Graphics.FillEllipse(Brushes.Yellow, ptCirclePos.X, ptCirclePos.Y, 75, 75);
        }

        private void tmrTimer_Tick(object sender, EventArgs e)
        {
            //We increase the X coordinate of the ptCirclePos
            ptCirclePos.X += 1;
            
            //The line bellow forces the application to refresh itself, triggering the Form1_Paint() event.
            this.Invalidate();
        }
    }
}

Simply copy and paste this code over the code of a new windows-form-type-project.

You will have a problem though, moving it every 1milisecond. .NET built in timers are not that precise. The StopWatch() should be used instead as it is far more precise than Form.Timer(). Don't forget to set you form's DoubleBuffered parameter to true to avoid flickering!
 
Last edited:
Snarfblam

I have the non-smooth animation problem you described in my game. I'm using Invalidate() as you described. I was going to dedicate myself over this issue at the end of the project but since you mentioned it...

Does the Update work with Overrided Paint events? Because Update() is not triggering it.
 
Well, firstly, the call to Update isn't even necessarily required. Depending on the circumstances, the painting may occur immediately, or it may be delayed until the message queue is emptied, or it may be delayed a certain amount of time. But if you are calling Update, and you're still getting choppy animation, it may simply be that you're pushing the limits of GDI+.

The mechanics of the paint event are this: when Windows feels it is appropriate, it issues a WM_PAINT message to the window in question. WinForms receives the WM_PAINT message, and in turn, calls the OnPaint method, which you may override. Control.OnPaint raises the Paint event (which is why it's important to call the base methods in these event method overrides). So, in short, calling Update should work with either the Paint event or the OnPaint method.
 
Well, firstly, the call to Update isn't even necessarily required. Depending on the circumstances, the painting may occur immediately, or it may be delayed until the message queue is emptied, or it may be delayed a certain amount of time. But if you are calling Update, and you're still getting choppy animation, it may simply be that you're pushing the limits of GDI+.

The mechanics of the paint event are this: when Windows feels it is appropriate, it issues a WM_PAINT message to the window in question. WinForms receives the WM_PAINT message, and in turn, calls the OnPaint method, which you may override. Control.OnPaint raises the Paint event (which is why it's important to call the base methods in these event method overrides). So, in short, calling Update should work with either the Paint event or the OnPaint method.

Ok then.

PS: Discard my last private message. I just made it. I came here to let you know this. I found another site which explained the whole thing step by step. I can now automatically get a realistic temperature in Kelvin of a given planet next to any star.
 
Back
Top