Continued from previous post...
Flicker is mostly caused by three (3) characteristics, they are: the presence of CS_HREDRAW and CS_VREDRAW in the window class and the processing of the WM_ERASEBKGND (erase background) window message.
The CS_HREDRAW and CS_VREDRAW cause the window's entire client area to be invalidated when the window is resized. This in turn causes the entire background of the window to be erased by the default window procedure when a WM_ERASEBKGND message is received. It is the full erasing of the background that is the source of flicker. It causes two different images to appear, the first image is the background (for a very short time) the second one is the image that is supposed to be painted on it.
Carefully controlling _what_ has to be erased when a WM_ERASEBKGND message is received can significantly reduce flicker and often eliminate it completely.
There are a number of ways to reduce or eliminate flicker, of those, double buffering is likely the most common one due to the fact that it is fairly simple to implement and it completely eliminates flicker. Double buffering consists of painting on a bitmap that is compatible with the output device (usually but not always the screen) then bitblt-ing the fully drawn bitmap onto the output device without having to erase the background first, which is not required because the bitmap is the full and final image that should appear on the output device.
Double buffering, while very effective is not without disadvantages, among them, it requires some code to implement it, often specific for each window that uses it and, it can potentially use a fair amount of memory. In addition to that, it has the potential of noticeably lowering the speed at which the windows that use it are repainted. This can be experienced when a program's window feels "elastic" when being resized. The more windows on the user interface, the greater the potential for the window to feel "elastic" when resized.
The
FlickerMax example is what happens when the window class includes CS_HREDRAW and CS_VREDRAW and the default window procedure is allowed to erase the background when processing the WM_ERASEBKGND. The resulting amount of flicker is substantial and visually offensive. It can be seen in its greatest "glory" when the window is resized horizontally.
FlickerMax uses the following code when registering the class:
if not GetClassInfoEx (hInstance, AppName, cls) then
begin
with cls do
begin
{ cbSize has already been initialized as required above }
style := CS_BYTEALIGNCLIENT or CS_VREDRAW or CS_HREDRAW;
lpfnWndProc := @WndProc; { window class handler }
cbClsExtra := 0;
cbWndExtra := 0;
and does not process the WM_ERASEBKGND thus letting the default window procedure erase the entire client area.
The
FlickerMin example is code-wise almost identical to
FlickerMax, the difference is that
FlickerMin does _not_ allow the default window procedure to erase the background, instead, it erases the rectangles that are above and below the text when processing the WM_PAINT, followed by outputting the line of text. That small difference greatly reduces the amount of flicker. Note that the flicker isn't totally eliminated in this case, resizing the
FlickerMin horizontally makes it easy to notice but, for most purposes, it can be considered tolerable.
FlickerMin is the reason for the
Rectangle example. When using the
Rectangle API it is important to keep in mind the type of pen that API uses to draw the edges of the rectangle. When the pen is a NULL pen the rectangle drawn will be one (1) pixel smaller horizontally and vertically than the specified rectangle coordinates. This is because
Rectangle uses the pen to draw the outer right and bottom edge of the rectangle and, when the pen is NULL this causes those edges not to be drawn resulting in a rectangle that is one (1) pixel short on the right and bottom edges.
This can be seen with the
Rectangle example by changing the pen used from a null pen to any other colored pen back and forth. A zoomed image of the bottom right of the resulting rectangle is attached where the difference between using a null pen and a colored pen is easier to see.
When using the
Rectangle API to erase parts of the client area background, the simplest method consists of specifying a null pen and increasing the Right and Bottom coordinates of the rectangle. This is what
FlickerMin does to erase the part of the client area that is above and below the text without erasing any portion where the new text should appear.
The little bit of flicker seen in
FlickerMin is due to its reliance on ExtTextOut to clear the narrow and wide rectangle where the text is displayed. It is possible to make the flicker imperceptible by using Rectangle to erase the area on the left and right of where the text will appear (in addition to above and below) and instruct ExtTextOut to confine its output to the rectangle that is snug to the text. (that improvement is left as an exercise for the reader
- simply two additional calls to Rectangle and a change in the rectangle used by ExtTextOut)
FlickerMin uses the same code as
FlickerMax when registering the window class but, when it gets a WM_ERASEBKGND it tells Windows that it took care of erasing the background by using the following code:
WM_ERASEBKGND:
begin
{ tell Windows we erased the background }
WndProc := 1;
exit;
end;
then when processing the WM_PAINT it uses:
{-----------------------------------------------------------------------}
{ clear the rectangles above and below the text }
{ this could have been done when processing the WM_ERASEBKGND message }
{ but it's more convenient to do here. }
CopyRect(ClearRect, ClientRect);
{ clear the rectangle above the text }
with ClearRect do
begin
{ Left, Right and Top are already set }
Bottom := TextRect.Top;
Rectangle(ps.hdc, Left, Top, Right, Bottom);
end;
{ clear the rectangle below the text }
with ClearRect do
begin
{ Left and Right are already set }
Top := TextRect.Bottom;
Bottom := ClientRect.Bottom;
Rectangle(ps.hdc, Left, Top, Right, Bottom);
end;
which erases the areas above and below the text. A couple of additional calls to rectangle could be used to erase the areas to the right and left of the text that _will_ be output to make flicker imperceptible.
One of the most important decisions is determining if CS_HREDRAW and CS_VREDRAW are necessary when registering the window class. It is, unfortunately, somewhat common to see windows include those two (2) styles in the window class when they aren't really necessary.
The HREDRAW_VREDRAW is an example. it does NOT include the CS_HREDRAW and CS_VREDRAW styles, yet it updates the window correctly and without a trace of flicker without using any methods to prevent flicker. The example includes the option of adding CS_HREDRAW and CS_VREDRAW in the class styles, when that is done, there will be a very significant amout of flicker.
Again, HREDRAW_VREDRAW does NOT include any code at all to prevent flicker. The exclusion of CS_HREDRAW and CS_VREDRAW is all that is needed to have no flicker whatsoever when resizing the window.
The CS_HREDRAW and CS_VREDRAW styles need to be included when what is displayed in the client area changes depending on the size of the window. The
Gradient is an example of this. Specifically, the color used at a particular location changes depending on the size of the window, because of this, the entire window needs to be invalidated and repainted whenever its size changes.
The
Gradient program draws everything when processing the WM_ERASEBKGND and tells Windows that it took care of erasing the background. This is just to show that, at least in some cases, it is possible to repaint the entire client area when processing the WM_ERASEBKGND. However, a "normal" program would simply return one (1) when processing WM_ERASEBKGND and redraw the gradient when dealing with WM_PAINT.
NOTE: the gradient program uses functions to draw the vertical and horizontal gradients. It would be fairly easy to roll them into a single function that uses an "orientation" parameter to select vertical or horizontal.
Gradient uses different functions to use a different number of bands depending on the orientation. The vertical orientation uses fewer bands resulting in noticeable banding while the horizontal orientation uses more bands thereby making the gradient band-less (much smoother.) Also, the difference in smoothness is more apparent when the gradient is blue than when it is grey.
ETA:The HREDRAW_VREDRAW was inadvertently left out. It is attached to a later post.
Continued on next post ...