Considerations for Horizontal Scroll Bars in List Boxes

Kraig Brockschmidt and Kyle Marsh

Microsoft Developer Network Technology Group

Created: March 20, 1992

ABSTRACT

Applications can add horizontal and vertical scroll bars to list boxes that contain items exceeding the length and width of the list box. The MicrosoftÒ WindowsÔ graphical environment currently supports both types of scroll bars, but vertical scroll bars are managed by the system whereas the management of horizontal scroll bars is left to the application. This article explains how applications can implement and control the display of horizontal scroll bars in list boxes. It also describes functions that facilitate the management of horizontal scroll bars and provides a sample application that illustrates their use.

INTRODUCTION

The MicrosoftÒ WindowsÔ graphical environment has supported vertical scroll bars in list boxes since version 1.0. When all the strings in the list box cannot be displayed, Windows gives that list box a vertical scroll bar. When all the strings in the list box can be displayed, Windows removes the vertical scroll bar. This feature frees the application from manually managing the list box’s scroll bar.

Windows version 3.0 adds support for horizontal scroll bars in list boxes, but this support is not as automatic as the support for vertical scroll bars. When a horizontal scroll bar is visible in a list box, the control responds properly to the user’s input on the scroll bar. However, Windows does not automatically display the scroll bar when the list box contains a string longer than the width of the list box, nor does Windows remove the horizontal scroll bar when all strings in the list box are shorter than the width of the list box. The application must therefore manage the visibility of the horizontal scroll bar. This article explains the steps necessary to control horizontal scroll bars in list boxes.

CONTROLLING HORIZONTAL SCROLL BARS

A list box must be created with the WS_HSCROLL style to display a horizontal scroll bar. If the list box does not have this style, the application cannot enable a horizontal scroll bar. By default, the horizontal scroll bar is not shown even when the list box is created with the WS_HSCROLL style. The application must manipulate the list box to display the horizontal scroll bar.

The WS_HSCROLL style enforces the WS_VSCROLL style for the list box. A regular single-column list box cannot have a horizontal scroll bar without a vertical scroll bar.

String Extent

To decide when a list box needs a horizontal scroll bar, the application must know the string length displayed in the list box. The application must determine the string width in pixels, a value known as the horizontal extent of the string.

The Windows graphics device interface (GDI) GetTextExtent function returns the extent of a string. The GetTextExtent function returns a DWORD, in which the low-order word contains the horizontal extent of the string and the high-order word contains the vertical extent. The GetTextExtent function requires a handle to a display context (hDC). When calculating the extent of a string that is displayed in a list box, you must use the hDC of the list box. Because the list box does not usually have the current font selected into the hDC, the application must select the list box font into this hDC before calling GetTextExtent. When the WM_GETFONT message is sent to the list box, a handle to the current font is returned. The handle that the list box returns must be selected into the hDC with SelectObject. With the correct font selected in the hDC, GetTextExtent returns a DWORD, which contains both extents.

The low-order word of the value returned by GetTextExtent is the exact width of the string. This value is not directly usable when controlling horizontal scroll bars because the list box clips a few pixels from the right side of the list box when it is scrolled completely to the right. The application needs to add buffer pixels to the string’s extent to compensate for this clipping. The width of the character ‘X’ provides a reasonable number of buffer pixels. This value is font specific and is a good substitute for the border size of the list box, which is unavailable. The width of ‘X’ can be obtained by calling GetTextExtent using the same hDC used in obtaining the string extent. By adding the width of ‘X’ to each string’s extent, the list box appears consistent when scrolled completely to the right, and no strings are clipped.

List Box Extent

List boxes also have an extent property, which is not the visible width of the list box but the horizontal scrolling range of the list box. For example, a list box that uses a fixed font 6 pixels wide with a display 25 characters across can show 150 pixels. When a string containing 100 characters is added to the list box, the list box must be able to scroll 75 characters (450 pixels) horizontally, and it must have its horizontal extent set to 600 pixels (100 characters multiplied by 6 pixels).

