45.2.2 Scrolling Text

This section describes changes you could make to an application's main window procedure to enable a user to scroll text. The example code in Section 0.2.2.5, “Example: Scrolling Text” creates and displays an array of text strings. The code also processes the WM_VSCROLL and WM_HSCROLL messages generated by the scroll bars, so that the user can scroll the text vertically and horizontally.

45.2.2.1 Processing the WM_CREATE Message

To scroll text in a window, you typically set the scrolling units while processing the WM_CREATE message. It is convenient to base the scrolling units on the dimensions of the font associated with the window's display context. Use the GetTextMetrics function to retrieve the font dimensions for a specific display context.

In the example in Section 0.2.2.5, “Example: Scrolling Text,” one vertical scrolling unit is equivalent to the height of a character cell plus external leading. One horizontal scrolling unit is equivalent to the average width of a character cell, so the horizontal scrolling positions will not correspond to actual characters unless the screen font is fixed-width.

45.2.2.2 Processing the WM_SIZE Message

While processing the WM_SIZE message, it is convenient to adjust the scrolling range and scrolling position to reflect the dimensions of the client area and the number of lines of text to be displayed.

The SetScrollRange function sets the minimum and maximum position values for a scroll bar. The SetScrollPos function adjusts the scroll box to reflect the current scrolling position.

45.2.2.3 Processing the WM_VSCROLL and WM_HSCROLL Messages

The scroll bar sends WM_VSCROLL and WM_HSCROLL messages to the window procedure whenever the user clicks the scroll bar or drags the scroll box. The low-order word of WM_VSCROLL and WM_HSCROLL contains a scroll bar notification code that indicates the direction and magnitude of the scrolling action.

To process the WM_VSCROLL and WM_HSCROLL messages, you must examine the scroll bar notification code and calculate the scrolling increment. Then, you must apply the increment to the current scrolling position, scroll the window to the new position by using the ScrollWindow function, and adjust the position of the scroll box by using the SetScrollPos function.

After scrolling a window, part of the client area will be invalid; therefore, the application must draw the appropriate lines of text in the invalid region. The UpdateWindow function generates a WM_PAINT message to ensure that the invalid region is updated.

45.2.2.4 Processing the WM_PAINT Message

While processing the WM_PAINT message, it is convenient to draw the lines of text that should appear in the invalid portion of the window. The example code uses the current scrolling position and the dimensions of the invalid region to determine the range of lines within the invalid region and display these lines.

45.2.2.5 Example: Scrolling Text

The following example shows how to scroll text in response to input from a window's horizontal and vertial scroll bars:

HDC hdc;

PAINTSTRUCT ps;

TEXTMETRIC tm;

/* These variables are required to display text. */

static int yClient; /* height of client area */

static int xClient; /* width of client area */

static int xClientMax; /* maximum width of client area */

static int yChar; /* vertical scrolling unit */

static int xChar; /* horizontal scrolling unit */

static int xUpper; /* average width of uppercase letters */

static int yPos; /* current vertical scrolling position */

static int xPos; /* current horizontal scrolling position */

static int yMax; /* maximum vert. scrolling position */

static int xMax; /* maximum horiz. scrolling position */

int yInc; /* vertical scrolling increment */

int xInc; /* horizontal scrolling increment */

int i; /* loop counter */

int x, y; /* horiz. and vert. printing coordinates */

int FirstLine; /* first line in invalidated area */

int LastLine; /* last line in invalidated area */

/* Create an array of lines to display. */

#define LINES 27

static char *abc[] = { "anteater", "bear", "cougar", "dingo",

"elephant", "frog", "gazelle", "hyena", "iguana", "jackal",

"kangaroo", "llama", "moose", "newt", "octopus", "penguin",

"quail", "rat", "squirrel", "tortoise", "uncle", "vampire",

"walrus", "xylophone", "yak", "zebra",

"This line contains many words, but no character. Go figure." };

