User Interfaces


Creating Spin Controls for Windows

Keith E. Bugg


Keith Bugg is a principal at Tristar Systems, Inc., which provides software consultants and technical training for Windows and DEC platforms. Direct queries to CompuServe ID 72203,3612.

Users of MS-Windows programs often need a convenient way to input a value that falls within a certain range. For example, users of a process control system may need to set the temperature of a vessel within a range of 100 to 200 F. Because of their simplicity and intuitive feel, spin controls often provide the best user interface in such applications; a spin control is one that allows the user to change an input value by clicking on an up or down arrow (or other appropriate icon). Clicking the up arrow increases the value; clicking the down arrow decreases it. As the arrows are clicked, the new value is displayed to the user via an edit box control and/or a graphical image (e.g., the temperature could be displayed as a thermometer).

In this article, I present a simple implementation of spin controls in C++. A sample program accompanies the text and should serve as a jumping-off point for future exploration. The sample program's spin controls allow the user to specify a date within a given range. The program demonstrates one of the benefits of spin controls: they can prevent the user from entering invalid inputs. In this example, spin controls keep the user from entering an invalid date (including leap years).

Creating Spin Controls

From a Windows programming perspective, a spin control consists of three fundamental parts: a read-only text box for displaying the current value, an up arrow control for increasing the value, and a down arrow control for decreasing the value. These controls can be placed on the screen in either a vertical or horizontal orientation. Figure 1 illustrates some typical spin controls. (The reason the text box is marked read-only is to prevent the user from clicking in the box and directly entering a value.) The controls are implemented as bit maps. Most Windows development kits supply a plethora of bit maps, among which are the familiar up/down arrows used here. By making a connection between an object (the edit box) and an event (a mouse click within the outlines of a bit map) you create a spin control.

Making the Connection

In essence, spin control software is simply a matter of managing rectangles on the screen. The connections between the edit box and its controls are established by the following steps:

1. Load into memory the control bit maps using LoadBitmap; note sizes.

2. Create a memory Device Context (DC) via CreateCompatibleDC.

3. Select bit maps into memory DC via SelectObject;

4. Copy bitmaps using BitBlt to the output DC; note their positions.

5. Trap left mouse click event; test if cursor was inside bit map.

6. Process accordingly; update edit box as necessary.

Save the bit map sizes and positions and map these into RECT objects; the API function PtInRect can then indicate if the mouse scores a "hit."

The Sample Program

The sample Windows program demonstrates the preceding actions in more detail. This program builds a main window with a menu bar containing one option. When selected, the option invokes a dialog box which is associated with the date processor (see Figure 2) . The function of most interest is DateDialog; the other stuff is mostly the grunt work to build the window, menu, etc. When the dialog box is initialized, the program creates edit boxes as child windows and maps them to the dialog box; these edit boxes show the current day and year, respectively. The program creates a combobox for the month and populates it with the names of the twelve months. (The months are not sorted alphabetically because the index into the combobox equals the numerical month; thus January is 0, February is 1, etc.) Next, the program loads the bit maps for the up and down arrows and saves each bit map's width and height in variables. The program then creates a memory device context by calling CreateCompatibleDC. The date processor is initialized with a default date of 15-Jan-1993. Finally, the program defines the bit maps as rectangles, and maps them to the screen. (These rectangles have been hard-coded for simplicity's sake.)

The Windows messages of major interest here are WM_PAINT, WM_LBUTTONDOWN, and WM_RBUTTONDOWN. WM_PAINT causes the current value of the date to be displayed, while the button messages effect changes in the date values. The program responds to WM_PAINT by selecting the bit map objects (both the up and down arrows, in sequence) into the memory DC created earlier; these objects are then copied to the screen's DC via the BitBlt function.

The message handlers for the buttons are much more involved, but they have more to do. First, the handlers record the position of the mouse cursor at the time of the click. The program calls the Windows API function PtInRect to determine if the cursor is in one of the rectangles occupied by a bit map. If the cursor lies within a bit map region, the current date value is incremented or decremented. Range checking is also enforced here; note the usual nasty code that checks for leap years.

The right button down message handler requires even more code, since it supports year "slewing." Slewing refers to the ability to update the value continuously by holding the mouse button down while in one of the bit maps. The slewing works as follows: once the program know the cursor is in one of the year control arrows, it starts another loop. This new loop calls PeekMessage to see what's in the pipeline. This loop immediately gets the mouse position — if the user has moved out of the rectangle, the year stops slewing (even if the button is still down). Also, if the program finds a WM_RBUTTONUP message, the loop breaks and the year stops slewing.

The rest of the program is concerned with housekeeping chores and exiting. Most noteworthy in this regard are the DeleteDC and DeleteObject functions. These functions free up the memory locked for loading the bit maps and the memory DC.

I built this program with Borland's Turbo C++ v 3.1, but it should port easily to most Windows-SDK-type platforms. To build this program, you will need to create your own MAKE file and include SPIN.C, SPIN.RC, and SPIN.DEF. These correspond to Listing 1, Listing 2 and Listing 3, respectively.

Ups and Downs of Spin Controls

As a Windows programmer, spin controls can save you a lot of work while increasing software reliability. This reliability results from a simple property of spin controls — values are selected, not entered. This property allows your program to easily enforce ranges, data typing, defaulting, and other ammenities.

Spin controls can also have a down side. For starters, the target platform must have a mouse. Writing a keyboard interface is clearly possible, but messy. Also, the value being updated should span a relatively narrow range. It would be poor design to force the user to click through 1000 values, for example. A slider control would be more appropriate for values with a wide range.

Final Remarks

For certain Windows programs, spin controls can provide a very user-friendly control that pays extra dividends by way of increasing quality and reliability. Likely candidates are process control systems, multi-media applications (e.g., volume controls), and data base front ends. The construction of spin controls by the technique described in this article lends itself quite readily to object orientation, or at least to a DLL.

Spin controls are widely available from Microsoft and other vendors in the guise of Visual Basic Custom Controls (VBXs). I've found these to be reliable and cost-effective; if you do a lot of development work, you may be better off to purchase a VBX. One caveat: VBXs will not be supported under Windows NT (in their current form, at least), so pick your battles carefully. You might want to hedge your bet and encapsulate the concepts of the sample program into a C++ class for spin controls.

Bibliography

The algorithm for the leap year calculations is from The C Programming Language by Kernighan & Ritchie, 2nd. edition.