Unfortunately, there is one small problem with Ruler's output: Unless you're running the program on a very high resolution video adapter, you can't see everything it draws. Even on a 1,280-pixel by 1,204-pixel screen, the window can't be stretched wide enough to make all the output visible. What doesn't fit inside the window's client area is clipped by the GDI. You could modify the sample program to make the ruler shorter, but that still wouldn't do much for someone running Windows on a 640-by-480 screen. No, there's a better solution, one that's entirely independent of the screen resolution. That solution is a scroll bar.
A scroll bar is a window with an arrow at each end and a traveling "thumb" in between that can be dragged with the mouse. Scroll bars can be oriented horizontally or vertically, but never at an angle. When the user clicks one of the scroll bar arrows, moves the thumb, or clicks the scroll bar shaft, the scroll bar informs the window it's attached to by sending it a message. It's up to the window to decide what, if anything, to do with that message because a scroll bar does very little on its own. It doesn't, for example, magically scroll the window's contents. What it does do is provide a very intuitive and universally recognized mechanism for scrolling backward and forward over a virtual landscape that's too large to fit within the physical confines of a window.
Adding a scroll bar to a window is one of the easiest things you'll ever do in a Windows program. To add a vertical scroll bar, create the window with the WS_VSCROLL style. To add a horizontal scroll bar, use the WS_HSCROLL style. To add horizontal and vertical scroll bars, use both WS_VSCROLL and WS_HSCROLL. Recall from Chapter 1 that the third parameter passed to CFrameWnd::Create is the window style, and that the default is WS_OVERLAPPEDWINDOW. An application that creates a conventional frame window with the statement
Create (NULL, _T ("My Application")); |
can create a frame window containing a vertical scroll bar with the statement
Create (NULL, _T ("My Application"), WS_OVERLAPPEDWINDOW ¦ WS_VSCROLL); |
Accordingly, Windows provides a scroll bar that extends the height of the window's client area from top to bottom on the right side. If you'd rather have the scroll bar appear on the left, include a WS_EX_LEFTSCROLLBAR flag in Create's optional dwExStyle (seventh) parameter.
After you create a scroll bar, you should initialize it with a range, position, and page size. The range is a pair of integers that define the upper and lower limits of the scroll bar's travel. The position is an integer value that specifies the current location within that range; its value is reflected in the position of the scroll bar thumb. The page size sets the size of the thumb to provide a visual representation of the relationship between the size of the window and the size of the scrollable view. For example, if the scroll bar range is 0 to 100 and the page size is 50, the thumb size is half the scroll bar length. If you don't set the page size, Windows picks a default, nonproportional thumb size for you.
One way to set a scroll bar's range and position is with the CWnd::SetScrollRange and CWnd::SetScrollPos functions. The statement
SetScrollRange (SB_VERT, 0, 100, TRUE); |
sets a vertical scroll bar's range to 0 through 100, while the statement
SetScrollPos (SB_VERT, 50, TRUE); |
sets the current position to 50 and consequently moves the thumb to the middle of the scroll bar. (For horizontal scroll bars, use SB_HORZ instead of SB_VERT.) A scroll bar maintains a record of its current range and position internally. You can query for those values at any time with CWnd::GetScrollRange and CWnd::GetScrollPos.
The TRUE parameter passed to SetScrollRange and SetScrollPos specifies that the scroll bar should be redrawn to reflect the change. You can prevent redraws by specifying FALSE. If you specify neither TRUE nor FALSE, both SetScrollRange and SetScrollPos default to TRUE. You generally want a scroll bar to redraw itself after one of these functions is called, but not if both are called in quick succession. Redrawing a scroll bar twice in a very short period of time produces an undesirable flashing effect. If you're setting the range and the position together, do it like this:
SetScrollRange (SB_VERT, 0, 100, FALSE); SetScrollPos (SB_VERT, 50, TRUE); |
SetScrollPos and SetScrollRange date back to the very first version of Windows. In today's versions, the preferred way to set a scroll bar's range and position is with the CWnd::SetScrollInfo function. In addition to allowing the range and the position to be set with a single function call, SetScrollInfo also provides a means—the only means, as it turns out—for setting the page size. SetScrollInfo accepts three parameters:
SCROLLINFO is defined as follows in Winuser.h:
typedef struct tagSCROLLINFO { UINT cbSize; UINT fMask; int nMin; int nMax; UINT nPage; int nPos; int nTrackPos; } SCROLLINFO, FAR *LPSCROLLINFO; |
cbSize specifies the size of the structure, nMin and nMax specify the scroll bar range, nPage specifies the page size, and nPos specifies the position. nTrackPos is not used in calls to SetScrollInfo, but it returns the scroll bar's thumb position when the complementary GetScrollInfo function is called to retrieve information about the scroll bar while the thumb is being dragged. The fMask field holds a combination of one or more of the following bit flags:
SetScrollInfo ignores fields for which bit flags are not specified. The statements
SCROLLINFO si; si.fMask = SIF_POS; si.nPos = 50; SetScrollInfo (SB_VERT, &si, TRUE); |
set the position while leaving the range and page size unaffected, and
SCROLLINFO si; si.fMask = SIF_RANGE ¦ SIF_POS ¦ SIF_PAGE; // Or SIF_ALL si.nMin = 0; si.nMax = 99; si.nPage = 25; si.nPos = 50; SetScrollInfo (SB_VERT, &si, TRUE); |
sets the range, page size, and position in one operation. You don't need to initialize cbSize before calling SetScrollInfo or GetScrollInfo because MFC initializes it for you.
You can make a scroll bar disappear by setting the upper limit of its range equal to the lower limit. The scroll bar doesn't go away entirely; it's still there, even though you can't see it, and—more important—you can bring it back by making the range upper and lower limits different again. This turns out to be quite a useful trick if you want to hide the scroll bar because the window has been enlarged to the point that a scroll bar is no longer required. SetScrollInfo's SIF_DISABLENOSCROLL flag prevents a scroll bar from accepting further input, but it doesn't make the scroll bar disappear. Having a disabled scroll bar visible inside a window can be confusing to users, who are apt to wonder why the scroll bar is there if it can't be used.
When you set a scroll bar's range, page size, and position, here's a convenient model to keep in mind. Suppose your window's client area is 100 units high and the workspace you want to cover with a vertical scroll bar is 400 units high. Set the scroll bar range to 0-399 and the page size to 100. Accordingly, Windows will draw the scroll bar thumb so that it is one-fourth the height of the scroll bar. When the scroll bar position is 0, the thumb is positioned at the top of the scroll bar. As the thumb is scrolled down, scroll the contents of your window up an amount proportional to the distance the thumb was moved. If you limit the scroll bar's maximum position to 300 (the difference between the magnitude of the scroll bar range and the page size), the bottom of the thumb will reach the bottom of the scroll bar at the same time that the bottom of the workspace scrolls into view at the bottom of the window.
Since a scroll bar's thumb size reflects the relative size of the window compared to the width or the height of the virtual workspace, you should update the thumb size when the window size changes. It's easy to do: Just call SetScrollInfo with an SIF_PAGE flag each time your window receives a WM_SIZE message. The first WM_SIZE message comes when a window is created. Subsequent WM_SIZE messages arrive whenever the window's size changes. In MFC, an ON_WM_SIZE entry in a class's message map directs WM_SIZE messages to a handler named OnSize. The handler is prototyped as follows:
afx_msg void OnSize (UINT nType, int cx, int cy) |
The nType parameter informs the window whether it has been minimized, maximized, or simply resized by using the code SIZE_MINIMIZED, SIZE_MAXIMIZED, or SIZE_RESTORED, respectively. cx and cy are the client area's new width and height in pixels. If you know the dimensions of your application's virtual workspace, you can set the thumb size accordingly.
A scroll bar notifies its owner (the window to which it is attached) of scroll bar events by sending messages. A horizontal scroll bar sends WM_HSCROLL messages, and a vertical scroll bar sends WM_VSCROLL messages. In MFC, these messages are directed to a window's OnHScroll and OnVScroll functions by ON_WM_HSCROLL and ON_WM_VSCROLL entries in the window's message map. Scroll bar message handlers are prototyped like this:
afx_msg void OnHScroll (UINT nCode, UINT nPos, CScrollBar* pScrollBar) afx_msg void OnVScroll (UINT nCode, UINT nPos, CScrollBar* pScrollBar) |
nCode identifies the type of event that precipitated the message; nPos contains the latest thumb position if the thumb is being dragged or was just dragged and released; and, for a scroll bar that was created by adding a WS_HSCROLL or WS_VSCROLL style bit to a window, pScrollBar is NULL.
There are seven different event notifications that an application might receive in OnVScroll's nCode parameter, as shown in the table below.
Event Code | Sent When |
---|---|
SB_LINEUP | The arrow at the top of the scroll bar is clicked. |
SB_LINEDOWN | The arrow at the bottom of the scroll bar is clicked. |
SB_PAGEUP | The scroll bar shaft is clicked between the up arrow and the thumb. |
SB_PAGEDOWN | The scroll bar shaft is clicked between the thumb and down arrow. |
SB_ENDSCROLL | The mouse button is released, and no more SB_LINEUP, SB_LINEDOWN, SB_PAGEUP, or SB_PAGEDOWN notifications are forthcoming. |
SB_THUMBTRACK | The scroll bar thumb is dragged. |
SB_THUMBPOSITION | The thumb is released after being dragged. |
Horizontal scroll bars send the same notifications as vertical scroll bars, but the notifications have slightly different meanings. For a horizontal scroll bar, SB_LINEUP signifies that the left arrow was clicked, SB_LINEDOWN means the right arrow was clicked, SB_PAGEUP means the scroll bar was clicked between the left arrow and the thumb, and SB_PAGEDOWN means the scroll bar was clicked between the thumb and the right arrow. If you prefer, you can use SB_LINELEFT, SB_LINERIGHT, SB_PAGELEFT, and SB_PAGERIGHT rather than SB_LINEUP, SB_LINEDOWN, SB_PAGEUP, and SB_PAGEDOWN. The discussions in the remainder of this chapter deal exclusively with vertical scroll bars, but keep in mind that anything said about vertical scroll bars also applies to horizontal scroll bars.
If the user clicks a scroll bar or scroll bar arrow and leaves the mouse button pressed, a series of SB_LINEUP, SB_LINEDOWN, SB_PAGEUP, or SB_PAGEDOWN notifications will arrive in rapid succession—similar to the stream of typematic key codes generated when a key is held down. SB_ENDSCROLL terminates a stream of UP or DOWN notifications and indicates that the mouse button has been released. Even a single click of a scroll bar or scroll bar arrow generates an UP or a DOWN notification followed by an SB_ENDSCROLL notification. Similarly, a window is bombarded with SB_THUMBTRACK notifications that report new thumb positions as a scroll bar thumb is dragged, and it receives an SB_THUMBPOSITION notification when the thumb is released. When an SB_THUMBTRACK or SB_THUMBPOSITION notification arrives, the message's nPos parameter holds the latest thumb position. For other event codes, the value of nPos is undefined.
How your program responds to scroll bar event messages is up to you. Most programs that use scroll bars disregard SB_ENDSCROLL messages and respond to SB_LINEUP, SB_LINEDOWN, SB_PAGEUP, and SB_PAGEDOWN messages instead. A typical response to SB_LINEUP and SB_LINEDOWN messages is to scroll the contents of the window up or down one line and call SetScrollPos or SetScrollInfo to set the new scroll bar position and update the thumb location. "Line" can have whatever physical meaning you want it to have; it might mean 1 pixel, or it might mean the height of one line of text. Similarly, the usual response to SB_PAGEUP and SB_PAGEDOWN messages is to scroll up or down a distance equal to or slightly less than one "page," which is typically defined as the height of the window's client area or slightly less, and to call SetScrollInfo to set the new scroll position. In any event, it's your responsibility to update the scroll bar position. The scroll bar doesn't do that by itself.
Another, though less common, approach to processing UP and DOWN notifications is to continually move the scroll bar thumb by calling SetScrollPos or SetScrollInfo but to defer scrolling the window until an SB_ENDSCROLL notification arrives. I once used this technique in a multimedia application that was relatively slow to respond to positional changes so that the latency of commands sent to a CD-ROM drive wouldn't impede the smooth movement of the scroll bar thumb.
SB_THUMBTRACK and SB_THUMBPOSITION notifications are handled a little differently. Since SB_THUMBTRACK notifications are liable to come fast and furious when the thumb is dragged, some Windows applications ignore SB_THUMBTRACK notifications and respond only to SB_THUMBPOSITION notifications. In this case, the window doesn't scroll until the thumb is released. If you can scroll the contents of your window quickly enough to keep up with SB_THUMBTRACK notifications, you can make your program more responsive to user input by scrolling as the thumb is dragged. It's still up to you to update the scroll bar position each time you scroll the window. Windows animates the movement of the scroll bar thumb as it's dragged up and down, but if you fail to call SetScrollPos or SetScrollInfo in response to SB_THUMBTRACK or SB_THUMBPOSITION notifications, the thumb will snap back to its original position the moment it's released.
Now that you understand how a scroll bar works, it's time to think about how to scroll the contents of a window in response to scroll bar messages.
The simplest approach is to change the scroll bar position each time a scroll bar message arrives and to call CWnd::Invalidate to force a repaint. The window's OnPaint handler can query the scroll bar for its current position and factor that information into its output. Unfortunately, scrolling a window this way is slow—very slow, for that matter. If the user clicks the up arrow to scroll the window contents up one line, it's wasteful to redraw the entire window because most of the information you want to display is already there, albeit in the wrong location. A more efficient approach to processing SB_LINEUP messages is to copy everything currently displayed in the window down one line using a fast block copy and then to draw just the new top line. That's what CWnd::ScrollWindow is for.
ScrollWindow scrolls the contents of a window's client area—in whole or in part—up or down, left or right, by 1 or more pixels using a fast block pixel transfer. Moreover, it invalidates only the part of the window contents that is "uncovered" by the scrolling operation so that the next WM_PAINT message doesn't have to repaint the entire window. If ScrollWindow is called to scroll a window downward by 10 pixels, it performs the scroll by doing a block copy. Then it invalidates the window's top 10 rows. This activates OnPaint and causes only the top 10 rows to be redrawn. Even if OnPaint tries to redraw the contents of the entire client area, performance is improved because most of the output is clipped. A smart OnPaint handler can further boost performance by restricting its GDI calls to those that affect pixels in the window's invalid rectangle. You'll see sample programs in Chapters 10 and 13 that use this technique to optimize scrolling performance.
ScrollWindow accepts four parameters. Two are required and two are optional. The function is prototyped as follows:
void ScrollWindow (int xAmount, int yAmount, LPCRECT lpRect = NULL, LPCRECT lpClipRect = NULL) |
xAmount and yAmount are signed integers that specify the number of pixels to scroll horizontally and vertically. Negative values scroll left and up, while positive values scroll right and down. lpRect points to a CRect object or a RECT structure that specifies the part of the client area to scroll, and lpClipRect points to a CRect object or a RECT structure that specifies a clipping rectangle. Specifying NULL for lpRect and lpClipRect scrolls the contents of the entire client area. The statement
ScrollWindow (0, 10); |
scrolls everything in a window's client area downward by 10 pixels and prompts a redraw of the first 10 rows.
You can use ScrollWindow whether your application displays text, graphics, or both. In Windows all things are graphical—including text.
Let's put this newfound knowledge to work by writing an application that scrolls. Accel draws a window that resembles Microsoft Excel. (See Figure 2-13.) The spreadsheet depicted in the window is 26 columns wide and 99 rows high—much too large to be displayed all at once. However, scroll bars allow the user to view all parts of the spreadsheet. In addition to providing a hands-on look at the principles discussed in the preceding sections, Accel demonstrates another way that a program can scale its output. Rather than use a non-MM_TEXT mapping mode, it uses CDC::GetDeviceCaps to query the display device for the number of pixels per inch displayed horizontally and vertically. Then it draws each spreadsheet cell so that it's 1 inch wide and ¼ inch tall using raw pixel counts.
Figure 2-13. The Accel window.
Figure 2-14. The Accel application.
Accel.h#define LINESIZE 8 class CMyApp : public CWinApp { public: virtual BOOL InitInstance (); }; class CMainWindow : public CFrameWnd { protected: int m_nCellWidth; // Cell width in pixels int m_nCellHeight; // Cell height in pixels int m_nRibbonWidth; // Ribbon width in pixels int m_nViewWidth; // Workspace width in pixels int m_nViewHeight; // Workspace height in pixels int m_nHScrollPos; // Horizontal scroll position int m_nVScrollPos; // Vertical scroll position int m_nHPageSize; // Horizontal page size int m_nVPageSize; // Vertical page size public: CMainWindow (); protected: afx_msg void OnPaint (); afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct); afx_msg void OnSize (UINT nType, int cx, int cy); afx_msg void OnHScroll (UINT nCode, UINT nPos, CScrollBar* pScrollBar); afx_msg void OnVScroll (UINT nCode, UINT nPos, CScrollBar* pScrollBar); DECLARE_MESSAGE_MAP () }; |
Accel.cpp#include <afxwin.h> #include "Accel.h" CMyApp myApp; ///////////////////////////////////////////////////////////////////////// // CMyApp member functions BOOL CMyApp::InitInstance () { m_pMainWnd = new CMainWindow; m_pMainWnd->ShowWindow (m_nCmdShow); m_pMainWnd->UpdateWindow (); return TRUE; } ///////////////////////////////////////////////////////////////////////// // CMainWindow message map and member functions BEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd) ON_WM_CREATE () ON_WM_SIZE () ON_WM_PAINT () ON_WM_HSCROLL () ON_WM_VSCROLL () END_MESSAGE_MAP () CMainWindow::CMainWindow () { Create (NULL, _T ("Accel"), WS_OVERLAPPEDWINDOW ¦ WS_HSCROLL ¦ WS_VSCROLL); } int CMainWindow::OnCreate (LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate (lpCreateStruct) == -1) return -1; CClientDC dc (this); m_nCellWidth = dc.GetDeviceCaps (LOGPIXELSX); m_nCellHeight = dc.GetDeviceCaps (LOGPIXELSY) / 4; m_nRibbonWidth = m_nCellWidth / 2; m_nViewWidth = (26 * m_nCellWidth) + m_nRibbonWidth; m_nViewHeight = m_nCellHeight * 100; return 0; } void CMainWindow::OnSize (UINT nType, int cx, int cy) { CFrameWnd::OnSize (nType, cx, cy); // // Set the horizontal scrolling parameters. // int nHScrollMax = 0; m_nHScrollPos = m_nHPageSize = 0; if (cx < m_nViewWidth) { nHScrollMax = m_nViewWidth - 1; m_nHPageSize = cx; m_nHScrollPos = min (m_nHScrollPos, m_nViewWidth - m_nHPageSize - 1); } SCROLLINFO si; si.fMask = SIF_PAGE ¦ SIF_RANGE ¦ SIF_POS; si.nMin = 0; si.nMax = nHScrollMax; si.nPos = m_nHScrollPos; si.nPage = m_nHPageSize; SetScrollInfo (SB_HORZ, &si, TRUE); // // Set the vertical scrolling parameters. // int nVScrollMax = 0; m_nVScrollPos = m_nVPageSize = 0; if (cy < m_nViewHeight) { nVScrollMax = m_nViewHeight - 1; m_nVPageSize = cy; m_nVScrollPos = min (m_nVScrollPos, m_nViewHeight - m_nVPageSize - 1); } si.fMask = SIF_PAGE ¦ SIF_RANGE ¦ SIF_POS; si.nMin = 0; si.nMax = nVScrollMax; si.nPos = m_nVScrollPos; si.nPage = m_nVPageSize; SetScrollInfo (SB_VERT, &si, TRUE); } void CMainWindow::OnPaint () { CPaintDC dc (this); // // Set the window origin to reflect the current scroll positions. // dc.SetWindowOrg (m_nHScrollPos, m_nVScrollPos); // // Draw the grid lines. // CPen pen (PS_SOLID, 0, RGB (192, 192, 192)); CPen* pOldPen = dc.SelectObject (&pen); for (int i=0; i<99; i++) { int y = (i * m_nCellHeight) + m_nCellHeight; dc.MoveTo (0, y); dc.LineTo (m_nViewWidth, y); } for (int j=0; j<26; j++) { int x = (j * m_nCellWidth) + m_nRibbonWidth; dc.MoveTo (x, 0); dc.LineTo (x, m_nViewHeight); } dc.SelectObject (pOldPen); // // Draw the bodies of the rows and the column headers. // CBrush brush; brush.CreateStockObject (LTGRAY_BRUSH); CRect rcTop (0, 0, m_nViewWidth, m_nCellHeight); dc.FillRect (rcTop, &brush); CRect rcLeft (0, 0, m_nRibbonWidth, m_nViewHeight); dc.FillRect (rcLeft, &brush); dc.MoveTo (0, m_nCellHeight); dc.LineTo (m_nViewWidth, m_nCellHeight); dc.MoveTo (m_nRibbonWidth, 0); dc.LineTo (m_nRibbonWidth, m_nViewHeight); dc.SetBkMode (TRANSPARENT); // // Add numbers and button outlines to the row headers. // for (i=0; i<99; i++) { int y = (i * m_nCellHeight) + m_nCellHeight; dc.MoveTo (0, y); dc.LineTo (m_nRibbonWidth, y); CString string; string.Format (_T ("%d"), i + 1); CRect rect (0, y, m_nRibbonWidth, y + m_nCellHeight); dc.DrawText (string, &rect, DT_SINGLELINE ¦ DT_CENTER ¦ DT_VCENTER); rect.top++; dc.Draw3dRect (rect, RGB (255, 255, 255), RGB (128, 128, 128)); } // // Add letters and button outlines to the column headers. // for (j=0; j<26; j++) { int x = (j * m_nCellWidth) + m_nRibbonWidth; dc.MoveTo (x, 0); dc.LineTo (x, m_nCellHeight); CString string; string.Format (_T ("%c"), j + `A'); CRect rect (x, 0, x + m_nCellWidth, m_nCellHeight); dc.DrawText (string, &rect, DT_SINGLELINE ¦ DT_CENTER ¦ DT_VCENTER); rect.left++; dc.Draw3dRect (rect, RGB (255, 255, 255), RGB (128, 128, 128)); } } void CMainWindow::OnHScroll (UINT nCode, UINT nPos, CScrollBar* pScrollBar) { int nDelta; switch (nCode) { case SB_LINELEFT: nDelta = -LINESIZE; break; case SB_PAGELEFT: nDelta = -m_nHPageSize; break; case SB_THUMBTRACK: nDelta = (int) nPos - m_nHScrollPos; break; case SB_PAGERIGHT: nDelta = m_nHPageSize; break; case SB_LINERIGHT: nDelta = LINESIZE; break; default: // Ignore other scroll bar messages return; } int nScrollPos = m_nHScrollPos + nDelta; int nMaxPos = m_nViewWidth - m_nHPageSize; if (nScrollPos < 0) nDelta = -m_nHScrollPos; else if (nScrollPos > nMaxPos) nDelta = nMaxPos - m_nHScrollPos; if (nDelta != 0) { m_nHScrollPos += nDelta; SetScrollPos (SB_HORZ, m_nHScrollPos, TRUE); ScrollWindow (-nDelta, 0); } } void CMainWindow::OnVScroll (UINT nCode, UINT nPos, CScrollBar* pScrollBar) { int nDelta; switch (nCode) { case SB_LINEUP: nDelta = -LINESIZE; break; case SB_PAGEUP: nDelta = -m_nVPageSize; break; case SB_THUMBTRACK: nDelta = (int) nPos - m_nVScrollPos; break; case SB_PAGEDOWN: nDelta = m_nVPageSize; break; case SB_LINEDOWN: nDelta = LINESIZE; break; default: // Ignore other scroll bar messages return; } int nScrollPos = m_nVScrollPos + nDelta; int nMaxPos = m_nViewHeight - m_nVPageSize; if (nScrollPos < 0) nDelta = -m_nVScrollPos; else if (nScrollPos > nMaxPos) nDelta = nMaxPos - m_nVScrollPos; if (nDelta != 0) { m_nVScrollPos += nDelta; SetScrollPos (SB_VERT, m_nVScrollPos, TRUE); ScrollWindow (0, -nDelta); } } |
GetDeviceCaps is called from CMainWindow's OnCreate handler, which is called upon receipt of a WM_CREATE message. WM_CREATE is the first message a window receives. It is sent just once, and it arrives very early in the window's lifetime—before the window is even visible on the screen. An ON_WM_CREATE entry in the window's message map connects WM_CREATE messages to the member function named OnCreate. OnCreate is the ideal place to initialize member variables whose values can only be determined at run time. It is prototyped as follows:
afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct) |
lpCreateStruct is a pointer to a structure of type CREATESTRUCT, which contains useful information about a window such as its initial size and location on the screen. The value returned by OnCreate determines what Windows does next. If all goes as planned, OnCreate returns 0, signaling to Windows that the window was properly initialized. If OnCreate returns -1, Windows fails the attempt to create the window. A prototype OnCreate handler looks like this:
int CMainWindow::OnCreate (LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate (lpCreateStruct) == -1) return -1; return 0; } |
OnCreate should always call the base class's OnCreate handler to give the framework the opportunity to execute its own window-creation code. This is especially important when you write document/view applications, because it is a function called by CFrameWnd::OnCreate that creates the view that goes inside a frame window.
You'll find the code that does the scrolling in the window's OnHScroll and OnVScroll handlers. switch-case logic converts the notification code passed in nCode into a signed nDelta value that represents the number of pixels the window should be scrolled. Once nDelta is computed, the scroll position is adjusted by nDelta pixels and the window is scrolled with the statements
m_nVScrollPos += nDelta; SetScrollPos (SB_VERT, m_nVScrollPos, TRUE); ScrollWindow (0, -nDelta); |
for the vertical scroll bar and
m_nHScrollPos += nDelta; SetScrollPos (SB_HORZ, m_nHScrollPos, TRUE); ScrollWindow (-nDelta, 0); |
for the horizontal scroll bar.
How are the scroll positions stored in m_nHScrollPos and m_nVScrollPos factored into the program's output? When OnPaint is called to paint the part of the workspace that was exposed by the scrolling operation, it repositions the window origin with the statement
dc.SetWindowOrg (m_nHScrollPos, m_nVScrollPos); |
Recall that CDC::SetWindowOrg tells Windows to map the logical point (x,y) to the device point (0,0), which, for a client-area device context, corresponds to the upper left corner of the window's client area. The statement above moves the origin of the coordinate system left m_nHScrollPos pixels and upward m_nVScrollPos pixels. If OnPaint tries to paint the pixel at (0,0), the coordinate pair is transparently transformed by the GDI into (_m_nHScrollPos,_m_nVScrollPos). If the scroll position is (0,100), the first 100 rows of pixels are clipped from the program's output and the real output—the output the user can see—begins with the 101st row. Repositioning the origin in this manner is a simple and effective way to move a scrollable window over a virtual display surface.
If you could enlarge the window enough to see the entire spreadsheet, you would see the scroll bars disappear. That's because CMainWindow::OnSize sets the scroll bar range to 0 if the window size equals or exceeds the size of the virtual workspace. The OnSize handler also updates the scrolling parameters whenever the window size changes so that the thumb size accurately reflects the relative proportions of the window and the virtual workspace.
And with that, all the pieces are in place. The user clicks a scroll bar or drags a scroll bar thumb; OnHScroll or OnVScroll receives the message and responds by updating the scroll position and scrolling the window; and OnPaint redraws the window, using SetWindowOrg to move the drawing origin an amount that equals the current scroll position. The program's entire workspace is now accessible, despite the physical limitations that the window size imposes on the output. And all for less than 100 additional lines of code. How could it be any easier?
Funny you should ask. Because that's exactly what MFC's CScrollView class is for: to make scrolling easier. CScrollView is an MFC class that encapsulates the behavior of a scrolling window. You tell CScrollView how large a landscape you wish to view, and it handles everything else. Among other things, CScrollView processes WM_VSCROLL and WM_HSCROLL messages for you, scrolls the window in response to scroll bar events, and updates the thumb size when the window size changes.
While it's perfectly possible to wire a CScrollView into an application like Accel, CScrollView was designed primarily for document/view applications. Chapter 10 examines CScrollView more closely and also introduces some of the other view classes that MFC provides.