Nancy Winnick Cluts
Microsoft Developer Network Technology Group
Created: October 28, 1993
Click to open or copy the files in the Sizebox sample application.
Click to open or copy the files in the Scroll32 sample application.
This article describes the functionality and design of scroll bar controls in the Microsoft® Win32® Application Programming Interface (API). It covers the following:
A scroll bar control is a rectangular window that contains a scroll box (usually called the thumb of the scroll bar) and two scroll arrows. The scroll bar control sends a notification message to its parent window whenever the user clicks the control with the mouse. The parent window is responsible for updating the parent window's contents and the position of the thumb (if necessary).
A scroll bar is part of a window, but a scroll bar control is actually a window itself. Unlike a scroll bar, a scroll bar control can be positioned anywhere in a window and used whenever the window needs to scroll input.
If you are comfortable with the use of scroll bars in Win16, you have already mastered most of the information in this technical article. There are, however, some differences between Win16 and Win32® scroll bars. The major difference is that Win32 scroll bars allow full 32-bit positioning while Win16 scroll bars allow 16-bit positioning. Currently, Win32 scroll bars are limited to 16 bits of positioning information during real-time scrolling (thumb tracking). However, I have come up with a workaround that will allow you to get the full 32 bits of positioning. (See the "Limitations" section of this technical article.)
A scroll bar consists of a shaded shaft with a scroll arrow at each end and a scroll box (usually called a thumb) between the arrows (see Figure 1).
Figure 1. The anatomy of a scroll bar
A scroll bar represents the full range (overall length or width) of a data object such as a document or picture in the window's client area. The thumb represents the portion of the data object that is visible in the client area. The user can scroll the contents of the window by clicking one of the scroll arrows, by clicking the area in the shaded shaft, or by dragging the thumb. For example, when the user clicks the up arrow, the thumb moves up and the application scrolls the contents of the window by one unit (typically a single line or column). When the user clicks inside the shaft, the application scrolls the contents by one window. The amount of scrolling that occurs when the user drags the thumb is known as the scrolling range and is defined by the application developer.
This section describes the styles that you can use when creating your scroll bar. These styles are also documented in the Platform SDK.
Style | Functionality |
SBS_BOTTOMALIGN | Places a horizontal scroll bar at the bottom of the rectangle specified by the CreateWindow rectangle parameters. The scroll bar has the default height for system scroll bars. This style is used with the SBS_HORZ style. |
SBS_HORZ | Creates a horizontal scroll bar. The scroll bar is sized according to the rectangle values passed to CreateWindow if neither the SBS_BOTTOMALIGN nor SBS_TOPALIGN style is specified. |
SBS_LEFTALIGN | Places a vertical scroll bar at the left edge of the rectangle specified by the CreateWindow rectangle parameters. The scroll bar has the default width for system scroll bars. This style is used with the SBS_VERT style. |
SBS_RIGHTALIGN | Places a vertical scroll bar at the right edge of the rectangle specified by the CreateWindow rectangle parameters. The scroll bar has the default width for system scroll bars. This style is used with the SBS_VERT style. |
SBS_SIZEBOX | Creates a size box. (A size box is a small rectangle that is used to set the size of its parent window. See the "Using the SBS_SIZEBOX Window Style" section later in this article for more information.) If you don't specify the SBS_SIZEBOXBOTTOMRIGHTALIGN or the SBS_SIZEBOXTOPLEFTALIGN style, the size box has the height, width, and position specified by the CreateWindow rectangle parameters. |
SBS_SIZEBOXBOTTOMRIGHTALIGN | Places the size box with the lower-right corner of the rectangle specified by the CreateWindow rectangle parameters. The size box has the default size for system size boxes. This style is used only with the SBS_SIZEBOX style. |
SBS_SIZEBOXTOPLEFTALIGN | Places the size box with the upper-left corner of the rectangle specified by the CreateWindow rectangle parameters. The size box has the default size for system size boxes. This style is used only with the SBS_SIZEBOX style. |
SBS_TOPALIGN | Puts a horizontal scroll bar at the top edge of the rectangle defined by the CreateWindow rectangle parameters. The scroll bar has the default height for system scroll bars. This style is used with the SBS_HORZ style. |
SBS_VERT | Creates a vertical scroll bar. If don't you specify the SBS_RIGHTALIGN or SBS_LEFTALIGN style, the scroll bar has the position specified by the CreateWindow rectangle parameters. |
If you want to allow users to scroll the contents of your window, you can add horizontal and vertical scroll bars to the window simply by adding the WS_HSCROLL and WS_VSCROLL styles to your window in your call to CreateWindow. This creates the look of a "traditional" window.
hWnd = CreateWindow(
"TypicalWindow",
"Test",
WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT, 300, 150,
NULL,
NULL,
hInstance,
NULL);
When you add scroll bar(s) to your window, Windows notifies you whenever the user clicks the scroll bar. The application responds to these notification messages by redrawing the contents of the window that need to be redrawn. One common method of scrolling a window's contents is to call the ScrollWindow or ScrollWindowEx function with a parameter specifying the rectangular area that needs to be scrolled and drawing only the area that contains new information.
A typical method used to scroll text in a window involves the following steps:
/*
* Increment the scrolling position, adjust the position
* of the thumb, 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);
}
It is possible to create a scroll bar control that acts exactly like the scroll bars created by calling CreateWindow with WS_HORZ or WS_VERT. Since the system already generates these scroll bars for you, there is no need to do this work. If, however, you wish to alter the standard look or function of the scroll bar, you can either subclass the scroll bars on your window or create a stand-alone control. To illustrate how built-in scroll bars work, I will now explain how you can create a scroll bar that looks and acts exactly like a system-generated scroll bar.
You can create a scroll bar control that is a stand-alone control by specifying the Scrollbar class in your call to CreateWindow. When the scroll bar has been created, set the range of the scroll bar by calling SetScrollRange. Set the size of the scroll bar based on the parent window's client area and the system metrics of the thumb size of the scroll bar. (This calculation ensures that your scroll bar controls look and work exactly like the scroll bars you generate by specifying WS_HORZ and WS_VERT in the CreateWindow call.) Specify SBS_BOTTOMALIGN for the horizontal scroll bar and SBS_RIGHTALIGN for the vertical scroll bar.
// Get the client rectangle of the parent window.
GetClientRect( hWndParent, &RclParent);
iHThumb = GetSystemMetrics(SM_CXHTHUMB);
iVThumb = GetSystemMetrics(SM_CYVTHUMB);
// Create a horizontal scroll bar to put in the window.
hWndHorzScroll = CreateWindow(
"SCROLLBAR",
(LPSTR)NULL,
WS_CHILD | WS_VISIBLE | SBS_HORZ | SBS_BOTTOMALIGN,
RclParent.left,RclParent.top,RclParent.right-iHThumb,RclParent.bottom,
hWndParent,
(HMENU)IDC_HSCROLL,
(HANDLE)hInstance,
NULL);
SetScrollRange( hWndHorzScroll, SB_CTL, 0, MAX_RANGE, FALSE);
// Create a vertical scroll bar to put in the window.
hWndVertScroll = CreateWindow(
"Scrollbar",
(LPSTR)NULL,
WS_CHILD | WS_VISIBLE | SBS_VERT | SBS_RIGHTALIGN,
RclParent.left,RclParent.top,RclParent.right,RclParent.bottom-iVThumb,
hWndParent,
(HMENU)IDC_VERT,
hInstance,
NULL);
SetScrollRange( hWndVertScroll, SB_CTL, 0, MAX_RANGE, FALSE);
When you create these scroll bars, you will notice a white rectangle in the bottom corner of the screen where traditionally you would see a light gray rectangle. This area is drawn in response to the WM_PAINT message in your parent window procedure. The code below illustrates this method.
RECT Rcl;
HDC hDC;
PAINTSTRUCT ps;
Rcl.left = RclParent.right - iHThumb;
Rcl.right = RclParent.right;
Rcl.top = RclParent.bottom - iVThumb;
Rcl.bottom = RclParent.bottom;
hDC = BeginPaint(hWnd, &ps);
FillRect(hDC, &Rcl, GetStockObject(LTGRAY_BRUSH));
EndPaint(hWnd, &ps);
If you want to implement scroll bars that look and act exactly like system-generated scroll bars, you must also take sizing into account. If your parent window is resizable, you will need to resize and redraw your scroll bars and the light gray rectangular fill whenever Windows sends a WM_SIZE message to the parent window procedure.
If you would like to add functionality to that small "dead" area between your horizontal and vertical scroll bars on your window, you can create a size box. The scroll bar window class provides a style, SBS_SIZEBOX, that can be used as an alternative to the sizing border on your window. A size box is a small rectangle that sits in the corner of your window. The user can drag the size box to resize the window. You can create a size box by using the standard CreateWindow call and specifying SBS_SIZEBOX in the style bits. SBS_SIZEBOX is available in both Win16 and Win32.
hWndSizeBox = CreateWindow(
"Scrollbar",
(LPSTR)NULL,
WS_CHILD | WS_VISIBLE | SBS_SIZEBOX | SBS_SIZEBOXTOPLEFTALIGN,
0,0,0,0,
hWnd,
(HMENU)10,
hInst,
NULL);
The function call above creates a size box as a child of the current window and places it in the upper-left corner of the window. If you choose to place the size box in the lower-right corner of your window screen, specify SBS_SIZEBOXBOTTOMRIGHTALIGN instead of SBS_SIZEBOXTOPLEFTALIGN.
The Sizebox sample that accompanies this technical article demonstrates this control type. It displays a window that prompts you to click and drag the small gray box in the upper-left corner of the window (Figure 2).
Figure 2. The Sizebox sample screen
The SBS_SIZEBOXBOTTOMRIGHTALIGN style currently has a bug: The positioning is off by 3 pixels in both directions. The workaround for this problem is to add 3 pixels to the xWidth and yHeight parameters specified in the CreateWindow call.
You should also note that a size box does not automatically reset its position when its parent window is resized. This is difficult to see in the case of the SBS_SIZEBOXTOPLEFTALIGN style because the size box is positioned at (0, 0) and seems to move along with its parent when the window is resized. However, if you align your size box control at the lower-right corner of the screen (SBS_SIZEBOXBOTTOMRIGHTALIGN), the xWidth and yHeight of the parent window will change. For this reason, you need to reset the position of the size box control using SetWindowPos in response to the WM_SIZE message.
Unlike other window classes, scroll bars are manipulated through functions. These functions are described in the sections below.
The first parameter to each function is the handle to the scroll bar control (or the handle to the owner window, if the scroll bar was created with the WS_HORZ or WS_VERT window style). The second parameter specifies the type of scroll bar to manipulate:
All scroll bar functions return an error in the following cases:
The following function sets the scroll position of the horizontal scroll bar control to 100 and forces a redraw of the scroll bar control:
Err = SetScrollPos(hWndHorzScroll, SB_CTL, 100, TRUE);
The EnableScrollBar function enables or disables one or both of the scroll arrows. If you specify both vertical and horizontal scroll bars, the system changes the horizontal scroll bar first, changes the vertical scroll bar next, and then forces a redraw of the window. This function supports the SB_CTL, SB_HORZ, SB_VERT, and SB_BOTH flags.
The GetScrollPos function returns the current position of the thumb in the specified scroll bar. If you specify a scroll bar control, GetScrollPos sends an SBM_GETPOS message to the control. Otherwise, it returns the thumb position that it keeps internally. This function supports the SB_CTL, SB_HORZ, and SB_VERT flags.
The GetScrollRange function returns the current range (minimum and maximum scroll position) of the scroll bar previously set with SetScrollRange. If you specify a scroll bar control, GetScrollRange sends an SBM_GETRANGE message to the control. Otherwise, it returns the minimum and maximum values that it keeps internally for the scroll bar. If the window handle you pass to the function is not a handle to a scroll bar, or if the window specified does not have scroll bars, GetScrollRange returns an error and sets the minimum and maximum values that you pass to the function to 0. This function supports the SB_CTL, SB_HORZ, and SB_VERT flags.
The SetScrollPos function sets the position of the thumb in the scroll bar to the specified value. If you specify a scroll bar control, SetScrollPos sends an SBM_SETPOS to the control. Otherwise, the system resets the thumb to the specified position and redraws the scroll bar and thumb. If you specify a position outside the current scroll range, SetScrollPos returns an error. This function supports the SB_CTL, SB_HORZ, and SB_VERT flags.
The SetScrollRange function sets the range (minimum and maximum scroll position) of the scroll bar. If you specify a scroll bar control, SetScrollRange sends an SBM_SETRANGE to the control. Otherwise, the system sets the range to the specified minimum and maximum values. If you specify invalid values or pass an invalid window handle to the function, SetScrollRange returns an error. This function supports the SB_CTL, SB_HORZ, and SB_VERT flags.
The ShowScrollBar function shows or hides the specified scroll bar. You can use this function to hide or show a horizontal scroll bar, a vertical scroll bar, both horizontal and vertical scroll bars, or a scroll bar control. If you hide a scroll bar and then show it, you do not have to reset the thumb position and range because the window handle is still valid. This function supports the SB_CTL, SB_HORZ, SB_VERT, and SB_BOTH flags.
Windows sends an SBM_ENABLE_ARROWS message to a scroll bar control whenever an application wants to enable or disable scroll arrows (when the EnableScrollBar function is called).
wParam = (WPARAM)fuArrows; /* enable/disable flags */
lParam = 0; /* not used - must be zero */
The flags you can specify for this message are the same as those you use for EnableScrollBar: ESB_ENABLE_BOTH, ESB_DISABLE_LTUP, ESB_DISABLE_RTDN, and ESB_DISABLE_BOTH.
Windows sends an SBM_GETPOS message to a scroll bar control whenever GetScrollPos is called. This message returns the current thumb position.
wParam = 0; /* not used - must be zero */
lParam = 0; /* not used - must be 0 */
Windows sends an SBM_GETRANGE message to a scroll bar control whenever GetScrollRange is called. This message retrieves the minimum and maximum scroll values associated with the scroll bar.
wParam = (WPARAM)(LPINT)lpnMinPos; /* minimum scrolling position */
lParam = (LPARAM)(LPINT)lpnMaxPos; /* maximum scrolling position */
Windows sends an SBM_SETPOS message to a scroll bar control whenever SetScrollPos is called. If the scroll bar position is changed, the SBM_SETPOS returns the previous position of the thumb; otherwise, it returns 0. If the new position specified with SetScrollPos is outside the current scrolling range, this message will not reset the thumb.
wParam = (WPARAM)nPos; /* new position of the thumb */
lParam = (LPARAM)(BOOL)fReDraw; /* redraw the scroll bar if TRUE */
Windows sends an SBM_SETRANGE message to a scroll bar control whenever SetScrollRange is called. The default minimum and maximum values are 0. The difference between the minimum and maximum value must not exceed 2,147,483,647(0x7FFFFFFF). If the minimum value equals the maximum value, the scroll bar control is hidden and disabled.
wParam = (WPARAM)nMinPos; /* minimum scrolling position */
lParam = (LPARAM)nMaxPos; /* maximum scrolling position */
Windows sends an SBM_SETRANGEREDRAW message to a scroll bar control when the scroll range is set and the redraw flag is TRUE. The default minimum and maximum values are 0. The difference between the minimum and maximum value must not exceed 2,147,483,647(0x7FFFFFFF). If the minimum value equals the maximum value, the scroll bar control is hidden and disabled.
wParam = (WPARAM)nMinPos; /* minimum scrolling position */
lParam = (LPARAM)nMaxPos; /* maximum scrolling position */
The ability to manipulate the scroll bar with the keyboard instead of the mouse is not a built-in function of the scroll bar class. However, implementing scrolling behavior based on keyboard input is a fairly trivial task. One interesting problem associated with this task is determining what to do in the case of the PAGE_UP, PAGE_DOWN, HOME, and END keys. In the example below, I have associated PAGE_UP and PAGE_DOWN with the vertical scroll bar, and HOME and END with the horizontal scroll bar.
case WM_KEYDOWN:
switch (wParam)
{
case VK_UP:
wScrollNotify = SB_LINEUP;
uMessage = WM_VSCROLL;
break;
case VK_PRIOR: //PAGEUP key
wScrollNotify = SB_PAGEUP;
uMessage = WM_VSCROLL;
break;
case VK_NEXT: // PAGEDOWN key
wScrollNotify = SB_PAGEDOWN;
uMessage = WM_VSCROLL;
break;
case VK_DOWN:
wScrollNotify = SB_LINEDOWN;
uMessage = WM_VSCROLL;
break;
case VK_HOME:
wScrollNotify = SB_BOTTOM;
uMessage = WM_HSCROLL;
break;
case VK_END:
wScrollNotify = SB_TOP;
uMessage = WM_HSCROLL;
break;
case VK_RIGHT:
wScrollNotify = SB_LINEDOWN;
uMessage = WM_HSCROLL;
break;
case VK_LEFT:
wScrollNotify = SB_LINEUP;
uMessage = WM_HSCROLL;
break;
default:
wScrollNotify = 0xFFFF;
break;
}
if (wScrollNotify != -1)
SendMessage(hWnd, uMessage, MAKELONG(wScrollNotify, 0), 0L);
break;
In Win32, you can use 32-bit values to set the range and position of the thumb in the scroll bar. However, if you are doing real-time thumb tracking using the SB_THUMBTRACK message, Win32 will give you only 16 bits of position data. This is because the position data is returned in the HIWORD of the lParam. To get around this limitation, you need to approximate the position yourself. One way to do this is to get the current cursor position, map it to the scroll window, and calculate the position based on the ratio between the mouse position in the scroll bar window and the point within the range of the scroll bar (Figure 3).
Figure 3. Calculating the position of the thumb
Here's one method for calculating the thumb position:
/* User dragged the thumb. */
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
/* If range is greater than 16 bits, then convert. */
if (uHorzMax > 65535)
{
double d1;
POINT Pt;
// Get the current cursor position.
GetCursorPos(&Pt);
// Get the client rectangle of the horizontal scroll bar.
GetClientRect(hWndHorzScroll, &RclHorz);
// Map the point of the cursor to the scroll window.
ScreenToClient(hWndHorzScroll, &Pt);
// Multiply by the maximum horizontal position
// and store in a double to keep from overflowing.
d1 = (double)Pt.x * (double)uHorzMax;
// Divide by the right side of the scroll window.
xNewPos = (UINT)(d1 / (double)RclHorz.right);
}
/* Else, just take the 16-bit value passed in HIWORD(wParam). */
else
xNewPos = HIWORD(wParam);
break;
The Scroll32 sample that is included with this technical article demonstrates this technique. Run this sample and drag the mouse along either the vertical or the horizontal scroll bar. You will see the scroll position updated in the text at the bottom of the screen. You can also set a specific scroll range and scroll position with this sample.
One potential problem with the method above manifests itself in the following scenario:
At this point, the actual scroll position will be off by the distance between the left side and the right side of the thumb. You can correct this problem by calculating the scroll position based on the center of the thumb. That is, when the user clicks the thumb, get the distance between the center of the thumb and the actual cursor point and subtract that distance from the position before calculating your cursor-to-scroll-position ratio.
Scroll bars are very similar in Win16 and Win32. The major difference is that Win32 scroll bars allow full 32-bit positioning while Win16 scroll bars allow 16-bit positioning. If you are already comfortable working with scroll bar controls in Win16, you will be pleasantly surprised to find that there is very little new work to do in Win32. In fact, if you do not need 32-bit precision in your positioning, you will not need to change your code at all. If you do need 32-bit precision, you will need to work around the real-time scrolling limitation that I explained in the previous section.