As the lack of scroll bars in EX04A and EX04B indicates, the MFC CView class, the base class of CEx04bView, doesn't directly support scrolling. Another MFC library class, CScrollView, does support scrolling. CScrollView is derived from CView. We'll create a new program, EX04C, that uses CScrollView in place of CView. All the coordinate conversion code you added in EX04B sets you up for scrolling.
The CScrollView class supports scrolling from the scroll bars but not
from the keyboard. It's easy enough to add keyboard scrolling, so we'll do it.
A Window Is Larger than What You See
If you use the mouse to shrink the size of an ordinary window, the contents
of the window remain anchored at the top left of the window, and items at
the bottom and/or on the right of the window disappear. When you expand
the window, the items reappear. You can correctly conclude that a window is
larger than the viewport that you see on the screen. The viewport doesn't have to
be anchored at the top left of the window area, however. Through the use of
the CWnd functions ScrollWindow and
SetWindowOrg, the CScrollView class
allows you to move the viewport anywhere within the window, including areas
above and to the left of the origin.
Scroll Bars
Microsoft Windows makes it easy to display scroll bars at the edges of a
window, but Windows by itself doesn't make any attempt to connect those
scroll bars to their window. That's where the
CScrollView class fits in.
CScrollView member functions process the WM_HSCROLL and WM_VSCROLL
messages sent by the scroll bars to the view. Those functions move the viewport
within the window and do all the necessary housekeeping.
Scrolling Alternatives
The CScrollView class supports a particular kind of scrolling that involves one big window and a small viewport. Each item is assigned a unique position in this big window. If, for example, you have 10,000 address lines to display, instead of having a window 10,000 lines long, you probably want a smaller window with scrolling logic that selects only as many lines as the screen can display. In that case, you should write your own scrolling view class derived from CView.
Microsoft Windows NT uses 32-bit numbers for logical coordinates, so your logical coordinate space is almost unlimited. Microsoft Windows 95, however, still has some 16-bit components, so it uses 16-bit numbers for logical coordinates, limiting values to the range -32,768 to 32,767. Scroll bars send messages with 16-bit values in both operating systems. With these facts in mind, you probably want to write code to the lowest common denominator, which is Windows 95.
You'll be seeing more of the
OnInitialUpdate function when you study the document-view architecture, starting in Chapter 16. The virtual OnInitial-Update function is important here because it is the first function called by the
framework after your view window is fully created. The framework calls
OnInitialUpdate before it calls OnDraw for the first time, so
OnInitialUpdate is the natural place for setting the logical size and mapping mode for a
scrolling view. You set these parameters with a call to the CScrollView::SetScrollSizes
function.
Accepting Keyboard Input
Keyboard input is really a two-step process. Windows sends WM_KEYDOWN and WM_KEYUP messages, with virtual key codes, to a window, but before they get to the window they are translated. If an ANSI character is typed (resulting in a WM_KEYDOWN message), the translation function checks the keyboard shift status and then sends a WM_CHAR message with the proper code, either uppercase or lowercase. Cursor keys and function keys don't have codes, so there's no translation to do. The window gets only the WM_KEYDOWN and WM_KEYUP messages.
You can use ClassWizard to map all these messages to your view. If
you're expecting characters, map WM_CHAR; if you're expecting other
keystrokes, map WM_KEYDOWN. The MFC library neatly supplies the character code
or virtual key code as a handler function parameter.
The EX04C ExampleScrolling
The goal of EX04C is to make a logical window 20 centimeters wide by 30 centimeters high. The program draws the same ellipse that it drew in the EX04B project. You could edit the EX04B source files to convert the CView base class to a CScrollView base class, but it's easier to start over with AppWizard. AppWizard generates the OnInitialUpdate override function for you. Here are the steps:
private: CRect m_rectEllipse; int m_nColor;
These are the same data members that were added in the EX04A and EX04B projects.
void CEx04cView::OnInitialUpdate() { CScrollView::OnInitialUpdate(); CSize sizeTotal(20000, 30000); // 20 by 30 cm CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2); CSize sizeLine(sizeTotal.cx / 50, sizeTotal.cy / 50); SetScrollSizes(MM_HIMETRIC, sizeTotal, sizePage, sizeLine); }
void CEx04cView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case VK_HOME: OnVScroll(SB_TOP, 0, NULL); OnHScroll(SB_LEFT, 0, NULL); break; case VK_END: OnVScroll(SB_BOTTOM, 0, NULL); OnHScroll(SB_RIGHT, 0, NULL); break; case VK_UP: OnVScroll(SB_LINEUP, 0, NULL); break; case VK_DOWN: OnVScroll(SB_LINEDOWN, 0, NULL); break; case VK_PRIOR: OnVScroll(SB_PAGEUP, 0, NULL); break; case VK_NEXT: OnVScroll(SB_PAGEDOWN, 0, NULL); break; case VK_LEFT: OnHScroll(SB_LINELEFT, 0, NULL); break; case VK_RIGHT: OnHScroll(SB_LINERIGHT, 0, NULL); break; default: break; } }
CEx04cView::CEx04cView() : m_rectEllipse(0, 0, 4000, -4000) { m_nColor = GRAY_BRUSH; } . . . void CEx04cView::OnDraw(CDC* pDC) { pDC->SelectStockObject( m_nColor); pDC->Ellipse(m_rectEllipse); }
These functions are identical to those used in the EX04A and EX04B projects.
void CEx04cView::OnLButtonDown(UINT nFlags, CPoint point) { CClientDC dc(this); OnPrepareDC(&dc); CRect rectDevice = m_rectEllipse; dc.LPtoDP(rectDevice); if (rectDevice.PtInRect(point)) { if (m_nColor == GRAY_BRUSH) { m_nColor = WHITE_BRUSH; } else { m_nColor = GRAY_BRUSH; } InvalidateRect(rectDevice); } }
This function is identical to the OnLButtonDown handler in the EX04B project. It calls OnPrepareDC as before, but there is something different. The CEx04bView class doesn't have an overridden OnPrepareDC function, so the call goes to CScrollView::OnPrepareDC. That function sets the mapping mode based on the first parameter to SetScrollSizes, and it sets the window origin based on the current scroll position. Even if your scroll view used the MM_TEXT mapping mode, you'd still need the coordinate conversion logic to adjust for the origin offset.