Click here to Skip to main content
15,891,184 members
Please Sign up or sign in to vote.
2.00/5 (2 votes)
See more:
I have a ball image on my form. How can i animate it, striking the form boundaries, in a infinite loop...?
Posted
Comments
Pete O'Hanlon 21-Jun-11 7:54am    
What type of form is it? Win forms, ASP.NET, WPF or Silverlight. This has a real effect on the code you write.
Sergey Alexandrovich Kryukov 21-Jun-11 13:40pm    
You're absolutely right. Almost nobody tag properly; what to do.
However, without any pleasure on my side, I figured out it's about Forms, so I added a detailed solution, please see.
--SA

Are you sure you want it on the whole form, not a form control such as panel? — would take a little more effort but add a lot of flexibility in layout. What if you want to add controls to the form.

You need to do the following:

1) Set the property DoubleBuffered of your form to true.

2) Override your form's OnPaint method. Render the ball using the instance of the class Graphics passed through the event arguments. Rendering will depend on the current ball coordinates of the type System.Drawing.Point; let's make it a field of your form named BallPosition.

3) Create a separate thread, let's call it BallThread when the form is shown — override your form's OnShown method to start the thread. Attention! Some will suggest you use timer. Don't listen to them. It only seems to be easier. Always prefer thread to timer if possible.

4) In the BallThread thread, organize infinite loop, use System.Threading.Thread.Sleep for certain period of time inside the loop. Then calculate current time using System.DateTime.Now; depending on current time, update BallPosition and call Form.Invalidate. It will trigger re-rendering of the ball by sending WM_PAINT to you form; it will call your OnPaint method. Checking real time is important; if you simply calculate time by counting sleep times, you won't get sufficient accuracy due to randomness of thread scheduling and some system latency. Alternatively, you can use the class System.Diagnostics.Stopwatch.

[EDIT]
Thanks to Pete O'Hanlon for a suggestion of important improvement: a more robust way of setting a thread in a wait state is the blocking call to System.Threading.Monitor.Wait(object, int) with timeout. During normal animation flow, the thread will be awaken always by timeout, but if immediate break out of animation loop is required, the monitor can be pulsed. This technique can be used in combination with exit loop condition; it could be a Boolean member set to true for breaking the loop. In both cases, the calling thread will be switched off by OS and never scheduled back to execution until awaken, so it uses strictly zero CPU time.
However, I think if you need a simple application which runs the animation during the application life time, Thread.Sleep is just fine. On application exit you simply call Thread.Abort.
Please see our discussion with Pete in comments below.
[END EDIT]

(Important! Don't call anything on UI directly from non-UI thread.
You cannot call anything related to UI from non-UI thread. Instead, you need to use the method Invoke or BeginInvoke of System.Windows.Threading.Dispatcher (for both Forms or WPF) or System.Windows.Forms.Control (Forms only).
You will find detailed explanation of how it works and code samples in my past answers:
Control.Invoke() vs. Control.BeginInvoke()[^],
Problem with Treeview Scanner And MD5[^].)

5) To improve performance invalidate only the part of the scene: around you previous ball position (store is as a form field as well) and new one. For this purpose, use the method Invalidate with parameters (Region or Rectangle). Also, for performance reasons, do not invalidate twice. Invalidate ether the rectangle covering both ball locations or the region which is composed as a union of the regions for both current and previous ball positions.

If you need to do animation on a custom control or Panel (which I would highly recommend), you need to do the same, but you have to make a control custom by subclassing System.Windows.Forms.Control or System.Windows.Forms.Panel. The main reason for this is: otherwise you won't get access to double buffering. You need to call protected method System.Windows.Forms.Control.SetStyle. Add the following styles: System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer | System.Windows.Forms.ControlStyles. AllPaintingInWmPaint.

If you fail to use double buffering, your animation will suffer heavy flicker, by obvious reasongs.

—SA
 
Share this answer
 
v10
Comments
Abhinav S 21-Jun-11 13:41pm    
Great answer. My 5.
Sergey Alexandrovich Kryukov 21-Jun-11 13:44pm    
Thank you, Abhinav.
--SA
Pete O'Hanlon 21-Jun-11 13:58pm    
I'll award a 5 on the condition that you change the recommendation of using Thread.Sleep to a thread-friendlier pause mechanism such as a Monitor.Pause. Thread.Sleep is generally a bad habit to get into inside thread-code because you can't cancel that thread until it's woken up, whereas a Monitor allows you to pulse the thread to wake it.
Sergey Alexandrovich Kryukov 21-Jun-11 14:03pm    
Never tried it. In my experience, Sleep is awaken if you use Thread.Abort (am I right? will you check up?), and in this application it would be fine. However, thank you for the good advice, I'll update the solution.
--SA
Sergey Alexandrovich Kryukov 21-Jun-11 14:24pm    
Updated, please check up. See also my argument in favor of limited use of Sleep. I'm completely agree with you about more serious scenarios though.

I typically use an instance of EventWaitHandle instead of Sleep which throttles the thread based on data arrived or similar events, because Sleep is associated with polling which is evil. For simple animation like the one OP wants it could be robust enough, don't you think so?

Thank you for your help.
--SA
From what I understand, you are looking for is a simple object collision sample (within form boundaries). This[^] could help you get started.

The first answer is more comprehensive in terms of the strategy to implement this though.
 
Share this answer
 
v2
Comments
Sergey Alexandrovich Kryukov 21-Jun-11 13:47pm    
I would not recommend using timer, as in this sample. What happen if timer callback or event handling is not finished when the next event comes (for example)? The thread is much safer and easier to implement. By that reason, I voted 4 this time.
--SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900