Enough explanation. It's time to put this stuff into practice. But let's start simply. We'll begin with vertical scrolling because that's what we desperately need. The horizontal scrolling can wait. SYSMETS2 is shown in Figure 2-10.
The new CreateWindow call adds a vertical scroll bar to the window; the scroll bar has this window style:
WS_OVERLAPPEDWINDOW | WS_VSCROLL
SYSMETS2.MAK
#------------------------
# SYSMETS2.MAK make file
#------------------------
sysmets2.exe : sysmets2.obj sysmets2.def
link sysmets2, /align:16, NUL, /nod slibcew libw, sysmets2
rc sysmets2.exe
sysmets2.obj : sysmets2.c sysmets.h
cl -c -Gsw -Ow -W2 -Zp sysmets2.c
SYSMETS2.C
/*----------------------------------------------------
SYSMETS2.C -- System Metrics Display Program No. 2
(c) Charles Petzold, 1990
----------------------------------------------------*/
#include <windows.h>
#include "sysmets.h"
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
static char szAppName[] = "SysMets2" ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
if (!hPrevInstance)
{
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
RegisterClass (&wndclass) ;
}
hwnd = CreateWindow (szAppName, "Get System Metrics No. 2",
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, nCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
static short cxChar, cxCaps, cyChar, cxClient, cyClient, nVscrollPos ;
char szBuffer[10] ;
HDC hdc ;
short i, y ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE :
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES, FALSE) ;
SetScrollPos (hwnd, SB_VERT, nVscrollPos, TRUE) ;
return 0 ;
case WM_SIZE :
cyClient = HIWORD (lParam) ;
cxClient = LOWORD (lParam) ;
return 0 ;
case WM_VSCROLL :
switch (wParam)
{
case SB_LINEUP :
nVscrollPos -= 1 ;
break ;
case SB_LINEDOWN :
nVscrollPos += 1 ;
break ;
case SB_PAGEUP :
nVscrollPos -= cyClient / cyChar ;
break ;
case SB_PAGEDOWN :
nVscrollPos += cyClient / cyChar ;
break ;
case SB_THUMBPOSITION :
nVscrollPos = LOWORD (lParam) ;
break ;
default :
break ;
}
nVscrollPos = max (0, min (nVscrollPos, NUMLINES)) ;
if (nVscrollPos != GetScrollPos (hwnd, SB_VERT))
{
SetScrollPos (hwnd, SB_VERT, nVscrollPos, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
}
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i < NUMLINES ; i++)
{
y = cyChar * (1 - nVscrollPos + i) ;
TextOut (hdc, cxChar, y,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, cxChar + 18 * cxCaps, y,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, cxChar + 18 * cxCaps + 40 * cxChar, y,
szBuffer,
wsprintf (szBuffer, "%5d",
GetSystemMetrics (sysmetrics[i].nIndex))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
SYSMET2.DEF
;-------------------------------------
; SYSMETS2.DEF module definition file
;-------------------------------------
NAME SYSMETS2
DESCRIPTION 'System Metrics Display No. 2 (c) Charles Petzold, 1990'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS WndProc
The WndProc window procedure has two additional lines to set the range and position of the vertical scroll bar during processing of the WM_CREATE message:
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES, FALSE) ;
SetScrollPos (hwnd, SB_VERT, nVscrollPos, TRUE) ;
The sysmetrics structure has NUMLINES lines of text, so the scroll bar range is set from 0 through NUMLINES. Each position of the scroll bar corresponds to a line of text displayed at the top of the client area. If the scroll bar thumb is at position 0, a blank line is left at the top of the screen for a margin. As you increase the position of the scroll bar by scrolling down, the text should move up. When the scroll bar position is at the bottom, the last line of the structure is at the top.
To help with processing of the WM_VSCROLL messages, a static variable called nVscrollPos is defined within the WndProc window procedure. This variable is the current position of the scroll bar thumb. For SB_LINEUP and SB_LINEDOWN, all we need to do is adjust the scroll position by 1. For SB_PAGEUP and SB_PAGEDOWN, we want to move the text by the contents of one screen, or cyClient divided by cyChar. For SB_THUMBPOSITION, the new thumb position is the low word of lParam. SB_ENDSCROLL and SB_THUMBTRACK messages are ignored.
The nVscrollPos is then adjusted using the min and max macros (defined in WINDOWS.H) to ensure that it is between the minimum and maximum range values. If the scroll position has changed, then it is updated using SetScrollPos, and the entire window is invalidated by an InvalidateRect call.
The InvalidateRect call generates a WM_PAINT message. When the original SYSMETS1 processed WM_PAINT messages, the y-coordinate of each line was calculated as:
cyChar * (1 + i)
In SYSMETS2, the formula is:
cyChar * (1 - nVscrollPos + i)
The loop still displays NUMLINES lines of text, but for values of nVscrollPos of 2 and above, the loop begins displaying lines above the client area. Windows merely ignores these lines.
I told you we'd start simply. This is rather wasteful and inefficient code. We'll fix it shortly, but first consider how we update the client area after a WM_VSCROLL message.