The current list box extent is obtained by sending the LB_GETHORIZONTALEXTENT message to the list box. Windows sets the default horizontal list box extent to 0. A list box with a horizontal extent of 0 will never show a horizontal scroll bar. This is why the scroll bar is not visible when a list box is created with the WS_HSCROLL style.

An application can change the list box extent by sending the LB_SETHORIZONTALEXTENT message to the list box with the desired pixel extent passed in wParam. However, setting the horizontal extent does not affect the visibility of the scroll bar until the control is repainted in Windows version 3.0. In Windows version 3.1, the scroll bar appears if the horizontal extent is set to a value greater than the list box pixel width.

SHOWING AND HIDING THE HORIZONTAL SCROLL BAR

Windows shows or hides the horizontal scroll bar depending on the list box extent and the list box pixel width. When the extent is greater than the list box display area, Windows shows the horizontal scroll bar; when the extent is less than the list box display area, Windows hides the horizontal scroll bar.

Windows version 3.1 shows or hides the scroll bar with the LB_SETHORIZONTALEXTENT message. Windows version 3.0, however, changes the scroll bar’s visibility only when a list box item is added, inserted, or deleted, and the list box is redrawn. Normally the add, insert, and delete operations cause the list box to redraw, but if the list box has received a WM_SETREDRAW message with wParam set to 0 (redraw turned off), the visibility of the scroll bar remains unchanged.

Before deleting the list box string that has the largest extent, the list box extent must be set to the next-largest remaining string extent. If this new extent is smaller than the list box width, Windows removes the horizontal scroll bar when the largest string is deleted. If the list box is scrolled to the right by as little as one pixel, Windows does not hide the horizontal scroll bar no matter what the list box extent because the user must be able to scroll the list box to the left. If the horizontal scroll bar were removed, the user would not be able to see any part of strings that were not already visible. Therefore, Windows does not remove the scroll bar. To ensure the removal of a horizontal scroll bar, force the list box to scroll completely to the left by sending a WM_HSCROLL message to the list box with SB_TOP in wParam. This is necessary only when all remaining list box strings have a smaller extent than the list box width.

RESETTING THE LIST BOX CONTENTS

An application must scroll the list box completely to the left to ensure removal of the horizontal scroll bar when resetting the list box content with the LB_RESETCONTENT message.

Windows version 3.0 requires more steps to remove a horizontal scroll bar because it does not automatically remove the horizontal scroll bar on an LB_RESETCONTENT message as does Windows version 3.1. Windows version 3.0 requires the list box extent to be set to 0 and an add, insert, or delete operation to occur. The following steps effectively reset the list box under Windows version 3.0:

1.Scroll the list box completely to the left by sending a WM_HSCROLL message with SB_TOP in wParam.

2.Set the horizontal extent to 0 with LB_SETHORIZONTALEXTENT.

3.Delete the first item in the list box with the LB_DELETESTRING message.

4.Remove the remaining strings by sending LB_RESETCONTENT.

MAINTAINING ALL STRING EXTENTS IN A LIST BOX

The most convenient way to maintain a list of the string extents in a list box is to use property lists.

Every window can have a property list in which each property is a string with an associated WORD value, which often contains a data handle. A window references a WORD in its property list by the string label.

You can save a list of the list box text extents in a local or a global memory object that can then be attached to a list box window property list. This allows each list box to keep its own list of string extents, freeing the application from maintaining a mapping array of list box handles to data handles.

If you store a sorted string extent list in descending order, an application can find string extents with a binary search. When a string is added to the list box, the application can insert that string’s extent into this list in the proper sort order. If this new extent is the first in the list, the string is the longest, and the application must send an LB_SETHORIZONTALEXTENT message to the list box specifying that new extent. If the extent is inserted in any other position, sending this message is unnecessary because this is not the longest string.

