Flicker is the sign of sloppy programming and a lack of attention to detail. There is no reason why any part of a Windows program should flicker. The aim of this article is to present the reader (that's you) with the techniques used to prevent their windows applications from flickering.
Flicker is simply this: the display of one image over the top of another in rapid succession. The result of this is screen flicker, where you can see one image briefly before another one is shown on top. Personally I find applications that "flicker" annoying to use, for this one reason: If the user-interface has been badly coded, then what does this say about the rest of the application, the part that you trust your data with? An application that has a smooth, fast user interface inspires confidence in it's users - it's as simple as that.
An application can flicker in many ways. The most common cause is when a window is resized, causing the contents to flicker badly as it is redrawn.
This is the golden rule when doing any kind of painting on a computer, be it Windows or whatever OS you are using. You must never draw over the same pixel twice. A lazy programmer will often avoid putting any thought into the painting process, instead opting to take the easy route.
With the case of flickering, it is your responsiblity to ensure that no "overdraw" occurs. Now, Windows and your computer are fundamentally stupid; they won't do anything unless you instruct them explicitly. If any flickering is occuring, it is because some part of your program has deliberately overdrawn some area of the screen.
This may be because of some explicit command, or something which you have neglected to do. In either case, if your Windows program has a flickering problem, you need to understand how best to remove the problem.
The prime suspect is usually the WM_ERASEBKGND message. This message is sent to a window when it's background needs to be erased. This happens because windows are usually painted using a 2-stage process:
This makes it easy to draw a window's contents: Every time you receive a WM_PAINT message, you know that you have a nice fresh canvas to draw on. However, drawing a window twice (once with WM_ERASEBKGND, once again with WM_PAINT) will cause the window to badly flicker. Just take a look at the standard Edit control in Windows - open up Notepad.exe and resize the window, and see how the contents flicker as it is redrawn.
Right then, how do we avoid erasing the background of a window? There are two methods.
Any one of these will steps will prevent the WM_ERASEBKGND message from clearing the window. The last option is usually easiest to implement
case WM_ERASEBKGND:
return 1;
|
It is also possible to prevent WM_ERASEBKGND when you invalidate and update a window. The InvalidateRect API call's last parameter specifies whether or not a portion of a window is to have it's background erased when it is next redrawn. Specifying FALSE for this paramter prevents WM_ERASEBKGND from being sent when the window is redrawn.
InvalidateRect(hwnd, &rect, FALSE);
|
It is quite common for a Windows application to redraw it's entire window contents, even if only a small part of it changed. This is most usually the case when a window is resized - some (but not all) programs redraw the whole window. This is normally not necessary, because when a window is resized, more often than not the previous window contents is left unchanged, and the resize has just uncovered a small border which needs painting. It is not necessary to redraw the entire contents in this case. If a little thought and care is used, the painting algorithms can be written so that only the bare minimum is painted at any one time.
Every window in the system keeps an update region. This region describes the area of a window that has become invalidated and needs repainting. If a windows only updates the required area, and no more, then the window will draw much quicker as a result.
There are several ways to retrieve the update region for a window. The GetUpdateRgn API call retrieves the exact region, be it rectangular, or a more irregular shape. The GetUpdateRect API call retrieves the smallest bounding rectangle that encloses the update region. It is usually easier to just work with a rectangular area like this. The third method is to use the PAINTSTRUCT structure in conjunction with the BeginPaint/EndPaint API calls.
A normal painting procedure looks like this:
PAINTSTRUCT ps;
HDC hdc;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
// do painting
EndPaint(hwnd, &ps);
return 0;
|
BeginPaint initializes the ps (PAINTSTRUCT) structure. One member, rcPaint, is a RECT structure which describes the smallest bounding rectangle that encloses the update region (Just like the GetWindowRect API call). By only limiting drawing to just this rectangular region, painting can be dramatically sped up.
Now, Windows automatically clips any drawing you perform outside the update region when you use BeginPaint/EndPaint. This means that there is no way you can draw outside the update region even if you tried. You might think that it is pointless to make sure your code doesn't try to draw outside the update region, even when nothing will be drawn anyway. However, you are still avoiding unnecessary API calls and calculations, so I think it is always worth putting in a little more effort to get things working as fast as possible.
There are occasions when you spend alot of time and effort getting your super-duper drawing code working, only to find that your window is still getting redrawn in it's entirety. This is usually the cause of two window class styles - CS_VREDRAW and CS_HREDRAW. When a window class has either of these two styles set, the window contents will be completely redrawn every time it is resized either vertically or horizontally (or both). So, you need to turn off these two class styles. The only way to do this is to make sure your window isn't created with them in the first place, and to prevent this from happening, you have to make sure that CS_HREDRAW and CS_VREDRAW aren't included when the window class is registered.
WNDCLASSEX wc;
wc.cbSize = sizeof (wc);
wc.style = 0; /* CS_VREDRAW | CS_HREDRAW; */
...
RegisterClassEx(&wc);
|
The above example is just to help illustrate the point that these two styles must not be included when the window class is registered.
Just a word of warning here: If the main window in an application has these two class styles set, then this will cause all child windows to be redrawn during a resize, even if those children don't have the redraw flags set. This can be avoided by following the next step:
Sometimes flickering occurs because a parent window doesn't clip it's children when it paints itself. This results in the entire parent window contents being shown, and the the child windows being displayed on top (causing flicker). This can be easily solved by setting the WS_CLIPCHILDREN style on the parent window.
When a window has this style set, any areas that its child windows occupy are excluded from the update region. So, even if you try to draw over a child control, the clipping region that BeginPaint assigns will prevent you from doing so.
A common method to completely eliminate flickering windows is to use a technique called double-buffering. This basic idea is to draw a window's contents into an off-screen buffer, and then transfer this buffer to the screen in one fell-swoop (using BitBlt). This is a pretty good way to reduce flicker, but is often overused, especially by programmers who don't really understand how get efficient drawing working.
The basic way double-buffering works is like this:
HDC hdcMem;
HBITMAP hbmMem;
HANDLE hOld;
PAINTSTRUCT ps;
HDC hdc;
....
case WM_PAINT:
// Get DC for window
hdc = BeginPaint(hwnd, &ps);
// Create an off-screen DC for double-buffering
hdcMem = CreateCompatibleDC(hdc);
hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height);
hOld = SelectObject(hdcMem, hbmMem);
// Draw into hdcMem here
// Transfer the off-screen DC to the screen
BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC (hdcMem);
EndPaint(hwnd, &ps);
return 0;
|
This method is a little slow, because the offscreen memory-DC is created from scratch every time the window needs to be drawn. A more efficient method would be to create the memory DC only once, big enough so that the entire window can be painted at any time. When the application terminates, the memory DC would then be destroyed. Both these methods are potentially quite memory-intensive, especially if the memory DC needs to be the size of a screen (1024 * 768 * 32 bytes=2.5 Mb).
Double-buffering will also be twice as slow as it needs to be. Because you are drawing once to the memory-DC, then again during the "blit", you are using up clock cycles when you don't need to. Granted, a fast graphics card will perform a BitBlt very quickly, but it's still wasted CPU.
If your application needs to display quite complicated information (say, like a web-page), then you would need to use the memory-DC method. Take Internet Explorer, for instance. There is no way it would be able to render a web-page with no flickering without using double-buffering.
Double-buffering doesn't have to be used to paint a whole window. Imagine that you had just a small portion of a window that contained a complex graphic object (maybe a semi-transparent bitmap or something). You could use an off-screen DC to draw just this one region, and BitBlt that to the screen, whilst drawing the rest of the window normally.
Sometimes though, with a little careful thinking, it is often possible to avoid double-buffering and draw straight to the screen. As long as you don't break the golden rule, "Never draw over the same pixel twice", you will achieve flicker-free drawing.
What I mean by this is the following type of situation. Say, you are custom-drawing the titlebar of a window. You draw the caption first, then draw some additional graphics over the top. Now, whenever the caption needs to be painted, it will flicker. This is because you haven't followed the "golden rule". In this case, the caption is being shown briefly before additional graphics are painted on top, which appear to flicker.
There are two techniques you can use to prevent this type of flickering. The first is to use clipping, the second is to use your brain.
In the case of clipping, you can use the ExcludeClipRect API call to mask out certain areas of a device context. When an area is masked, it is not affected when painted over. Once a background has been drawn, the clipping area can be removed with SelectClipRgn, and another graphic can be painted in the previously masked-out area. By using appropriate masking (or clipping), overdraw can be eliminated in alot of cases.
The other option is to take a more intelligent approach. Imagine you had to draw a grid. A grid would normally be painted by first drawing a blank background, and then drawing a series of lines (horizontal and vertical) to create the grid effect. The problem with this type of approach is that the grid lines will appear to flicker, because the background is briefly appearing underneath each line before the lines are drawn. However, the same effect can be achieved with a different approach. Instead of drawing a single blank background, draw a series of blank squares, separated by a pixel-wide space on each side. When you come to draw the grid lines, they can be placed in the pixel-wide gaps which haven't been painted over yet. The result is the same, but this time there is no flickering because no pixel has been painted over twice.
Using your brain to think around a problem may take slightly longer than the direct "no-brainer" approach, but I think it is worth the extra effort, because the results can be so much better.
Hopefully you should never have to ask the question "Why does my window flicker?" ever again. I have presented the major causes of flickering in a windows program, and also the techniques you can use to remove this flickering. If you encounter flickering in a program you are developing, you should be able to identify the possible causes, and use the techniques described in this tutorial to completely eliminate flicker from your applications.