25.3 Persistent display

It takes some work to make a Windows program have a persistent display that stays in place even if the window is resized or covered and then uncovered.

The OnDraw method

The standard practice is to have our Windows programs do all their writing to the screen within one single function, the CView::OnDraw method. When does your CView get an OnDraw message? Here are four important ways that this can happen.

  • When the CView is first created, whether automatically at startup, by a File | New call or by a Window | New call. First there's a call to the constructor, then a call to OnCreate, and then a call to OnDraw.

  • Whenever your CView gets resized, whether by dragging a corner using a command like Window | Tile, or by clicking the Maximize or Restore buttons in the upper right-hand window corner. First there's a call on OnSize, and then a call to OnDraw.

  • When you uncover part of your window that has been covered by another window, whether by clicking on your window to bring it to the 'front' of onscreen windows, or by dragging a covering window to one side. Your CView gets an OnDraw call. If you put the line pDC->GetClipBox(rect) inside your OnDraw code, you'll find that the rect will receive the client window coordinates of the smallest rectangle that covers the part of the window that was just uncovered. This rectangle is known as the clip box of the CDC.

  • When the CView::Invalidate function is called from within one of the other CView functions. This will queue up a message telling the CView to redraw itself as soon as there are no other messages for it to handle on its queue. If you want the redrawing operation to happen immediately and with no lapse in time, you sometimes follow a call to Invalidate by a call to UpdateWindow.

(By the way, if you try and do an UpdateWindow call without calling Invalidate first, the call may have no visible effect. OnDraw generally only redraws as much of the window as it 'thinks' needs to be done. A call to Invalidate tells the OnDraw that the whole window needs to be redrawn, but a naked UpdateWindow call on its own will generate an OnDraw call with a CDC* argument that 'thinks' that none of the window really needs redrawing.)

Bitmaps or display lists?

There are actually two possible approaches towards keeping a persistent appearance in our onscreen window, and we end up using both of them. These are sometimes called the display list approach and the bitmap approach.

The idea behind a display list approach is to set up code which will keep track of every time the user draws something on the screen and then 'replay' this during OnDraw. Because you have your items in a list, you can remove things from the list and add things, too. Keeping track of the display list takes a goodly amount of program machinery, especially if you write your own display list code. But thanks to the CArray template it's not so hard to do.

The display list approach embodies the insight that you can store a complicated graphics file rather compactly by getting a list of structures, with each structure including, say, a location and a description of what you wrote there. Any formatted word-processor file is a kind of display list. Instead of storing bitmap images of your pages, the file just stores the ASCII codes of the letters and information about their locations. The popular Adobe PostScript format extends this technique to files with lots of graphic information. There are two big wins with display lists. (1) They take up much less room than bitmaps. (2) They store the image in a format that is independent of the resolution. Printers typically have a much higher resolution than computer screens (a laser printer might have 600 dots per inch, while a typical monitor will show something more like 75 pixels per inch.)

Display lists usually use real-number-valued positions, where a 'real number' is a mathematician's expression for what a computer programmer calls a 'double' or a 'float.' We use our cVector class to hold pairs or triples of real numbers.

The idea behind the 'bitmap' approach is to keep a background bitmap as big as the window's largest possible size and do a pixel-for-pixel copy from this bitmap to the screen whenever you need to repair part of your window in an OnDraw. Windows has a powerful BitBlt function for moving blocks of pixels very rapidly. ('Blt ' stands for 'bl ock t ransfer,' and you pronounce 'blt' like 'blit.') There is also a StretchBlt function which can copy a block of pixels to an area of a different size. StretchBlt can help to make the 'bitmap' approach relatively resolution-independent, although one tries to avoid using StretchBlt whenever possible, both because it's slow and because its results aren't always pretty. Stretching a small bitmap to a large size introduces the 'jaggies,' while shrinking a large bitmap to a small size is done by rows and columns of pixels getting skipped, which can lead to a poor image.

Broadly speaking, paint programs or image-retouching programs tend to use a bitmap-based approach. What's being changed in these programs is primarily at the individual pixel level. A bitmap the size of a detailed photograph or image scan is quite large. It's easy for an image editor to run out of memory, and to start acting sluggishly as it pages memory from the RAM out to temporary files on the hard disk.

Computer drafting programs, mesh design programs like 3D Studio Max and libraries like OpenGL tend to use a display list approach. What's being done here is to compute things like the cVector coordinates of object positions. These programs are less memory-intensive, but they need to do a lot of computations in order to 'move' things around.

All professional programs will actually have some display list-like aspects and some bitmap-like aspects. Most 3D computer games use creatures that are based on display list meshes of polygons, with the polygons being filled in using pixel colors from bitmap skins.

Even in two dimensions, it's going to be worthwhile for our computer game programs to incorporate aspects of both the display list approach and the bitmap approach. The display list approach is an excellent way of achieving resolution-independence. And the bitmap method of refreshing the OnDraw method proves to be essential for preventing screen flicker. The bitmap approach is especially crucial for achieving the smooth real time animation effects you want in a program that shows rapidly moving objects. Our approach in designing our cGraphicsMFC class is that each of our views that is using Windows graphics will own a memory bitmap that's wrapped inside an instance of a new class called a cMemoryDC. (The situation works a bit differently with OpenGL graphics, as the OpenGL library automatically provides a kind of memory bitmap or buffered graphics output.) The array of moving game cCritter objects, on the other hand, acts a bit like a display list. In Windows graphics mode, a view's runcycle will go like this.

  • Update the cVector -valued positions of the list of critters that are stored in the document.

  • Convert the cVector positions into CPoint pixel positions relative to your CView client area. Blank out the old information in the cMemoryDC. Draw any desired background into the cMemoryDC. And then draw icons for the critters at the computed CPoint positions in your cMemoryDC.

  • Use the CDC::BitBlt function to rapidly copy the cMemoryDC bitmap to the onscreen CDC.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference