[MSDN2001文章]Control Spy Exposes the Clandestine Life of Windows Common Controls, Part II
Control Spy Exposes the Clandestine Life of Windows Common Controls, Part II
Mark Finocchio
The source code for every Control Spy contains customizable functions. The suite includes ready-to-build projects for each control located in the Control Spy Source directory. You can easily create projects for your compiler. Just make sure to link in COMCTL32.LIB.
This article assumes you're familiar with Windows SDK
Code for this article: ControlSpy0998.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.
In the first part of this series of articles about the common controls (see "Control Spy Exposes the Clandestine Life of Windows Common Controls, Part I," MSJ July 1998), I introduced a utility suite called Control Spy, which I wrote to make my job on the Microsoft user interface developer support team easier. Control Spy is comprised of 22 programs, one for each control in COMCTL32.DLL, that let you learn about, test, and experiment with the common controls.
In this article, I will cover the Control Spy features that were not discussed in Part I, and explain how to alter the source code to provide custom notification handlers and add custom code. I will also continue my discussion of the new and updated common controls that were introduced with Microsoft® Internet Explorer 4.0. See http://www.microsoft.com/msdn/downloads/files/40comupd.htm for a quick way to get the common controls without installing Internet Explorer.
Advanced Control Spy
In Part I, I presented basic features of Control Spy such as how to change control styles on the fly and how to use the built-in parser to send any control-specific message to the control at runtime. To review, the parser handles the creation of data structures in memory (if needed), and cleans up afterwards. The result of the sent message (or API call) is displayed in the Return Value box. The parts of Control Spy that remain to be discussed are labeled in Figure 1.
![]() |
You specify a log file either by manually typing a file name in the Log File edit box or by navigating to the file with the Log File Browse button. To start or stop logging, select the Logging button. To erase the contents of the log file, just select the Log File Erase button.
So far, I've explained how to manually send messages to a control, one at a time. But in some situations, you may want to repeatedly send the same group of messages—for example, when testing a modification to a control. You can accomplish this with statement lists.
A statement list is simply a series of message statements listed one after the other in a text file and separated by semicolons. (This is the only situation in which semicolons are allowed.) You can use comments to make a statement list more readable. Comments always start with double slashes (//). When the parser encounters a double slash, it ignores everything that follows, up to the next line.
Figure 2 shows a sample statement list for use with the ComboBoxEx Control Spy. To load a list, use the Statement List Open button. Control Spy will separate all the statements and display them individually. You can move between statements with the scroll arrows, and use the Refresh button to reload the list. You will want to use the Refresh button if you used an editor to alter and save the list that was in use. The Stop button lets you stop using the statement list. If you choose Send while a statement list is open, Control Spy executes the current statement and moves to the next one. Select Send All to send all the statements consecutively from start to finish.
The last area to discuss is how to handle callbacks. Callbacks require source code modification. Let's say you want to run a block of your code while using Control Spy. A source code function called ControlStateCallback facilitates this. First, choose a callback number, then select the Run Callback button. This will call the function ControlStateCallback with the number chosen through the interface as an argument. I've provided a switch statement so you can easily insert code into a case block.
Comments in the function list all the handles you might need for custom code. I've defined global variables to give you access to the handle of the control, application instance, container window, and Control Spy main dialog. The callback function can also be called automatically with two other values: CSPY_STARTUP and CSPY_SHUTDOWN. CSPY_STARTUP occurs after the control has been created. CSPY_SHUTDOWN occurs before the control is destroyed. Controls are created and destroyed when Control Spy starts and closes, and when they are recreated via the Styles dialog.
The source code for every Control Spy contains other customizable functions as well. The suite includes ready-to-build projects for each control located in the Control Spy Source directory. Each project contains three directories: Parser, Resource, and VC5Workspace. The Parser and Resource directories contain what you'd expect. The VC5Workspace directory contains the project files for Visual C++® 5.0. If you don't have Visual C++ 5.0, you can easily create projects for your compiler. The required source files are <ControlName>.c, Parser"lex_yy.c, and Parser"ytab.c. You must also include the resource file Resource"<ControlName>.rc. Finally, make sure to link in COMCTL32.LIB.
If you build the project yourself, some warnings might be generated for lex_yy.c and ytab.c when you compile. This is because I didn't create these files directly (more on this later). They are harmless warnings that you can safely ignore, or you can eliminate them by turning down the warning level for these files.
The file with all the customizable functions is <ControlName>.c. These functions and some global variable definitions are located at the beginning of the file. These variables usually can be referenced through the parser and are available for you to customize.
Image Lists are a good example of this. The first function in the file is InitialControlStyles. This lets you set the automatic startup styles for the control without having to use the Styles dialog. Next is a function that I've already mentioned: ControlStateCallback. Following that is the function ProcessControlNotification, which provides a place for your control notification handlers (the handlers called in response to WM_NOTIFY messages). The source already contains a switch statement and all the necessary pointer initialization and typecasting. Of course, it also has access to all the handles you might need for your code.
The next function is ProcessControlCommand. Some of the controls still send commands (WM_COMMAND messages) to their parents. This function provides a place to handle those commands. In a few cases, a parent is notified of child events with standard Windows® messages. Control Spy defines ProcessWindowsMessage in those circumstances.
The last customizable function, RegisterControl, demonstrates how the control is registered and contains a single call to InitCommon–ControlsEx. Most of these functions and global variables have already been populated and initialized. If you want to initialize the control differently, add different data, or handle notifications differently, just change the source code.
Some of you may be wondering how Control Spy works under the surface—how flexible the parser really is. In general, the parser is only as functional as it needs to be to send all the messages to a particular control. Every Control Spy has a different implementation of the parser that understands only the constants that the control uses. Each defines only those keywords needed for the messages of the control. The keywords are usually mapped to internal global variables.
The parser syntax is faintly similar to C (like the logical-OR operator), but it only supports what it needs. You can't use any other bitwise operators. Also, the parser recognizes many more types than are defined by C. For example, the parser defines TRUE as a boolean type and "Spy" as a string type.
The parser provides strong type checking. If you try to use a number in an argument where a string is expected, a syntax error results. I wrote it this way because if Control Spy forced the user to use the messages correctly, it would provide a better learning experience.
When the actual argument is within a structure, the parser uses weak type checking. For example, say you want to set the text color of a Tree View control to light blue. You would use:
MSG (TVM_SETTEXTCOLOR, 0, RGB(0,0,255))
Strong type checking prevents you from doing this:
MSG (TVM_SETTEXTCOLOR, 0, RGB(0,0,"Blue")) Syntax error ("Blue")!
However, weak type checking at the argument level lets you do this:
MSG (TVM_SETTEXTCOLOR, 0, "Blue") Not a syntax error.
This last statement doesn't make sense, but it doesn't produce a syntax error.
If a control expects a string type as an argument, it's all right to pass it a NULL, if needed. However, if a structure expects a string, it's not okay to substitute a NULL unless it is documented as an appropriate value for the structure member.
A good example of this is text callbacks. For some controls, you can substitute a LPSTR_TEXTCALLBACK value in place of a string. The parser would support this case. Since you can do things like passing NULL to controls, it is entirely possible to crash Control Spy if the message causes an illegal operation.
You might be curious about the files I mentioned earlier—lex_yy.c and ytab.c—that weren't created by me directly. These files comprise the parser. lex_yy.c is the lexical analyzer that breaks down input into tokens and passes them on to the parser. I used the compiler generation programs LEX and YACC to create the Control Spy interpreter. That's why they produce warnings if the warning level is too high. Again, the warnings are harmless.
Now you are an expert at using Control Spy and can use its full potential for your benefit! Next, I will continue the discussion of common controls in the context of Control Spy. In the last article, I covered two of the seven new common controls that were released with Internet Explorer 4.0. Again, the controls I refer to as "new" are those that were technically in preview with Internet Explorer 3.0.
Flat Scroll Bar
Flat scroll bars are enhanced standard scroll bars with different bevel types and colors (see Figure 3). They even support palettes. The Flat Scroll Bar control is strictly API-based. It uses functions rather than responding to messages. To use Flat Scroll Bars on a window, the window's handle must be passed to InitializeFlatSB. You don't need to specify the standard scroll bar styles (WS_HSCROLL and WS_VSCROLL) for the window.![]() |
API:
UnintializeFlatSB(window)
Then, try setting the horizontal scroll position using a Flat Scroll Bar API:
API:
FlatSB_SetScrollPos(window,SB_HORZ,0,TRUE)
The call was diverted to the normal scroll bar function, SetScrollPos. Now, start using flat scroll bars again:
API:
InitializeFlatSB(window)
The Flat Scroll Bar control includes functions to change its appearance. The following code changes
the horizontal scroll bar's appearance to Encarta mode and specifies a blue background:
API:
FlatSB_SetScrollProp(window,WSB_PROP_HBKGCOLOR,RGB(0,0,128),TRUE)
API:
FlatSB_SetScrollProp(window,WSB_PROP_HSTYLE,FSB_ENCARTA_MODE,TRUE)
If you are adding custom notification handlers, keep in mind that a special ProcessWindowsMessage
function has been added for Flat Scroll Bar's notifications in Control Spy. For more coloring options, check
on the control's palette support. The source code defines a handle that can be used for custom palettes.
IP Address
MSG (IPM_SETADDRESS,
0, // Unused parameter
MAKEIPADDRESS(198,105,232,37))// Address to set
|
| Then, get the current address by supplying a pointer to a DWORD buffer: |
MSG (IPM_GETADDRESS,
0, // Unused parameter
ipbuffer) // Pointer to integer buffer
|
| Observe the output in the Return Value box. Control Spy uses the IP Address field-extraction macros to get the values—for example, FIRST_IPADDRESS. |
MSG (IPM_SETRANGE,
1, // Limit range of Field 1
MAKEIPRANGE(100,150)) // Range is 100 to 150
|
|
Month Calendar
|
MSG (MCM_SETRANGE,
GDTR_MIN|GDTR_MAX, // Set min and max date values
systimearr( // Pointer to SYSTEMTIME array
// Starting date is 7/1/1972
SYSTEMTIME(1972,7,0,1,0,0,0,0),
// Ending date is 3/10/1976
SYSTEMTIME(1976,3,0,10,0,0,0,0)))
|
| The MCM_SETTODAY message is used to change the today date: |
MSG (MCM_SETTODAY,
0, // wParam is unused
// Set today to January 1, 2000
SYSTEMTIME(2000,1,0,1,0,0,0,0))
|
| Instead of handling notifications, set the bold state of the calendar days (make sure the MCS_ DAYSTATE style is in use): |
MSG (MCM_SETDAYSTATE,
3, // Using 3 element array
mdsarr( // MONTHDAYSTATE array pointer
BOLDDAY(31), // Bold day 31 of last month
BOLDDAY(15)|BOLDDAY(16), // Bold day 15 and 16
BOLDDAY(1))) // Bold day 1 of next month
|
| The size of the actual area that's used to display the calendar depends on the font used. To determine the rectangle's size, use the following: |
MSG (MCM_GETMINREQRECT,
0, // wParam is unused
RECT) // Pointer to RECT
|
MSG (MCM_HITTEST,
0, // wParam is unused
// Pointer to part initialize MCHITTESTINFO
MCHITTESTINFO(
mchtisize, // Size of MCHITTESTINFO
POINT(45,50))) // What's at (45,50)?
|
| The return value is one of many values that specify where the point is on the control—for example, the scroll next month button.
List View
|
![]() |
MSG (LVM_SETITEM,
0, // wParam is unused
LVITEM( // Pointer to LVITEM structure
LVIF_TEXT // Mask: Text member is valid
|LVIF_STATE // Mask: State member is valid
|LVIF_IMAGE, // Mask: Image member is valid
// Set properties item with index 1
1,
0, // No subitem
LVIS_SELECTED/0/0, // Selected state flag
// Don't forget the state mask!
LVIS_SELECTED,
// Text label is callback based
LPSTR_TEXTCALLBACK,
// Text buffer size doesn't apply
0,
// Image to display is callback based
I_IMAGECALLBACK,
// User data and indent (not in mask)
0,0))
|
MSG (LVM_SETWORKAREAS,
1, // Number of valid RECTs in our array
rectarr( // Pointer to RECT array (2 supplied)
// Working area boundaries
RECT(40,40,500,500),
// Second supplied RECT not used
RECT(0,0,0,0)))
|
| Nothing happens until you use LVM_ ARRANGE. After that, items are arranged in an area where they wouldn't normally be arranged.
Progress Bar
|
MSG (PBM_SETRANGE32, -125, 125)
|
| The range is set between -125 and 125. Now, set the position in the middle: |
MSG (PBM_SETPOS, 0, 0)
|
|
Property Sheet
|
![]() |
MSG (PSM_CANCELTOCLOSE, 0, 0)
|
| The Cancel button is disabled and the OK button now displays Close. Now, set the title: |
MSG (PSM_SETTITLE, 0, "My Property Sheet")
|
|
Status Bar
|
MSG (SB_SETPARTS,
// Use 2 element array
2,
// Part right edge positions
INTARR { 100,185,0,0,0,0,0,0 })
|
| Control Spy automatically allocates enough room for an 8-element array, and initializes it using the syntax shown above. This example only uses two of the elements. Each element defines the right edge of the part. If an element used -1, the right edge of the part would extend to the right edge of the control. To set the icon and text, try this message: |
MSG (SB_SETTEXT, 1, "My Text!")
|
| Control Spy defines two icons internally for use: |
MSG (SB_SETICON, 1, Icon0)
|
|
Tab
|
|
|
|
|
MSG (TCM_INSERTITEM,
6, // Insert at index position 6
TCITEM( // Pointer to TCITEM structure
TCIF_IMAGE // Mask: Image member is valid
|TCIF_TEXT, // Mask: Text member is valid
0,0, // State members not used
// (unused, not in mask)
"My Tab", // Label to display
0, // No need for buffer size
-1, // Don't use an image
0)) // User defined data (unused,
// not in mask)
|
| A new tab will be inserted at the current display location of index 6. |
MSG (TCM_HITTEST,
0, // wParam is unused
// Pointer to TCHITTESTINFO structure
TCHITTESTINFO(
POINT(10,15), // Point for hit test
0)) // Hit location will be
// filled by control
|
|
The control will report where the hit occurred.
To Be Continued...
From the September 1998 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.
|
|
For related information see: The Rebar Control: Using a Coolbar in Your Application, http://premium.microsoft.com/msdn/library/techart/msdn_rebar.htm. Also check http://www.microsoft.com/msdn for daily updates on developer programs, resources and events. |






浙公网安备 33010602011771号