This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


July 1998

Microsoft Systems Journal Homepage

Control Spy Exposes the Clandestine Life of Windows Common Controls, Part I

Download ControlSpy.exe (2,914KB)

Mark Finocchio is on the Microsoft user interface developer support team. He specializes in windowing, messaging, user interface, and common controls. He can be reached at markfi@microsoft.com.

What you don't know about common controls can lead to much frustration. You might spend many hours writing an interface feature only to find out later that it already has been implemented by a control. Or maybe you knew the control was there and had to spend hours trying out its various styles and messages to get a feel for how to use it. Control Spy (see Figure 1) is a new tool that I wrote for learning, understanding, and testing the common controls.
Figure 1 Control Spy
  1. Control list
  2. Control Notifications list
  3. Messages to Control list
  4. Messages that can be sent to the selection control
  5. Popup message description
  6. Styles command
  7. Filters command
  8. Information command
  9. Statement box
  10. Send button
  11. Return Value box 
  12. Parameter and Return Types box
Figure 1 Control Spy


      Control Spy is a suite of 22 programs; there is one Control Spy program for each Windows® common control. This article covers how to use Control Spy to gain new insight into the common controls, including the Microsoft® Internet Explorer 4.0 controls and the latest updates to older controls. I'll use Control Spy as a starting point for discussing the important messages, notifications, styles, and structures of each common control, along with typical problems and workarounds.
      This article talks about straight Win32® SDK programming. At this level, messages are sent directly to the control and notifications are handled directly. While it's more convenient to program the controls using the Microsoft Foundation Classes (MFC) or Visual Basic®, you must know what's happening behind the scenes to really understand how the controls work. SDK programming is more time-consuming, but it gives you power, speed, understanding, and flexibility.
      This article assumes that your system has version 4.71 or higher of COMCTL32.DLL—the DLL that contains the common controls. This version comes with Internet Explorer 4.0, which you can download from http://www.microsoft.com/ie. You can obtain the Control Spy suite by downloading the code file at the top of this page. The suite comes with a utility called ComCtl32Ver that lets you check your version of COMCTL32.DLL.
      The Control Spy suite includes source code so you can write custom notification handlers and the like. To build the source, you must have the header and library files for COMCTL32.DLL present in the appropriate directories. the Se files come with the Internet Explorer 4.0 Author's Toolkit, which is available at http://msdn.microsoft.com/sdk.

