You've seen an example program that displays a bitmap that originated outside the program. Now you'll see an example program that generates its own bitmap to support smooth motion on the screen. The principle is simple: you draw on a memory device context with a bitmap selected, and then you zap the bitmap onto the screen.
In the EX05C example in Chapter 5, the user dragged a circle with the mouse. As the circle moved, the display flickered because the circle was erased and redrawn on every mouse-move message. EX11B uses a GDI bitmap to correct this problem. The EX05C custom code for mouse message processing carries over almost intact; most of the new code is in the OnPaint and OnInitialUpdate functions.
In summary, the EX11B OnInitialUpdate function creates a memory device context and a bitmap that are compatible with the display. The OnPaint function prepares the memory device context for drawing, passes OnDraw a handle to the memory device context, and copies the resulting bitmap from the memory device context to the display.
Here are the steps to build EX11B from scratch:
private: const CSize m_sizeEllipse; CPoint m_pointTopLeft; BOOL m_bCaptured; CSize m_sizeOffset; CDC* m_pdcMemory; CBitmap* m_pBitmap;
CEx11bView::CEx11bView() : m_sizeEllipse(100, -100), m_pointTopLeft(10, -10), m_sizeOffset(0, 0) { m_bCaptured = FALSE; m_pdcMemory = new CDC; m_pBitmap = new CBitmap; } CEx11bView::~CEx11bView() { delete m_pBitmap; // already deselected delete m_pdcMemory; }
void CEx11bView::OnInitialUpdate() { CScrollView::OnInitialUpdate(); CSize sizeTotal(800, 1050); // 8-by-10.5 inches CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2); CSize sizeLine(sizeTotal.cx / 50, sizeTotal.cy / 50); SetScrollSizes(MM_LOENGLISH, sizeTotal, sizePage, sizeLine); // creates the memory device context and the bitmap if (m_pdcMemory->GetSafeHdc() == NULL) { CClientDC dc(this); OnPrepareDC(&dc); CRect rectMax(0, 0, sizeTotal.cx, -sizeTotal.cy); dc.LPtoDP(rectMax); m_pdcMemory->CreateCompatibleDC(&dc); // makes bitmap same size as display window m_pBitmap->CreateCompatibleBitmap(&dc, rectMax.right, rectMax.bottom); m_pdcMemory->SetMapMode(MM_LOENGLISH); } }
CPaintDC dc(this); OnPrepareDC(&dc); OnDraw(&dc);
In this example, you will be using the OnPaint function to reduce screen flicker through the use of a memory device context. OnDraw is passed this memory device context for the display, and it is passed the printer device context for printing. Thus, OnDraw can perform tasks common to the display and to the printer. You don't need to use the bitmap with the printer because the printer has no speed constraint.
The OnPaint function must perform, in order, the following three steps to prepare the memory device context for drawing:
After the memory device context is prepared, OnPaint can call OnDraw with a memory device context parameter. Then the CDC::BitBlt function copies the updated rectangle from the memory device context to the display device context. Add the following boldface code:
void CEx11bView::OnPaint() { CPaintDC dc(this); // device context for painting OnPrepareDC(&dc); CRect rectUpdate; dc.GetClipBox(&rectUpdate); CBitmap* pOldBitmap = m_pdcMemory->SelectObject(m_pBitmap); m_pdcMemory->SelectClipRgn(NULL); m_pdcMemory->IntersectClipRect(&rectUpdate); CBrush backgroundBrush((COLORREF) ::GetSysColor(COLOR_WINDOW)); CBrush* pOldBrush = m_pdcMemory->SelectObject(&backgroundBrush); m_pdcMemory->PatBlt(rectUpdate.left, rectUpdate.top, rectUpdate.Width(), rectUpdate.Height(), PATCOPY); OnDraw(m_pdcMemory); dc.BitBlt(rectUpdate.left, rectUpdate.top, rectUpdate.Width(), rectUpdate.Height(), m_pdcMemory, rectUpdate.left, rectUpdate.top, SRCCOPY); m_pdcMemory->SelectObject(pOldBitmap); m_pdcMemory->SelectObject(pOldBrush); }
InvalidateRect(rectOld, TRUE); InvalidateRect(rectNew, TRUE);
to
InvalidateRect(rectOld, FALSE); InvalidateRect(rectNew, FALSE);
If the second CWnd::InvalidateRect parameter is TRUE (the default), Windows erases the background before repainting the invalid rectangle. That's what you needed in EX05C, but the background erasure is what causes the flicker. Because the entire invalid rectangle is being copied from the bitmap, you no longer need to erase the background. The FALSE parameter prevents this erasure.
Is the circle's movement smoother now? The problem is that the bitmap is only 8-by-10.5 inches, and if the scrolling window is big enough, the circle goes off the edge. One solution to this problem is to make the bitmap as big as the largest display.
EX11B is a crude attempt at Windows animation. What if you wanted to move an angelfish instead of a circle? Win32 doesn't have an Angelfish function (yet), so you'd have to keep your angelfish in its own bitmap and use the StretchBlt mask ROP codes to merge the angelfish with the background. You'd probably keep the background in its own bitmap, too. These techniques are outside the scope of this book. If you are interested in learning more about Windows Animation, run out and get Nigel Thompson's Animation Techniques in Win32 (Microsoft Press, 1995). After you read it, you can get rich writing video games for Windows!