When a string is removed from the list box, the application must remove that string’s extent from the sorted string extent list. If the extent is the first in the list, the string is the longest, and the application must send an LB_SETHORIZONTALEXTENT message to the list box specifying the next extent, which is the next longest. If the extent is not the first in the extent list, sending this message is unnecessary.

When all items are cleared from the list box, the application must clear the entire list of extents and send an LB_SETHORIZONTALEXTENT message specifying an extent of 0. If the application is running under Windows version 3.0, it should also scroll the list box completely to the left, delete the first item in the list box by sending an LB_DELETEITEM message, and send the LB_RESETCONTENT message.

SAMPLE CODE

A complete sample application, LISTHORZ, demonstrating a list box with a horizontal scroll bar is included on this CD. The sample code consists of two sections:

1.A dynamic link library (DLL) containing five support functions that simplify management of horizontal scroll bars. These functions are described in the next section.

2.A sample application that demonstrates how to call the DLL functions.

DLL Support Functions

FInitListboxExtents

Syntax BOOL FInitListboxExtents(hList)

This function allocates local memory to store a list of string extents for the list box identified by hList. The handle to this local memory is saved in the property list of the list box. This function should be called after the list box is created—for instance, during WM_INITDIALOG processing.

Parameters hList HWND Handle to the list box that will use a horizontal scroll bar.
Return Value The return value is TRUE if no errors occur; FALSE if memory could not be allocated.

FFreeListboxExtents

Syntax BOOL FFreeListboxExtents(hList)

This function frees the memory allocated for the extent list of the list box identified by hList. The property that stores the memory handle set by FInitListboxExtents is removed. This function should be called when the list box is being destroyed.

Parameters hList HWND Handle to the list box that was previously used by FInitListboxExtents.
Return Value The return value is TRUE if no errors occur; FALSE if memory could not be freed, in which case the property is not removed.

ResetListboxExtents

Syntax void ResetListboxExtents(hList)

This function removes all previously saved extents in the extent list by calling FFreeListboxExtents and FInitListboxExtents in succession. The horizontal extent of the list box is set to 0, and any horizontal scroll bar is removed. This function should be called before an LB_RESETCONTENT message is sent to the list box.

Parameters hList HWND Handle to the list box that will be reset.
Return Value None.

WAddExtentEntry

Syntax WORD WAddExtentEntry(hList, psz)

This function adds an extent entry into the extent list for the list box, using the string identified by psz and the current font in the list box. If the extent added is larger than any other in the extent list, an LB_SETHORIZONTALEXTENT message is sent to the list box with this new extent.

This function must be called before the string is added to the list box with LB_ADDSTRING or LB_INSERTSTRING.

Parameters hList HWND Handle to the list box to which the string will be added.

psz LPSTR Pointer to the string to be added. In Windows version 3.0, a pointer must be passed instead of an index into the list box because this function must be called before the string is added. In Windows version 3.0, if this function is called after the string is added, the horizontal scroll bar is not properly maintained.

Return Value The return value is 0 if the string added is not the longest string in the list box and therefore did not change the visibility of the horizontal scroll bar. In this case, the extent is added to the list.

If the added string was the longest, the return value is the extent of that string, indicating that the scroll bar has possibly become visible.

A return value of –1 indicates an error.


WRemoveExtentEntry

Syntax WORD WRemoveExtentEntry(hList, iSel)

This function removes the extent entry of the list box string identified by the index iSel. If the string to be removed is the longest in the list box, the list box is scrolled completely to the left and the horizontal extent is set to the extent of the next longest string.

In Windows version 3.0, this function must be called before an LB_DELETESTRING message is sent.

Parameters hList HWND Handle to the list box from which a string will be removed.

iSel WORD Index of the string to be removed.

Return Value The return value is 0 if the string removed did not affect the visibility of the horizontal scroll bar, that is, if a longer string exists or if no scroll bar existed in the first place.

If the removed string was the longest, the return value is the extent of that string, indicating that the scroll bar has possibly disappeared.

A return value of –1 indicates an error.