switch (uMsg) {

case WM_CREATE:

/* Get the handle of the client area's device context. */

hdc = GetDC(hwnd);

/* Extract font dimensions from text metrics. */

GetTextMetrics(hdc, &tm);

xChar = tm.tmAveCharWidth;

xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * xChar / 2;

yChar = tm.tmHeight + tm.tmExternalLeading ;

/* Free the device context. */

ReleaseDC(hwnd, hdc);

/*

* Set (arbitrary) maximum width for client area.

* (xClientMax is the sum of the widths of 48 average

* lowercase letters and 12 uppercase letters.)

*/

xClientMax = 48 * xChar + 12 * xUpper;

return 0;

case WM_SIZE:

/* Retrieve the dimensions of the client area. */

yClient = HIWORD(lParam);

xClient = LOWORD(lParam);

/*

* Determine the maximum vertical scrolling position.

* The '2' is added for extra space below the lines

* of text.

*/

yMax = max(0, LINES + 2 - yClient / yChar);

/*

* Make sure the current scrolling position does

* not exceed the maximum.

*/

yPos = min(yPos, yMax);

/*

* Adjust the vertical scrolling range and scroll-box

* position to reflect the new yMax and yPos values.

*/

SetScrollRange(hwnd, SB_VERT, 0, yMax, TRUE);

SetScrollPos(hwnd, SB_VERT, yPos, TRUE);

/*

* Determine the maximum horizontal scrolling position.

* The '2' is added for extra space to the right of the

* lines of text.

*/

xMax = max(0, 2 + (xClientMax - xClient) / xChar);

/*

* Make sure the current scrolling position does

* not exceed the maximum.

*/

xPos = min(xPos, xMax);

/*

* Adjust the horizontal scrolling range and scroll-box

* position to reflect the new xMax and xPos values.

*/

SetScrollRange(hwnd, SB_HORZ, 0, xMax, TRUE);

SetScrollPos(hwnd, SB_HORZ, xPos, TRUE);

return 0;

case WM_PAINT:

/* Prepare the window for painting. */

hdc = BeginPaint(hwnd, &ps);

/*

* Use the current vertical scrolling position and

* coordinates of the invalid rectangle to determine

* the range of new lines that should be drawn in the

* client area.

*/

FirstLine = max(0, yPos + ps.rcPaint.top / yChar - 1);

LastLine = min(LINES, yPos + ps.rcPaint.bottom / yChar);

/* Display these lines. */

for (i = FirstLine; i < LastLine; i++) {

x = xChar * (1 - xPos);

y = yChar * (1 - yPos + i);

TextOut(hdc, x, y, abc[i], lstrlen(abc[i]));

}

/* Indicate that painting is finished. */

EndPaint(hwnd, &ps);

break;

case WM_VSCROLL:

switch(LOWORD(wParam)) {

/* User clicked in the shaft above the scroll box. */

case SB_PAGEUP:

yInc = min(-1, -yClient / yChar);

break;

/* User clicked in the shaft below the scroll box. */

case SB_PAGEDOWN:

yInc = max(1, yClient / yChar);

break;

/* User clicked on the top arrow. */

case SB_LINEUP:

yInc = -1;

break;

/* User clicked on the bottom arrow. */

case SB_LINEDOWN:

yInc = 1;

break;

/* User dragged the scroll box. */

case SB_THUMBTRACK:

yInc = HIWORD(wParam) - yPos;

break;

default:

yInc = 0;

}

/*

* If applying the vertical scrolling increment won't

* take the scrolling position out of the scrolling range,

* increment the scrolling position, adjust the position

* of the scroll box, and update the window. UpdateWindow

* sends the WM_PAINT message.

*/

if (yInc = max(-yPos, min(yInc, yMax - yPos))) {

yPos += yInc;

ScrollWindow(hwnd, 0, -yChar * yInc,

(CONST RECT *) NULL, (CONST RECT *) NULL);

SetScrollPos(hwnd, SB_VERT, yPos, TRUE);

UpdateWindow(hwnd);

}

return 0;

case WM_HSCROLL:

switch(LOWORD(wParam)) {

/* User clicked in shaft left of the scroll box. */

case SB_PAGEUP:

xInc = -8;

break;

/* User clicked in shaft right of the scroll box. */

case SB_PAGEDOWN:

xInc = 8;

break;

/* User clicked on the left arrow. */

case SB_LINEUP:

xInc = -1;

break;

/* User clicked on the right arrow. */

case SB_LINEDOWN:

xInc = 1;

break;

/* User dragged the scroll box. */

case SB_THUMBTRACK:

xInc = HIWORD(wParam) - xPos;

break;

default:

xInc = 0;

}

/*

* If applying the horizontal scrolling increment won't

* take the scrolling position out of the scrolling range,

* increment the scrolling position, adjust the position

* of the scroll box, and update the window.

*/

if (xInc = max(-xPos, min(xInc, xMax - xPos))) {

xPos += xInc;

ScrollWindow(hwnd, -xChar * xInc, 0,

(CONST RECT *) NULL, (CONST RECT *) NULL);

SetScrollPos(hwnd, SB_HORZ, xPos, TRUE);

UpdateWindow(hwnd);

}

return 0;

.

.

.