Here are a few tips from personal experience (rather than an article):
- Never have a control or form's Paint
event trigger complex rendering code
directly
A common mistake I see in C# apps is when the developer puts the code to render a control in the control's Paint event handler. In normal usage, this works fine (maybe): when the display needs to be updated, the programmer calls Invalidate()
or Refresh()
on the control, which generates a Paint event, and thus the control is redrawn.
The problem is that a control's Paint event can also be triggered by external happenings in Windows, such as when a user drags another form over yours. This will generate multiple Paint events in a short time period, which will overwhelm even modestly time-consuming rendering code.
The only thing that should happen inside a control's Paint event is a copying from an off-screen drawing surface to the control's visible surface (either through Graphics.DrawImage
or BitBlt
). In a double-buffered control, all the complex rendering is done to an offscreen buffer, and when the rendering operation is complete the entire contents of the buffer are copied to a second offscreen buffer. This second buffer is what's copied to the control's surface inside the Paint event.
The reason you need two offscreen buffers here instead of one is so that the first buffer can be rendered to while the second is used as the source for any Paint events that come along in the meantime.
If your application runs fine normally but seems to sort of lock up and look weird when you drag another form over it, and you have a large amount of code inside Paint events, you definitely have this problem.
- Instantiate as few controls as
possible
In Windows programming, forms with a huge number of controls on them are pretty much doomed from the start, from a system resources standpoint as well as from a UI usability standpoint. If your design calls for a large number of controls to all be visible on a form at the same time, I can only suggest trying to simplify this wherever possible.
If your design incorporates a large number of controls so that they are all available but not all visible at one time (like maybe on tab pages, for example) try instead doing "lazy loading" of these controls, where you don't create them until they're actually visible to the user (and you dispose of them when they're not visible).
If you have a composite control that contains multiple smaller owner-drawn user controls which are primarily for display (as opposed to being for user interaction), consider not implementing these elements as actual user controls, and instead code them so that they render themselves onto some other control's surface.
Example: let's say you want a form that has 200 little LEDs on it that can be off or on and all different colors. You could write an LED UserControl and then put 200 instances of it on your form, but this would immediately push you into "I don't know how many controls is too many, but that's too many" territory.
A better approach would be to write a single LEDPanel control (double-buffered as described above) and an LED class that consists basically of a Bitmap
and X and Y coordinates. Whenever you tell the LEDPanel to re-draw itself, it would iterate through its collection of LEDs and copy their bitmaps onto its own rendering buffer.
The difference here might seem trivial, but it's really not. A .NET app can happily keep a huge number of large objects (like Bitmaps) in memory, but an actual control constitutes a much more limited resource. If I had a form with 50 controls on it, I would start to be worried about my design (if I even let it go that far).
Also, doing something like LED as a class would let you lighten this design in a way that a control wouldn't (easily, at least). Let's say that an LED can be either off or on, and if on, either red, green, blue or white. With 200 user controls, each control would basically have its own bitmap, so there's no way to share between all LEDs that are the same color.
With an LED class, however, you could add a static collection of 5 bitmaps (off, red, green, blue and white), and then each instance of LED would just have an index instead of an actual bitmap, and its Bitmap
property would just return a reference to one of the bitmaps in the static collection. This way, you only have 5 bitmaps lying around no matter how many LEDs you're displaying.
- Don't PInvoke SendMessage for anything (even if it seems to work)
If your complex controls are properly double-buffered (and you don't have too many of them on your form), then you won't need to be sending around any WM_SETREDRAW messages. The use of SendMessage
for rendering a control in .NET is something I've honestly never seen before, and I'd say it definitely qualifies as a "code smell" (i.e. probably not a good idea, generally).