On Sat, 08 Dec 2007 08:11:47 -0800, Peter Webb
<we********@DIESPAMDIEoptusnet.com.auwrote:
Yes. I am looping through dozens of on-screen objects, updating their
position.
Don't do that.
That's what my program does, and spends all its time.
Even so, don't do that.
In the main animation loop, I still use a myBuffer.Render() command, as
the drawBox.Invalidate() command flickers (even though it just does a
myBuffer.Render() ).
The flicker is because calling Invalidate() causes the entire control to
be erased first, before the OnPaint() method is called. You can avoid
this in a couple of ways, but the simplest in a custom control is to just
set the DoubleBuffered property of your control to true (note that this
property has nothing to do with your own buffering...it causes the OS to
create a new off-screen buffer to be used for drawing during updates to
the control).
Flicker isn't a good reason to abandon the standard Windows drawing
model. There are better ways to fix it.
I was told before (and I think by you) to use a timer in asynchronous
thread. It didn't seem an issue in my app, as most people will want to
run it flat out, so having two threads was more complexity that I didn't
understand.
Some points here:
* You might not need a different thread. It's possible that the
Forms.Timer class provides enough accuracy for your animation. It offers
a slightly simpler way to deal with timing.
* A second thread should not be all that complicated in this case.
The trickiest part is remembering that you need to use Control.Invoke() to
call any methods that directly use the UI component. In this case, that's
likely to just be the call to Invalidate(), which you can accomplish like
this (using an anonymous method):
myCustomControl.Invoke((MethodInvoker)delegate()
{ myCustomControl.Invalidate(); });
Note: when using a second thread, you _might_ still run into timing
issues, because while the thread itself might be able to manage its own
timing reasonably well, the redraw stuff (invalidation and redrawing after
invalidation) still has to go through the normal window message queue that
the Forms.Timer object would have had to. There are some basic
limitations when writing a regular Windows applications that will be
present no matter _how_ you do the drawing, right way or wrong way.
I would recommend you try for the Forms.Timer class first, as it provides
a nice, easy API where you don't have cross-thread issues. In addition to
the requirement to invalidate from the main GUI thread (and thus the need
to use Invoke()), using a second thread will also mean you need to impose
some sort of thread synchronization. Either you'll need to essentially
double-buffer your buffer, always having a finished copy used for handling
the control update and a second copy into which you actually draw the new
data, or you'll need to use lock() to protect any code using the buffer so
that you don't have two threads trying to manipulate the data structure at
the same time. Neither is difficult, but why introduce the complexities
if you don't have to?
Note that no matter what you do, you're going to be limited to something
like 15-20 fps using a mechanism like this. Trying to update any more
frequently than that is likely to lead to uneven animation, with some
frames being displayed much shorter or longer periods of time than
others. For faster animation than that, you simply need to get away from
the standard Windows application model completely, which you're not going
to do with a regular .NET forms application.
* It's not really true that most people "will want to run it flat
out". Practically every user I've met wants to be able to exit an
application whenever they want to. Using your design won't allow that, as
the form will not respond to user input until the animation has completed.
The annoying thing is that it is just a problem at start up of the Form.
Everything else works brilliantly.
I'm not sure I'd use the term "brilliantly" to describe how _everything
else_ in your application works. You may have gotten the animation to
animate, but other than that your application isn't a very well-behaved
Windows application.
As promised to you, by failing to adhere to the standard drawing model in
Windows, you've created new, difficult-to-solve problems.
Is there some quick and dirty I can do for the start-up to make the form
appear before the animation starts (or even finishes!)?
Well, one option would be to move your animation code to some place where
you know it will take place after the form has drawn. But keep in mind
that doing that will only change the specific scenarios in which your form
won't draw. It will be trivial to demonstrate other scenarios in which
the form needs to be draw _again_, but won't be able to because you're
stuck in your animation loop.
The correct fix, and the only one I will recommend, is to fix your code so
that your control _only_ draws itself in response to WM_PAINT messages.
In other words, in the OnPaint() method, and in there drawing only the
_current_ state of the data. Updates of the data used for animation
(which in your case basically just means your off-screen buffer) must be
done outside of the drawing code. When the data is updated, simply call
Invalidate() to cause a redraw to happen.
Or if I have to do this properly, can you refer me to a web page that
might get me started?
I posted a number of useful links in the previous thread, to pages on the
MSDN web site. I believe someone else has posted one or more links to Bob
Powell's web site as well (which I can't vouch for, but I've seen other
people recommend it highly).
Mostly though, you really need to just focus on the basics. There's a
very specific, but easily-described paradigm here:
* Make sure your drawing code responds correctly to OnPaint(),
rendering the current state of the data and being able to do so at any
time.
* Don't draw from the same place where you change the data. Call
Invalidate() instead.
From that, everything else derives.
Pete