The problem
If we want to make a simple image viewer with scroll capability, CScrollView MFC class is great. Let’s say, we have a CBitmap object in the document class, that keeps the image to be displayed. All we have to do in the view’s class (derived from CScrollView) is something like this:
- Override CScrollView::OnUpdate virtual function and set the scroll sizes according to the image sizes.
void CMyScrollView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { CDemoDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CSize sizeTotal(100, 100); // get bitmap from document CBitmap& bmpImage = pDoc->m_bmpImage; if(NULL != bmpImage.GetSafeHandle()) { // get bitmap sizes BITMAP bmp = {0}; bmpImage.GetBitmap(&bmp); sizeTotal.SetSize(bmp.bmWidth, bmp.bmHeight); } // set scroll sizes SetScrollSizes(MM_TEXT, sizeTotal); }
- Override CScrollView::OnDraw virtual function and draw the image.
void CDemoScrollView::OnDraw(CDC* pDC) { CDemoDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // get bitmap from document CBitmap& bmpImage = pDoc->m_bmpImage; if(NULL != bmpImage.GetSafeHandle()) { // create compatible memory device context CDC dcMem; dcMem.CreateCompatibleDC(pDC); // select bitmap in memory device context CBitmap* pOldBitmap = dcMem.SelectObject(&bmpImage); BITMAP bmp = {0}; bmpImage.GetBitmap(&bmp); // bit-blit from memory DC in view's DC pDC->BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &dcMem, 0, 0, SRCCOPY); // release the bitmap from memory DC dcMem.SelectObject(pOldBitmap); } }
No sweat, like a walking in the park! Of course, we can further improve it by adding more features, like image zoom and so on.
Now, let’s say, we have to draw a text over the image in a fixed position. One first attempt would be to simply draw the text in view’s client area, after the bitmap has been bit-blitted, like in the next example:
// ... // bit-blit from memory DC in view's DC pDC->BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &dcMem, 0, 0, SRCCOPY); // overlay draw text pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(RGB(255,255,0)); CPoint ptScroll = GetScrollPosition(); pDC->TextOut(ptScroll.x + 10, ptScroll.y + 10, CString(_T("some text"))); // ...
It is not working OK: when scrolling the view, some weird “effects” occur. Let’s see a little “movie” that shows what happens when the user scrolls the view.
- Let’s say we have loaded and displayed a bitmap. Additionally, using the code from above examples, we wrote a little text in the top-left corner of the view.
- When the user clicks in the vertical scrollbar, the view window receives WM_VSCROLL message with SB_PAGEDOWN scroll-bar code. If put a breakpoint in the beginning of OnDraw, the image looks like this:
After ScrollWindow and before OnDraw Somewhere in CScrollView implementation, the ScrollWindow function has been called. That simply shifted up the contents of the view. The remained area from the bottom side is invalidated in order to be further drawn in WM_PAINT message handler (in case of views, OnDraw virtual function).
- Finally, when OnDraw function is done, we get this:
Hmmn…the text has been moved up, even in OnDraw we’ve tried to put it back at the same coordinates. That’s because ScrollWindow moved up the area which contains the text. Besides, the area containing the text was not invalidated, so TextOut did nothing. Of course anyone can say: “it’s easy from now, just invalidate the whole view’s client area, somewhere at the end of scroll message handler”. Yes, may be a little bit better but still not OK. When scrolling, the text will “jump” and flicker.
In CODEPERT forum, we discussed different ideas for overcoming this problem, including the using of layered windows (that works quite fain but still rises some issues). Next time, I’ll show the best solution we found. Just keep in touch!
Resources
- MSDN: CScrollView Class
- MSDN: CWnd::ScrollWindow
See also
- Codexpert blog: Overlay Drawing in CScrollView – Part 2