Scroll Bar Controls

 

Nancy Winnick Cluts
Microsoft Corporation

October 28, 1993 Updated February 2004

Summary: This article describes the functionality and design of scroll bar controls in the Windows Application Programming Interface (API). It covers common programming techniques associated with scroll bars; the functions used to manipulate scroll bars; the messages sent to scroll bars; and current limitations and known problems with scroll bars. Whenever possible, I have provided a workaround for a given limitation or bug. (15 printed pages)

Contents

Introduction Anatomy of a Scroll Bar Scroll Bar Styles Scroll Bar Techniques Scroll Bar Functions Notification Messages Interfacing with the Keyboard Limitations Conclusion

Introduction

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.

Anatomy of a Scroll Bar

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.

Scroll Bar Styles

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.

Scroll Bar Techniques

Adding a Scroll Bar to a Window

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);

Scrolling Text in a Window

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:

  1. Get the current text metrics for the window, and calculate how many lines of code will fit on the screen.

  2. Set the vertical scroll range and the scroll position based on the number of additional lines you need to display. Use the text metric for average character height in this calculation.

  3. Set the horizontal scroll range and scroll position based on the amount of text available for horizontal viewing. Use the text metric for average character width in this calculation.

  4. Once a scroll notification is given, scroll the window using the ScrollWindow function. Note that ScrollWindow invalidates part of the window for repainting. An example of this is shown in the code below. For a full code listing of this scrolling technique, see the Platform SDK documentation. In the code listing below, xInc is the increment amount, xPos is the current scroll position, and xMax is the maximum horizontal scrolling position.

    /*
    * 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);
    }
    
  5. Draw the text out to the screen using the current vertical scrolling position and the coordinates of the invalid rectangle to determine the range of new lines that should be drawn.

  6. In the case of a horizontal or vertical scroll notification, reset the scroll position based on an appropriate increment. For example, if the user clicks the right scroll arrow, increment your scroll position by one unit. If the user drags the thumb to the end of the scroll bar, calculate the end point and set your position to that point.

Creating a Typical Scroll Bar Control

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.

Using the SBS_SIZEBOX Window Style

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.

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.

Scroll Bar Functions

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:

  • SB_CTL: A scroll bar control
  • SB_HORZ: The horizontal scroll bar of the specified window
  • SB_VERT: The vertical scroll bar of the specified window
  • SB_BOTH: The horizontal and vertical scroll bars of the specified window

All scroll bar functions return an error in the following cases:

  • The second parameter is SB_CTL, but the first parameter is not a valid handle to a scroll bar control.
  • The second parameter is SB_HORZ, SB_VERT, or SB_BOTH, but the first parameter points to a window that does not have a scroll bar child window.

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);

EnableScrollBar

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.

GetScrollPos

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.

GetScrollRange

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.

SetScrollPos

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.

SetScrollRange

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.

ShowScrollBar

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.

Notification Messages

SBM_ENABLE_ARROWS

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.

SBM_GETPOS

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 */

SBM_GETRANGE

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 */

SBM_SETPOS

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 */

SBM_SETRANGE

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 */

SBM_SETRANGEREDRAW

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 */

Interfacing with the Keyboard

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;

Limitations

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, Windows 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 retrieve the full 32-bit value explicitly.

/* User dragged the thumb. */
case SB_THUMBPOSITION:
    xNewPos = GetScrollPos(hWndHorzScroll, SB_CTL);
    break;

case SB_THUMBTRACK:
    {
        SCROLLINFO si;
        si.cbSize = sizeof(si);
        si.fMask = SIF_TRACKPOS;
        GetScrollInfo(hWndHorzScroll, SB_CTL);
        xNewPos = si.nTrackPos;
    }
    break;

The Scroll32 sample that is included with this technical article demonstrates a different, less reliable technique, based on estimating the scroll position from the cursor position. Run that 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 problem with the method in the Scroll32 sample manifests itself in the following scenario:

  1. The user clicks to the right of the thumb and drags the thumb to set the scroll position.
  2. The user then clicks to the left of the thumb to set the scroll position.

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.