The Control Spy Story
      As a member of the Microsoft user interface support team, I receive reports of problems with the common controls. To better understand the problems and come up with solutions, my colleagues and I must write programs to reproduce the reported behavior. This can be a repetitive and time-consuming task since each control has different messages, notifications, and styles. To speed up the process, I decided to write some sample programs to change a few styles and send a few messages to controls at runtime—at least that's what I started out doing!
      Early into this new project, I decided that supporting only a few messages and styles wasn't going to cut it. To be truly useful, a developer must be able to see all the messages going to and from the control. Another great feature would be the ability to send messages to the control at runtime without having to compile any code. Finally, I thought it would be very handy if the program gave easy access to an extended description of any message, so the user wouldn't have to refer to the documentation. The result was Control Spy. (Please note that Microsoft does not provide technical support for Control Spy because it's not part of the regular product line.)
      Control Spy can be used at three different levels. At minimum, it lets you interact with each control to get a feel for it—see what the user sees. You can observe the messages passing to and from the control to get an idea of how to handle notifications from it.
      The next level is experimentation with the control. You can turn styles on and off, and you can use the built-in parser to create and send messages to the control at runtime. To save typing, you can create a statement list of messages you send frequently. Control Spy tracks results from the messages and logs the M in a separate text file.
      The last level involves modifying Control Spy source code to suit specific requirements. It is easy to add notification handlers and callbacks. The source code is structured so that code can be dropped in without having to write all the logic code around it (such as switch statements, pointer initialization, type casting, and so on). I've already written the template for you.
      The benefits of Control Spy are apparent. You can spend less time learning about a control while gaining a better understanding of it than you would otherwise. You can quickly discover a control's features, flexibility, limitations, and problems. Any limitations in Control Spy can be overcome by modifying the source code.

The Control Spy Primer
      Since this series of articles uses Control Spy as a way to explain how the common controls work, I will start with a quick run-through of Control Spy fundamentals. In Part 2 of this series, I'll take a detailed look at the more advanced functions of Control Spy, such as using log files and statement lists, and tailoring Control Spy by modifying its source code.
      The Control Spy interface consists of several sections (see Figure 1). The controls are listed in the upper left-hand Control area of the window. The Control Spy window is not the parent of a control. Instead, it registers and creates a separate window for the control to reside in. This lets you alter any property and ensures that all testing scenarios are possible.
      All notification messages coming from the control are displayed in a Control Notifications list. All messages sent to the control are recorded in a Messages to Control list. To clear either list, click on the red X right above it. The set of messages you can send to the control at runtime is listed in the lower-left corner of the window. You can send any control message, but regular Windows messages are not supported in this version. Some controls use functions instead of messages, and the Se are supported. To obtain a detailed description of a message, simply move the cursor over it and the description will pop up.
      Select Styles from the pulldown menu to access the Control Styles dialog box (see Figure 2). This dialog displays all the available and extended styles for a control, and highlights the Ones that are in use. For most controls, you can apply styles in two ways: by using SetWindowLong or by recreating the control. For controls that do not accept style changes via SetWindowLong, recreating the control is the Only option. Note that any changes made to the control will be lost when you recreate it. As you experiment with a control's styles, you may notice that styles you didn't specify are turned on. This is because some styles are combinations of other styles.

Figure 2 Control Styles Dialog
Figure 2 Control Styles Dialog


      While interacting with a control, hundreds of messages and notifications go flying by in the message lists. Select Filters from the menu to choose which types of messages or notifications to ignore or "filter out." There are three types of messages: Windows, Control, and Unknown. Windows messages have the WM prefix. Control messages are those that are native to the control. For example, Tree Views use messages prefixed with TVM. Unknown messages are everything else.
      There are also three types of notifications: Common, Control, and Unknown. Common notifications start with NM. Control notifications are what the control sends. For example, Tree View sends notifications prefixed with TVN. Unknown refers to all other notifications.
      Control Spy provides useful information about each control which you can access with the Information menu option. The Information dialog (see Figure 3) displays every message, notification, style, and extended style associated with the control. The Facts section provides clarifications where needed, and information on problems you may encounter. The Messages section lists control-specific messages (including some that were inadvertently omitted from the control documentation) as well as the Windows messages handled by the control directly—information that is generally undocumented.

Figure 3 Information Dialog
Figure 3 Information Dialog


      Control Spy's greatest strength is its ability to construct and send messages to controls at runtime. This is accomplished via a built-in parser. Messages are sent to controls with a simple command string. You can even call functions for those controls that require it. You can send simple messages or complex ones with structures composing the parameters.
      The message string has a one-to-one correspondence with a standard SendMessage call. To see how it works, try this example. Run the Tree View Control Spy. In the Statement box, enter the following:

 
 MSG (TVM_EXPAND,TVE_EXPAND,node2)
Then select the Send button. The Earth node will expand and the Return Value box will contain some information.
      What happened behind the scenes? Technically, Control Spy did the following: it created a Tree View control with several styles, including TVS_ EDITLABELS, TVS_HASBUTTONS, TVS_HASLINES, and TVS_LINESATROOT. Then it initialized the control and added items and subitems. Next, it obtained the Tree View handle to the Earth node, and called the statement
 
 SendMessage(hTVControl,TVM_EXPAND,TVE_EXPAND, hEarthNode);
where hTVControl is the Tree View's handle and hEarthNode is Earth node's handle. Finally, it noted the return value and noted all messages going to and coming from the control during the Operation. That's a lot for the time it took to type in that statement!
      All messages are sent to controls in this manner. The syntax for sending messages is:
 
 MSG (<MessageID>, <wParam>, <lParam>)
You can also use the parser to change styles with the following syntax:

 Styles (<StylesOn>, <StylesOff>, <ExStylesOn>, <ExStylesOff>,
         Recreate|SetWinLong)
Note that with this syntax, styles are turned off before they are turned on. As I mentioned earlier, some controls have no associated messages and instead use function calls. The syntax for this is:

 API: <FunctionName> (<Param1>, <Param2>, ..., <ParamN>)
      Control Spy provides default statements for every message. When you select a message, the Statement box is filled with correct syntax for sending that message, and the Parameter and Return Types box displays message- specific parameter and return value descriptions.
      Control Spy can handle any defines used in conjunction with messages. In the previous example, the parser knew the numeric meaning of TVE_EXPAND and substituted the actual value before sending the message. Many messages require more complex arguments, such as pointers to structures or even handles to other windows or controls. Control Spy can handle the Se messages, too. In the previous example, the node2 keyword referred to the second parent node in the tree. When the parser encountered this, it located the second parent item handle and sent its numeric value on to the Tree View control.
      Many controls let you associate an Image List with the M. In the Se cases, the parser defines the keyword imagelist and makes available a predefined, pre-initialized Image List control. If you don't want to use this free Image List, you can define your own by modifying the source code. The location in the source code is clearly marked with comments.
      Besides imagelist, other keywords are defined in nearly all the Control Spy programs. You can reference the M when needed in the Parameter and Return Types box. In all cases, there is a corresponding pre-initialized global variable in the source that lets you provide your own object. the Se custom objects are then accessible through the Control Spy parser.
      The parser defines a straightforward syntax for structures:

 <StructureName> (<Member0>, <Member1>, ..., <MemberN>)
Sometimes, structures are defined within structures. Control Spy always provides a template statement for you to use or modify when a message is selected. As an example, launch the Tree View Control Spy and either select TVM_ SETITEM and modify the template, or type in the following:

 MSG ( TVM_SETITEM, 
     0, 
     TVITEMEX(
         TVIF_IMAGE|TVIF_TEXT|TVIF_STATE, 
         node2,
         TVIS_BOLD/0/0,
         TVIS_BOLD,
         LPSTR_TEXTCALLBACK,
         127,
         I_IMAGECALLBACK,
         0,0,0,0))
Press Send and see what happens: the Earth node now says Callback, the image associated with it changed, and now its label is in boldface.
      Actually, what you did was set the properties of a Tree View item. For a Tree View control, you need to use a TVITEMEX structure to define the properties. Upon hitting Send, a TVITEMEX structure was allocated in memory, all of its members were set, and a pointer to it was sent to the control using SendMessage. After SendMessage returned, all memory used for the structure was freed. The parser was aware of all the defines used in the structure and the bitwise OR operators.
      You might wonder what the slashes in the syntax mean. The third parameter of this structure is the state member of TVITEMEX. This member packs three separate values into one variable. If you were to look up this structure, you'd find that bits 0 through 7 are used for state, 8 through 11 for overlay image, and 12 through 15 for state image. The parser distinguishes between the Se values by using slashes.
      After sending a message, the Return Value box displays the return value. Depending on the message, Control Spy might include additional information about the value such as "success" or "fail." It also displays the parameters sent to the control (wParam and lParam).
      This redisplay of the parameters can be very useful. Many controls alter data passed to the control through a pointer. You can see the Se changes in the Return Value box. Also, the redisplay of the parameters lets you compare what was sent with what came back to ensure that the correct data was sent in the first place. The Return Value box always displays the result of the last sent message.
      You now have enough information about Control Spy to start benefiting from it. Its functionality doesn't end here, though. In the next article in this series, I will explain the more advanced features of Control Spy. The rest of this article is dedicated to a discussion of the common controls.

The Evolution of Common Controls
      The initial release of Windows 95 (also known as Windows 95 Gold) included a set of 15 common controls that developers could use to enhance the functionality of their applications. Various updates have since been released to increase the total number of common controls to 22. the Se updates have come with new releases of Internet Explorer.
      The common controls ship in a single DLL called COMCTL32. Windows 95 Gold contained version 4.0 of COMCTL32. The controls included were: Animation, Drag List Box, Header, Hot Key, Image List, List View, Progress Bar, Property Sheet, Status Bar, Tab, Toolbar, Tooltip, Trackbar, Tree View, and Up-Down. (The Rich Edit control is also considered a common control even though it doesn't actually exist in COMCTL32. Control Spy doesn't cover this control.)
      Internet Explorer 3.0 included version 4.70 of COMCTL32. Four more controls were added: ComboBoxEx, Date and Time Picker, Month Calendar, and Rebar. The "custom draw" technology was also included with this version. The 4.71 release of COMCTL32 shipped with Internet Explorer 4.0. This version introduced Flat Scroll Bar, IP Address, and the Pager control.
      If you've been keeping up with all the COMCTL32 releases, you may be familiar with the ActiveX® SDK. This SDK came out around the same time as Internet Explorer 3.01. It included the common control library and header file so that you could create applications with the new controls. There was also a red note in the common controls documentation saying that the SDK was provided as a "technology preview" of what was to come in Internet Explorer 4.0. Thus, the new and updated controls weren't actually released until Internet Explorer 4.0 was released.
      Of the 22 controls released with Internet Explorer 4.0, only three are actually brand new. However, since the new controls that came with Internet Explorer 3.0 were considered a preview and subject to change, I'm considering those new as well. Figure 4 lists all the new, updated, and unchanged controls with information about their usage.
      Like all windows, controls require that you register their window classes. You do this with the InitCommonControlsEx API function. (It's more flexible than InitCommonControls because you can choose to register specific control classes by using the ICC flags.) Once registered, you create the control using the class name define values listed in Figure 4.

New Common Controls
      Seven new controls are available for use in your applications. They range from simple controls like the Flat Scroll Bar to complex controls such as the Rebar (also known as the "coolbar"). The new controls can give your interfaces a modern look and feel. You can use Control Spy to try out the Se controls. The statements you'll need are available in a text file within the Control Spy Source directory. Each individual statement can be cut and pasted into Control Spy to save typing. So, without further ado, let's take a look at the new common controls.

ComboBoxEx
      ComboBoxEx is an extension to the normal combobox (see Figure 5). It uses an associated Image List to provide support for images. It also supports text and image callbacks so the control can ask you what text or image to display when it needs it. The Windows shell update that comes with Internet Explorer 4.0 uses ComboBoxEx to display the current address.

Figure 5 ComboBoxEx
Figure 5 ComboBoxEx

      The ComboBoxEx control contains a child combobox, and supports a subset of the standard combobox messages and styles. The Control Spy Information dialog lists the M. A set of extended styles lets you customize the behavior of the control. As with many other common controls, the Se extended styles are not set using CreateWindowEx or SetWindowLong like normal extended styles in Windows. Instead, a special message is used. In this case, it's CBEM_ SETEXTENDEDSTYLE. To manipulate the ComboBoxExImage List, use CBEM_SETIMAGELIST and CBEM_ GETIMAGELIST.
      For all controls that refer to it, "index" means an internal item position maintained by the control, such as an index into an array. Index 0 refers to the first item in the control. The indices of items change as items are inserted and removed. Thus, indices cannot be used as unique identifiers for items. Depending on the control, the internal index may or may not correspond to the Order in which items are displayed on the screen.
      The ComboBoxEx control uses indices to refer to its items, and displays items in the same order as their indices. If you need a unique value for identifying a ComboBoxEx item, employ the user-definable storage available in the COMBOBOXEXITEM structure.
      The name "ComboBoxEx" might mislead you into thinking the control is an extension of the combobox that inherits all its functionality from a standard combobox. This is by no means the case. Unlike a standard combobox, ComboBoxEx cannot sort items, does not support automatic keyboard selection, does not provide integral-height lists, and does not support variable-height items. Though ComboBoxEx accepts the CBS_HASSTRINGS style, it will cause the control to behave improperly. The CBS_SIMPLE style, which causes the dropdown list to always stay open, does not display selections correctly. Item selection with CB_SELECTSTRING always fails. To search for exact string matches, you must use CB_FINDSTRINGEXACT instead. ComboBoxEx also does not provide automatic string completion like you see throughout the rest of Internet Explorer 4.0.
      Still, you'll find ComboBoxEx to be useful once you understand what it can and cannot do. If you're familiar with List Views or Tree Views, you will see that ComboBoxEx messages are similar. It uses structures to define item properties, you can assign an Image List to it, and it supports text and image callbacks.
      If you load ComboBoxEx Control Spy and try out the control, you will notice that it never gets user input messages such as WM_KEYDOWN. This is because the child combobox gets the messages. If the control is in dropdown mode, the child edit control of the combobox is the One that gets the messages. ComboBoxEx gives you access to the Se controls with CBEM_GETCOMBOCONTROL and CBEM_ GETEDITCONTROL. Use the Se handles to implement your own custom keyboard handling. To simply determine if the content of the edit control has changed, use CBEM_ HASEDITCHANGED.
      To insert, modify, or retrieve information about an item in a ComboBoxEx control, use a COMBOBOXEXITEM structure. Try the following Control Spy message statement for inserting an item:

 MSG ( CBEM_INSERTITEM,
       0,                    // wParam is not used
       COMBOBOXEXITEM(       // lParam is pointer to COMBOBOXEXITEM
       CBEIF_IMAGE           // Set mask
       |CBEIF_SELECTEDIMAGE|CBEIF_INDENT|CBEIF_TEXT,
       1,                    // Insert at the end
       "My ComboBoxEx Item", // Item label
       0,                    // Text length not needed
       2,                    // Use Earth image
       2,                    // Still use Earth when selected
       0,                    // Overlay image (unused, not in mask)
       8,           // Indent 80 pixels
       0))          // Extra information (unused, not in mask)
After selecting Send, you will see a new item inserted and indented at the end of the list. The return value is the index number of the new item. Make a note of the index number for use in the next example.
      If you want the control to ask for an item's label or image every time the control needs to draw it, use callbacks. Let's change the item just entered to use text and image callbacks. Again, a COMBOBOXEXITEM structure is required.

 MSG ( CBEM_SETITEM,
       0,                  // wParam is not used
       COMBOBOXEXITEM(     // lParam is pointer to COMBOBOXEXITEM
       CBEIF_IMAGE         // Set mask
       |CBEIF_SELECTEDIMAGE|CBEIF_TEXT,
       9,                  // Item index (yours might be different)
       LPSTR_TEXTCALLBACK, // Notify us when need label
       0,                  // Text length not needed
       I_IMAGECALLBACK,    // Notify us when image is needed
       I_IMAGECALLBACK,    // Notify us when selected image is needed
       0,                  // Overlay image (unused, not in mask) 
       0,                  // Indentation (unused, not in mask)
       0))                 // Extra info (unused, not in mask)
If you scroll to the item, you can see that both the label and image has changed. Control Spy provided default handling of CBEN_GETDISPINFO. This is the notification that ComboBoxEx uses to inform the parent that callback information is needed.
      Select another item in the ComboBoxEx so the callback item isn't the selected item. Now scroll the list up and down and keep an eye on the notification list. Notice that CBEN_ GETDISPINFO appears only when the callback item is in view. If the ComboBoxEx control contained thousands of items, you could use callbacks so the control wouldn't have to maintain all your data. This approach would take less memory and load much quicker.

Date and Time Picker
      The Date and Time Picker control (see Figure 6) provides an easy way to exchange date and time information with the user. You can use custom format strings to change how the control displays date and time information. The callback feature lets you accept custom information from the user that the control doesn't support directly. You can see an example of the Date and Time Picker in the Windows Start|Find|Files or Folders dialog box, where it lets the user specify a date range for the search.

Figure 6 Date and Time
Figure 6 Date and Time

      After you create a Date and Time Picker control, initialize it with the current system date and time. Use the SYSTEMTIME structure to get date and time information to and from the control. The phrase "system time" in the Date and Time Picker documentation refers to the date and time information maintained by the control, not the computer's time.
      Occasionally, you might want the Date and Timer Picker to prompt the user for information that it doesn't support directly. For example, you might want the user to be able to input time zones. This can be implemented by using field callbacks.
      When using callbacks, you need to keep a few things in mind. The Date and Time Picker sends notifications asking for information concerning the callbacks in use. Callbacks are identified by the number of Xs used in the Original format string for the particular callback. Fields are unique only in the number of Xs used. The amount of display room the control sets aside for fields depends on what is returned in response to DTN_FORMATQUERY notifications, not by how many Xs are used.
      The control knows what text to display in callback fields by responding to DTN_FORMAT. When the user interacts with callback fields via the keyboard, DTN_WMKEYDOWN notifications are sent. If there are changes to any member of the SYSTEMTIME structure passed via this notification, the control will update its display and reissue the format notifications. If no changes are made to the structure, any changes made to the field won't be displayed until an event occurs that makes the control refresh its contents.
      When the user can edit the date and time string directly, a notification is sent when the entire operation is complete—not during the Operation with keyboard notifications. It's up to the application to parse the string and update the provided SYSTEMTIME structure with any changes.
      When needed, the control creates a temporary Month Calendar control to query date information from the user. Messages are provided to allow the colors and font of the Month Calendar control to be changed. Technically, this can be done by using DTM_GETMONTHCAL to get a handle to the Month Calendar control and then sending messages directly to it. However, since the Month Calendar control is only created when the user selects the down-arrow button, code would have to be written to handle the dropdown, get a handle to the control, and then set the control properties. The addition of DTM_SETMCCOLOR, DTM_GETMCCOLOR, DTM_SETMCFONT, and DTM_ GETMCFONT actually saves quite a few steps.
      There are some peculiarities in how the control handles tabbing when it is in "app-can-parse" mode. The DTS_ APPCANPARSE style lets users edit the entire date and time string. A notification (DTN_USERSTRING) is sent to the application when the user is done editing this string. While editing, the user can't tab out of the control. To get around this problem, you must subclass the edit control that's created for the user edit operation and handle the keystroke messages directly.
      As mentioned before, the SYSTEMTIME structure is used to communicate date and time information to the control. To see it in action, load the Date and Time Picker Control Spy and send the DTM_GETSYSTEMTIME message. You will see how the SYSTEMTIME structure is updated and how the member variables are arranged.
      Next, try setting a date range:

 MSG ( DTM_SETRANGE,
       GDTR_MIN|GDTR_MAX,               // Set min and max date values
       systimearr(                      // Pointer to SYSTEMTIME array
       SYSTEMTIME(1998,1,0,1,0,0,0,0),  // Starting date is 1/1/1998
       SYSTEMTIME(2000,1,0,1,0,0,0,0))) // Ending date is 1/1/2000
The first parameter of the message specifies which range values are valid. The second parameter is a pointer to a 2-element array of SYSTEMTIME structures. The structures represent the low and high values of the range. The parser creates the array and fills its members with the values specified. Now let's say you want to change the way the control displays the date and time to something like "Jan 2, 98 Fri 2:30 PM." You can use this syntax:

 MSG ( DTM_SETFORMAT,
       0,                                 // Not used
       "MMM' 'M','yy' 'ddd' 'h':'mm' 'tt")// Pointer to format string
Notice the use of the single quotes. Single quotes surround text that is not intended for display formatting. You can insert the X format character into the string to specify field callbacks.
      Except for format strings and field callbacks, the Date and Time Picker control isn't very flexible. It's very good at the specific job it was designed to do, but if you want it to do just a little bit more you may be very frustrated!

Updated Common Controls
      The next set of controls I'll present have been updated in some way. The discussion will include updates that were introduced with Internet Explorer 3.0 since they were being previewed at the time. The updates come in the form of additional messages, notifications, styles, and structure members. Some controls have minor changes while others have gone through substantial changes. Thirteen controls have been updated, three of which will be presented here.

Animation
      The Animation control (see Figure 7) plays simple animation (AVI) files. It's meant for use during lengthy operations to indicate activity, but you can also use it just for a nice effect. Windows uses the Animation control by having pages fly from one folder to another folder during lengthy file operations.
Figure 7 Animation Control
Figure 7 Animation Control
      The Animation control normally creates a separate thread to play clips. However, you can override this and use a timer to synchronize playback by using ACS_TIMER. Sometimes it's convenient to make AVI files a resource of the application, so the Animation control supports loading from both resources and files. With ACS_TRANSPARENT, the control can remove the background of the clips it displays. The background color of the clip is defined as the color of the first pixel (location 0,0). The ability to load resource-based clips from other source modules is new to the Animation control. You can do this via the ACM_OPEN message or a new macro called Animate_OpenEx.
      The Animation control is limited to simple AVI files. This means the control supports animation clips that are uncompressed or RLE8 compressed, have a color table, contain a single video stream, and have no sound. Various utilities exist for creating the Se simple AVI files from a series of bitmaps. WriteAVI is one such program that can be found in the Microsoft Developer Network (MSDN).
      The Animation control is a simple way to add life to your app. Try experimenting with the control and its styles using Animation Control Spy. This Control Spy includes resource-based AVI files for testing the control's behavior.

Header
      The updated Header control (see Figure 8) lets you add images to column headers. The List View control makes use of this functionality in its report view. The Header control includes built-in support for drag-and-drop column reordering, and makes use of the standard common control styles (those styles starting with CCS).

Figure 8 Header Control
Figure 8 Header Control

      To better understand the Header control, it is important to know the difference between "index" and "order." Index does not refer to column position; order does. The control provides various order-to-index conversion messages. When you insert items into the control using the message HDM_INSERTITEM, you specify the column position of insertion—the Order, not the index.
      The issue of order versus index arises because the user can move Header items around (with HDS_DRAGDROP) and insert or delete items dynamically. If you always use order and keep in mind what the index is really up to, then you'll find the Header control to be more manageable.
      The Header control has several new additions. The new HDM_SETIMAGELIST lets you assign an Image List to the control. You can simultaneously display bitmap images and text. Text and image callbacks are now available along with HDN_GETDISPINFO to query for the information. The HDS_HOTTRACK style lets you highlight the item under the cursor. The drag-and-drop style and related notifications (HDN_BEGINDRAG and HDN_ENDDRAG) are new. Finally, new messages were added to support the Ordering I mentioned.
      There are some issues with the Header control concerning its notification messages. When the HDN_ITEMCLICK, HDN_ITEMDBLCLICK, or HDN_BEGINDRAG message is received, a pointer to a NMHEADER structure is sent along with it. This structure contains information about the item to which the notification refers. One member of the NMHEADER is not set when the Se notifications are received: pItem. This member points to a structure called HDITEM that contains additional information. However, it is set to null. Three other notifications make use of NMHEADER: HDN_ENDDRAG, HDN_BEGINTRACK, and HDN_ENDTRACK. the Se notifications set pItem, but they don't set the lParam member of the HDITEM structure to which it points. Take this into consideration when writing notification handlers.
      A Header item's properties are defined by the HDITEM structure. This structure is used for insertion and for modifying an item with HDM_SETITEM. Say, for example, you wanted to place your images on the right and right-justify the text:

 MSG ( HDM_SETITEM,         // Set Header item properties
       0,                   // Index of item to change
       HDITEM(              // Pointer to HDITEM structure
       HDI_FORMAT,          // Set mask
       0,                   // Item size (unused, not in mask)
       "",                  // Item string (unused, not in mask)
       0,                   // Bitmap handle (unused, not in mask)
       0,                   // Length of string (unused, not in mask)
       HDF_RIGHT|           // Format: Right-justify text
       HDF_BITMAP_ON_RIGHT| // Format: Place image on right
       HDF_STRING|          // Format: Display item's label
       HDF_IMAGE,           // Format: Display the image
       0,                   // User data (unused, not in mask)
       0,                   // Image list image (unused, not in mask)
       0))                  // Item's column (unused, not in mask)
      You can use hit testing to determine where a point lies on the Header control. You can also use it to support additional drag-and-drop capabilities. Try the following in the Header Control Spy:

 MSG ( HDM_HITTEST,
       0,                   // wParam is unused
       HDHITTESTINFO(       // Pointer to a HDHITTESTINFO structure
       POINT(10,15)))       // Point to test
The HDHITTESTINFO structure members are filled by the control after the hit test message is sent. Control Spy only requires you to initialize the structure members needed for a message. To gain more insight on the Order and index of Header controls, check out HDM_ORDERTOINDEX, HDM_GETORDERARRAY, and HDM_SETORDERARRAY messages.

Image List
      The Image List control (see Figure 9) maintains collections of equal-size images and supports visual dragging of the images for drag-and-drop operations. You can draw images with transparency and display images on top of other images. The common controls that display images use Image Lists.

Figure 9 Image List Control
Figure 9 Image List Control

      Like the Flat Scroll Bar control, the Image List control doesn't exist as an actual window. It never has a window handle so you can't send messages to it. Instead, you communicate with Image Lists using API functions. To create an Image List, use the API function ImageList_ Create. This returns a handle of type HIMAGELIST to the Image List.
      Though Image Lists are primarily used by other common controls for holding their images, they aren't limited to that task. You can also use the M in applications as a way to manage images. When images are added to Image Lists, two insertion types are available: masked and unmasked. A masked Image List contains complimentary monochrome images. You can supply the Se images directly, or have the control create the M for you.
      The additional masked bitmaps are needed for transparency. To remove an image's background color requires raster operations that use the Original image and a monochrome mask. The end result is a transparent BitBlt. The Image List control takes care of this internally.
      To draw images on top of each other, the control uses "overlay" images—images in the Image List designated by the application as overlays. Overlays have an additional numeric identifier. When you draw an image, you can use this number to specify an overlay image to be drawn on top of it. The number of available overlay images is now up to 15 from the Original four.
      The Image List control has many other new features. Image Lists can be resized dynamically with ImageList_ SetImageCount. If you make the list smaller, images are removed. If you make it larger, you must insert images in the new slots with ImageList_Replace. To create an exact duplicate of an Image List, use ImageList_Duplicate. The result is a new Image List that's identical to the source except for its handle.
      The new ImageList_DrawIndirect function provides more flexibility when drawing images—for example, it adds new raster operations. Its use requires an IMAGELISTDRAWPARAMS structure. ImageList_Copy can be used to move images around within the Image List.
      When creating an Image List for use by another common control, it's important to consider color depth. If you are happy with 16-color images, there shouldn't be any difficulty. However, if you want to display images with 256 or more colors, there will be problems on displays that don't support this color depth.
      You might think you could solve the problem by creating the Image List to hold device-independent color information (like with ILC_COLOR24). Unfortunately, this won't work because at some point the image must be rendered on the display—it must become device-dependent. On palettized displays (like 8-bit displays), all device contexts created for drawing have the default 20-color palette selected. The common controls (except the Flat Scroll Bar and Rebar) don't maintain palettes. So when the control draws images on a palettized display, there are only 20 colors available. You will barely recognize those high-color images you worked so hard on!
      Unfortunately, there is no way to get around this. Control Spy does the best it can with the limited colors available when the display is palettized. It includes two versions of its images: a true color set and a 16-color set. The 16-color set is included because the system doesn't do a very good job when it maps 16-million color images down to 16 colors. You could do the same thing in your application.
      Of course, there is another alternative: Custom Draw. If the control supports it, Custom Draw lets you force a control to display its contents based on a palette. The Custom Draw notification (NM_CUSTOMDRAW) includes a NMCUSTOMDRAW structure. This structure informs the parent of the current draw stage and supplies a device context handle that is used by the control for drawing. With this you can select and realize a custom palette into the device context. Custom Draw provides a lot of flexibility with the visual aspects of the controls. Please refer to the Platform SDK documentation for more information about it.
      Load up Image List Control Spy to see firsthand what the Image List control can do. Control Spy creates an Image List for initial use (called imagelist0) and displays all its pictures in the container window. It also defines two bitmaps. The first, bitmap0, is the image that's displayed initially. The second, bitmap1, is stored in a second Image List. Both Image Lists are masked, and the images were added using ImageList_AddMasked.
      To specify one of the images as an overlay image, use ImageList_SetOverlayImage:

 API:
     ImageList_SetOverlayImage(
                               imagelist0, // Handle to Image List
                               8,          // New overlay image
                               1)          // New overlay image ID
 
If the supplied Image Lists are used, this code adds Pluto as an overlay image. Now, let's draw Jupiter and overlay it with our new overlay image:

 API:
     ImageList_Draw(
         imagelist0,            // Handle to Image List
         4,                     // Image number to draw
         dc,                    // Device context handle
         0,                     // X coordinate of DC
         0,                     // Y coordinate of DC
         ILD_TRANSPARENT|       // Style: Draw with transparency
         INDEXTOOVERLAYMASK(1)) // Style: Include overlay 1
      Now, let's try the drag image support. Begin the drag operation using this code:

 API:
     ImageList_BeginDrag(
         imagelist0,             // Handle to Image List
         4,                      // Image to drag
         16,                     // Image drag hotspot (X)
         16)                     // Image drag hotspot (Y)
Next, set the initial position of the drag image within the window you choose. Updates to the window are locked. If you specify NULL for the window, the desktop window will be used. This is the syntax:

 API:
     ImageList_DragEnter(
         NULL,                   // Handle of window for drag image
         100,                    // Initial position of image (X)
         100)                    // Initial position of image (Y)
      See the image on your desktop? Typically, the cursor image is combined with this drag image and the real system mouse cursor is hidden. Use ImageList_SetDragCursorImage to do this. Now, move the image around:

 API:
     ImageList_DragMove(150,200)     // X and Y position to move
Complete the drag operation by unlocking the window:

 API:
     ImageList_DragLeave(NULL)      // Unlock updates to window handle
Finally, end the drag operation:

 API:
     ImageList_EndDrag()            // Finish drag
In an actual program, all the Se steps would have been accomplished in mouse message handlers—the initialization in a mouse button down message, the drag move in the mouse move handler, and the drag end in a mouse up message.
      I wrote Control Spy to gain insight into the common controls. This article introduced Control Spy and used it to explore some of what COMCTL32.DLL has to offer. User interface design is an important aspect of application development and a better understanding of the common controls will not only make development times faster, but will also make for a better experience for users. In my next article, I'll dive deeper into the common controls and cover some advanced functions of Control Spy.

From the July 1998 issue of Microsoft Systems Journal.