July 1998
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 |
The Control Spy Story
The Control Spy Primer
|
Figure 2 Control Styles Dialog |
|
Figure 3 Information Dialog |
|
|
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 |
|
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: |
|
You can also use the parser to change styles with the following syntax: |
|
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: |
|
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: |
|
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: |
|
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
New Common Controls
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: |
|
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. |
|
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
|
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 completenot 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: |
|
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: |
|
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
Animation
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
|
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 insertionthe 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: |
|
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: |
|
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
|
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" imagesimages 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 imagesfor 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 displayit 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: |
|
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: |
|
Now, let's try the drag image support. Begin the drag operation using this code: |
|
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: |
|
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: |
|
Complete the drag operation by unlocking the window: |
|
Finally, end the drag operation: |
|
In an actual program, all the Se steps would have been accomplished in mouse message handlersthe 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.
|