Painting on Forms

Painting on Forms

Why do you need to handle the OnPaint event to produce proper output, and why can't you paint directly over the form canvas? It depends on Windows' default behavior. As you draw on a window, Windows does not store the resulting image. When the window is covered, its contents are usually lost.

The reason for this behavior is simple: to save memory. Windows assumes it's "cheaper" in the long run to redraw the screen using code than to dedicate system memory to preserving the display state of a window. It's a classic memory-versus-CPU-cycles trade-off. A color bitmap for a 600×800 image at 256 colors requires about 480 KB. By increasing the color count or the number of pixels, you can easily reach 4 MB of memory for a 1280×1024 resolution at 16 million colors.

In the event that you want to have consistent output for your applications, you can use two techniques. The general solution is to store enough data about the output to be able to reproduce it when the system sends a painting requested. An alternative approach is to save the output of the form in a bitmap while you produce it, by placing an Image component over the form and drawing on the canvas of this image component.

The first technique, painting, is the common approach to handling output in most windowing systems, aside from particular graphics-oriented programs that store the form's whole image in a bitmap. The approach used to implement painting has a very descriptive name: store and paint. When the user clicks a mouse button or performs any other operation, you need to store the position and other elements; then, in the painting method, you use this information to paint the corresponding image.

This approach lets the application repaint its whole surface under any of the possible conditions. If you provide a method to redraw the contents of the form, and if this method is automatically called when a portion of the form has been hidden and needs repainting, you will be able to re-create the output properly.

Because this approach takes two steps, you must be able to execute these two operations in a row, asking the system to repaint the window—without waiting for the system to ask for a repaint operation. You can use several methods to invoke repainting: Invalidate, Update, Repaint, and Refresh. The first two correspond to the Windows API functions, and the latter two have been introduced by Delphi:

  • The Invalidate method informs Windows that the entire surface of the form should be repainted. The most important point is that Invalidate does not enforce a painting operation immediately. Windows stores the request and responds to it only after the current procedure has been completely executed (unless you call Application.ProcessMessages or Update) and as soon as no other events are pending in the system. Windows deliberately delays the painting operation because it is one of the most time-consuming operations. At times, with this delay, it is possible to paint the form only after multiple changes have taken place, avoiding multiple consecutive calls to the (slow) paint method.

  • The Update method asks Windows to update the contents of the form, repainting it immediately. However, this operation will take place only if there is an invalid area. This happens if the Invalidate method has just been called or as the result of an operation by the user. If there is no invalid area, a call to Update has no effect. For this reason, it is common to see a call to Update just after a call to Invalidate, as is done by the two Delphi methods Repaint and Refresh.

  • The Repaint method calls Invalidate and Update in sequence. As a result, it activates the OnPaint event immediately. A slightly different version of this method, called Refresh, by default calls Repaint. The fact that there are two methods for the same operation dates back to Delphi 1 days, when the two were subtly different.

When you need to ask the form for a repaint operation, you should generally call Invalidate, following the standard Windows approach. Doing so is particularly important when you need to request this operation frequently, because if Windows takes too much time to update the screen, the requests for repainting can be accumulated into a simple repaint action. The wm_Paint message in Windows is a low-priority message; if a request for repainting is pending but other messages are waiting, the other messages are handled before the system performs the paint action.

On the other hand, if you call Repaint several times, the screen must be repainted each time before Windows can process other messages; because paint operations are computationally intensive, this behavior can make your application less responsive. Sometimes, however, you want the application to repaint a surface as quickly as possible. In these less-frequent cases, calling Repaint is the way to go.

Note 

Another important consideration is that during a paint operation Windows redraws only the so-called update region, to speed up the operation. For this reason, if you invalidate a portion of a window, only that area will be repainted. To accomplish this, you can use the InvalidateRect and InvalidateRegion functions. This feature is a double-edged sword: It is a powerful technique that can improve speed and reduce the flickering caused by frequent repaint operations, but, it can also produce incorrect output. A typical problem occurs when only some of the areas affected by the user operations are modified, while others remain as they were even if the system executes the source code that is supposed to update them. If a painting operation falls outside the update region, the system ignores it, as if it were outside the visible area of a window.



Part I: Foundations