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