September 1998
Control Spy Exposes the Clandestine Life of Windows Common Controls, Part II
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.
|
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://msdn.microsoft.com/downloads/files/40comupd.htm for a quick way to get the common controls without installing Internet Explorer.
Advanced Control Spy
|
Figure 1 Control Spy |
|
|
Strong type checking prevents you from doing this: |
|
However, weak type checking at the argument level lets you do this: |
|
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 earlierlex_yy.c and ytab.cthat weren't created by me directly. the Se files comprise the parser. lex_yy.c is the lexical analyzer that breaks down input into tokens and passes the M 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
|
Figure 3 Flat Scroll Bars |
|
|
Then, try setting the horizontal scroll position using a Flat Scroll Bar API: |
|
The call was diverted to the normal scroll bar function, SetScrollPos. Now, start using flat scroll bars again: |
|
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: |
|
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
|
Figure 4 IP Address Control |
IP Address is a rather simple control. It only supports a handful of messages. Despite its simplicity, it does have a flaw. Try typing an alphabetic character in the field; it can't be done. This makes sense since an IP address doesn't contain alphabetic characters. Copy some text from somewhere else and paste it into the control. Now you can see the problem; characters can be pasted in the control. If you retrieve the value of the control, the field with alphabetic characters resolves to zero. To get around this problem, you must subclass the control and trap paste operations.
The value of the control is set and retrieved with a single 32-bit number, and the MAKEIPADDRESS macro makes it easier to create this number. Control Spy supports this macro. Run the IP Address Control Spy and try setting the control: |
|
Then, get the current address by supplying a pointer to a DWORD buffer:
|
|
Observe the Output in the Return Value box. Control Spy uses the IP Address field-extraction macros to get the valuesfor example, FIRST_IPADDRESS.
You might need to limit the possible values by a range of IP addresses. This is done on a per-field basis. Say you want to limit the first field's range to the numbers between 100 and 150. Since fields are zero-based, the following message would be used: |
|
Finally, keep in mind that you can use WM_SETFONT if you don't like the initial font chosen by the control (stock fonts are always readily available). Other controls also support this message. Check the Information section of the control to see if it looks at the WM_SETFONT message.
Month Calendar
The Today section can be turned off with the MCS_NOTODAY style. Further, to remove the Today circle, use MCS_NOTODAYCIRCLE. For multiple date selections, make sure to use the MCS_MULTISELECT style. Day State notifications are generated when you use the MCS_DAYSTATE style. A day displayed by the control can have one of two states: bold and not bold. MCN_GETDAYSTATE is sent by the control to ask what days it should display in bold. A month is represented by a value of type MONTHDAYSTATE (which is a fancy name for a DWORD). Each bit in the value represents a day. The BOLDDAY macro can be used to do the work of shifting the bits. Control Spy supports this macro, although with slight differences. It returns a value in Control Spy rather than updating a supplied variable. Thus, Control Spy's implementation takes one value (the day to make bold), not two. Portions of the previous and next months are usually displayed along with the current month, and the Se are usually a different color. So, at most, you will need an array of three MONTHDAYSTATEs to describe everything that is to be displayed in bold. To determine how many months are currently shown, use MCM_GETMONTHRANGE. To specify what days to make bold without providing a notification handler, use the MCM_SETDAYSTATE message. The Month Calendar control has a few quirks you will need to look out for. If there is no date range selected, the range of years differs when month scroll buttons are used rather than the year spin box. The year selection always stops at 1753 if it is being changed with the year spin box. To get around this problem, set a date range (I'll explain later). Another problem exists with the popup listbox that is used to select the current month. If the date range used spans less than a year, the month listbox will display all the months of the year as selectable. Also, there is no way to change the circle that's drawn around the Today date. Finally, you might think that the array of MONTHDAYSTATEs used in response to MCN_GETDAYSTATE would be allocated by the application. This is not the case; the array is supplied by the control. To familiarize yourself with the control, load up the Month Calendar Control Spy. Try setting the date range using MCM_SETRANGE: |
|
The MCM_SETTODAY message is used to change the today date:
|
|
Instead of handling notifications, set the bold state of the calendar days (make sure the MCS_
DAYSTATE style is in use):
|
|
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:
|
|
There are not many messages and styles for customizing the Month Calendar control interface. But if you really want something else to happen when the user clicks on some part of the control, you can use subclassing and hit testing. Hit testing is done via the MCM_HITTEST message:
|
|
The return value is one of many values that specify where the point is on the controlfor example, the scroll next month button.
Finally, there are messages available for changing the colors of the control. Use MCM_SETCOLOR and MCM_ GETCOLOR for this. You can make color changes in different sections, and you can also use WM_SETFONT to make font changes. Now I'll continue my discussion of the updated common controls. Again, the Se updates come in the form of additional messages, notifications, styles, and structure members. Some controls have only minor changes, while others have undergone substantial revision.
List View
|
Figure 6 The List View |
The List View has quite a few messages. Several item-oriented messages make use of the LVITEM structure. the Se messages include LVM_INSERTITEM, LVM_SETITEM, LVM_GETITEM, LVM_SETITEMTEXT, and LVM_SETITEMSTATE. The state and text of an item can be changed with LVM_SETITEM, but it's more convenient to do this with the LVM_
SETITEMTEXT and LVM_SETITEM-STATE messages.
You don't need to set the mask member of LVITEM with the Se messages to specify valid data. Also, when changing an item you specify its index in one of the parameters of the message, not within the LVITEM structure. When dealing with states, don't forget that the state member of LVITEM itself has a mask. If stateMask isn't set, all the state bits will be ignored. There are other sets of messages for report-mode columns, cursors, item position and spacing, color, and sorting. An item's index changes when items are added and deleted and is not dependent on screen position. If you need to identify items uniquely, use the user-definable member (lParam) of the LVITEM structure. The List View control has been updated in many ways. It now supports extended styles set via LVM_SETEXTENDEDLISTVIEWSTYLE. The new styles include full-row selection in report mode, one-click activation, images for subitems in report mode, hot underlining, and track selection ("hover" selection) of items. There are also extended styles to enable item checkboxes, use flat scroll bars, display gridlines, and generate tooltip text callback notifications. In report mode, the control uses a Header control for the column labels. Columns are maintained by the List View through the LVCOLUMN structure. This structure has been updated to accommodate the new features of the Header control: item images and column ordering. Messages specific to List View also have been added for column ordering: LVM_SETCOLUMNORDERARRAY and LVM_ GETCOLUMNORDERARRAY. Another new feature of the List View is the concept of "working areas"rectangles defined within the control. While in icon or small icon mode, items are restricted to working areas. If an item is moved outside a working area, it is placed in the first working area (that is, the area with the index of 0). To make an item a member of another working area, use LVM_SETITEMPOSITION to move it to the new area. To create working areas, use LVM_SETWORKAREAS. This message takes a pointer to an array of rectangle structures whose coordinates are relative to the List View's client area. To restore the List View to its normal state, remove all working areas by using NULL for the array pointer. Working areas let you group items with something in common. You can force items to be displayed in specific regions of the control, and you can display scroll bars when they ordinarily wouldn't be displayed. The actual scrollable area of the control depends on item placement, not the size and position of the working areas. No matter how the working areas are arranged, the control will be scrollable so that all items can be reached. To determine if an item exists in a working area, you must compare the item's position with all the areas. If you don't already know the Se area boundaries, use the messages LVM_GETNUMBEROFWORKAREAS and LVM_ GETWORKAREAS to get this information. In virtual mode, a List View can display millions of items without a performance hit. Virtual List Views use text and image callbacks. With normal callbacks, the List View maintains information about its items. In virtual mode, the control maintains a minimum amount of information and no item-specific information. To place the control in virtual mode, use the LVS_ OWNERDATA style. When the control needs text and image information, it generates the LVN_GETDISPINFO notification. To optimize performance, the List View uses the LVN_OCACHEHINT notification to inform the application about index values that are good to have loaded and ready to go. Items can be added and removed in a virtual List View with LVM_INSERTITEM and LVM_DELETEITEM, respectively. The effect is no different than setting the current item count with LVM_SETITEMCOUNT. The virtual List View keeps track of the total number of items and their display; it's up to the application to give the Se items meaning based on the index. The virtual List View is a great way to present large amounts of data without sacrificing performance, but it does have some limitations. Some styles and messages are either partially supported or not supported at all while in virtual mode. Items can't be sorted while in virtual mode. the Only item state attributes maintained by the control are selection and focus (which isn't per item). LVM_SETITEM is not supported, so the Only other option is LVM_ SETITEMSTATE. LVM_GETNEXTITEM is limited, and LVM_SETITEMPOSITION isn't supported. List View has a few problems worth mentioning. When you are using a virtual List View, you must supply the text displayed for items. If you use a label so long that it runs into another item, the control will clip the text incorrectly. The result is icons that display overlapping parts of their text labels. Another problem involves the use of LVM_ GETNEXTITEM when there is only one item present in the control and nothing is selected. If the LVNI_ABOVE flag is used to search above an item, or the LVNI_SELECTED flag is used to find the next selected item, the application will lock. Make sure not to use LVM_GETNEXTITEM with the Se flags if there is a single unselected item in the list. Another problem to look out for concerns the LVS_EX_ CHECKBOXES style, which adds checkboxes next to each item. Checkboxes are automatically implemented with a state Image List. While in icon mode, the checkboxes can be toggled only if they are clicked on the upper-left area of the corresponding box. The last problem concerns setting the control's background color with LVM_SETBKCOLOR. The background color is not set correctly in report mode; only the color beneath the images is changed. You can use custom draw to get around this problem. With all this information in mind, let's try out a few things with List View Control Spy. First, let's set the attributes of an item. The LVITEM structure is important to understandit is used with LVM_SETITEM. To see the effects of the following statement, make sure the LVS_ SHOWSELALWAYS style is on in the Styles dialog. |
|
After this message is sent, the text and image display
of the second item in the List View will be callback-based, and the item will be selected. Notice the syntax for the
state member. Since the state variable holds the item's state, state image, and overlay image, Control Spy defines a syntax with slashes so you can input the values. Internally, Control Spy shifts the bits to create the Overall
value automatically.
Now try a working area: |
|
Nothing happens until you use LVM_
ARRANGE. After that, items are arranged in an area where they wouldn't normally be arranged.
Some messages (like this one) expect variable-length arrays as parameters. It's possible to have more than two working areasin fact, you can have up to LV_MAX_WORKAREAS. Control Spy defines an array of two RECT structures for this use. If you specify that the number of working areas is 1 (for the wParam), Control Spy ignores the second RECT structure. Finally, try putting the List View in virtual mode. Select Styles to view all the available styles for the control, then select LVS_OWNERDRAW and press Recreate Control. You must recreate the control since you cannot switch in and out of virtual mode dynamically. Once in virtual mode, notice that all the images and labels specify "callback." This is due to Control Spy's default handling of LVN_GETDISPINFO. Look at the List View Control Spy source code to see how to implement one of the Se handlers.
Progress Bar
|
Figure 7 Progress Bar |
Progress Bars have been updated to support vertical bars (PBS_VERTICAL) and a new smooth look (PBS_SMOOTH). The new PBM_SETRANGE32 provides 32-bit range support. The colors of the Progress Bar can be changed with PBM_SETBARCOLOR and PBM_SETBKCOLOR.
Load up Progress Bar Control Spy and try the Se messages to get familiar with the control: |
|
The range is set between -125 and 125. Now, set the position in the middle:
|
|
Property Sheet
The Property Sheet control (see Figure 8) presents information to the user in tabbed form. It's convenient because it uses standard dialog templates for the contents of each tab, and standard dialog callbacks. Property Sheets are used all over the Windows shell. When you view the properties of any object (for example, a file), the information is usually displayed in a Property Sheet. Property Sheets are also the basis of the "wizards" used by many applications. |
Figure 8 Property Sheet |
Programming with Property Sheets involves a mix of functions and messages. Property Sheets contain pages, and are
created with the PropertySheet API or
with CreatePropertySheetPage. Each page
makes up one tab in the sheet. The PSM_
ADDPAGE message is used to add pages if they were created with CreatePropertySheetPage. It takes a page handle, which is returned by the function. Property Sheets usually exist as modal dialog boxes, but they can also exist as modeless dialogs.
Before you can create a Property Sheet, you must fill out a PROPSHEEthe ADER structure. This structure defines all the Options available for creating the sheet. This is where you supply the title, pick what buttons to display, turn on the wizard mode, specify whether it's modeless (see the dwFlags member), specify what icons to display (see hIcon), and so on. You can even supply a watermark bitmap for display in the background (see hbmWatermark). Another important part of this structure is the ppsp member, which points to an array of PROPSHEETPAGE structures. Synonymous with PROPSHEEthe ADER, PROPSHEETPAGE specifies all the attributes of Property Sheet pages. Make sure to have this structure member point to at least one PROPSHEETPAGE structure. Before and after a Property Sheet and its pages are created, the application is informed of events with callbacks. You specify callback functions in the previously mentioned structures. Your Property Sheet callback (PropSheetProc) gets called before the Property Sheet is created and when it's being initialized. The pre-creation callback (when PSCB_PRECREATE is passed) gives you the Opportunity to alter the creation styles and position of the sheet. Property Sheet page callbacks (PropSheetPageProc) are called when pages are about to be created and destroyed. You can prevent pages from being created by handling the PSPCB_CREATE case of the callback. Property Sheet pages are similar to dialog boxes in that you must use dialog box callback procedures to handle messages coming from the page. Dialog box procedures are called in response to messages, much like standard Windows procedures. The difference is that the return value specifies whether the message was handled. The return value isn't sent back to the caller. To return a value from a handler, use SetWindowLong and the DWL_MSGRESULT index. Looking through the Property Sheet Control Spy source code, you can see that Property Sheets take more code to implement than the Other controls. This is because every page requires a dialog procedure and perhaps a callback. Once a Property Sheet is up and running, several messages are available for interacting with the control. As stated above, PSM_ADDPAGE adds a page to the control. To remove a page, use PSM_REMOVEPAGE. Pages are identified by an index or handle. A page's index changes as pages are added and removed. Handles are unique. Removing a page removes it from memory. PSM_REMOVEPAGE causes DestroyPropertySheetPage to be called. DestroyPropertySheetPage is available for your own use if the page wasn't initially added using PropertySheet. Various messages let you manipulate the buttons on the sheet. To programmatically select buttons, use PSM_APPLY and PSM_PRESSBUTTON. The PSM_CANCELTOCLOSE, PSM_SETFINISHTEXT, and PSM_SETTITLE messages are used to set text on different parts of the sheet. PSM_CHANGED and PSM_UNCHANGED enable and disable the Apply button, respectively. PSM_REBOOTSYSTEM and PSM_RESTARTWINDOWS don't actually reboot and restart the system. the Se messages cause special values (ID_PSREBOOTSYSTEM and ID_PSRESTARTWINDOWS) to be returned from PropertySheet when the sheet is modal. It's up to the application to reboot or restart. Activate a page with PSM_SETCURSEL, or use PSM_ SETCURSELID to activate based on a resource identifier. PSM_ISDIALOGMESSAGE passes messages to modeless Property Sheets from the application's message loop. This message does more internally to support Property Sheets than just call IsDialogMessage, which normally is used to handle dialog input. Keep this in mind when building message loops. Notifications of user interaction with the sheet are sent to the individual page dialog procedures, not the sheet itself. Depending on the notification and the response to the notification, some or all of the page procedures will be notified. Some notifications are sent when buttons are pressedfor example, PSN_APPLY, PSN_HELP, PSN_ QUERYCANCEL, and PSN_RESET. Others are specific to sheets in wizard mode, like PSN_WIZBACK, PSN_WIZNEXT, and PSN_WIZFINISH. You can stop operations like Apply and Cancel by returning the appropriate value. For example, pages will receive PSN_APPLY in index order as long as no page returns PSNRET_INVALID_NOCHANGEPAGE to stop the process. Control Spy provides default handling of all the notifications to keep the Property Sheet from closing. As a result, you won't ever see notifications like PSN_RESET, which is sent when the sheet is about to be destroyed. Property Sheets have been updated slightly. A new notification, PSN_GETOBJECT, has been added to allow a page to be an OLE drop target. The PROPSHEEthe ADER structure has been modified to include members for watermarks. Additionally, PROPSHEETPAGE has been updated with members for displaying headers and subtitles. When you use Property Sheets, make sure to look out for a few things. When a Property Sheet is created and the first page is displayed, any other pages in the sheet do not yet exist. Only when the user selects another page for the first time will it be created. Another potential problem area is Property Sheet page handles, which are used to identify pages. the Se handles are returned from CreatePropertySheetPage calls. Handles for pages created indirectly via a PropertySheet call can't be retrieved. One final tip: if you need access to the underlying Tab control used by the Property Sheet, you can use the PSM_GETTABCONTROL notification. Try the following messages in Property Sheet Control Spy to get more familiar with the control: |
|
The Cancel button is disabled and the OK button now displays Close. Now, set the title:
|
|
Status Bar
Most of the applications you interact with every day include Status Bar controls (see Figure 9). Status Bars are divided into parts that can be used to display text and icons. The Status Bar controls support the set of shared common control styles prefixed with CCS. Both CreateWindowEx and CreateStatusWindow can be used for the control's creation. CreateStatusWindow is provided as a convenience. It doesn't accept control dimensions since they are ignored (as long as the CCS_NORESIZE style isn't used). When in simple mode, the control's multiple panes are replaced with one long pane. This mode is usually used for temporarily displaying information such as file loads and saves. |
Figure 9 Status Bar Control |
Status Bar parts are defined by an array that is sent to the control with the SB_SETPARTS message which I'll explain shortly. To set the text that is displayed in a part, use SB_SETTEXT. This message also includes drawing options for the border displayed around the text (such as SBT_POPOUT). Use SB_SIMPLE to move in and out of simple mode. Make sure to use 255 as the part index if you are displaying text while in simple mode; otherwise, it won't display.
You can retrieve information about the text displayed in a part with SB_GETTEXT and SB_GETTEXTLENGTH. The DrawStatusText function supplies text drawing options. You can use this function with a handle to a device context (for example, the Status Bar's) to display text anywhere and with any of the border styles. You can obtain the location of parts with SB_GETRECT. Parts are referenced by their index; index 0 is always the leftmost part. Status Bars have been updated in several ways. Icons are now supported. To assign an icon to a part, use SB_SETICON. Tooltips are now supported with the SBT_TOOLTIPS style. You can set and retrieve the tooltip text associated with a part by using SB_SETTIPTEXT and SB_GETTIPTEXT. Tooltips will not be displayed if all the text can be seen in the part, or if the control is in simple mode. SB_ISSIMPLE has been added so you can find out if the control is in simple mode. The notification SBN_SIMPLEMODECHANGE has been added as well. Finally, to change the background color of the control, use SB_SETBKCOLOR. To demonstrate the control, let's use Control Spy to set the control's parts, text, and icons. Load up Status Bar Control Spy and try the following message: |
|
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:
|
|
Control Spy defines two icons internally for use:
|
|
Tab
Tab controls (see Figure 10 ) provide an interface for defining multiple pages within the area of the control. A very common use for Tab controls is within Property Sheets, which use the M to define their pages. The Tab control is also used in an unexpected place: the Windows taskbar. Tab controls support the CCS styles. A Tab control item is an individual tab. Although it may appear otherwise, a Tab control is only one window; items are redrawn when they are selected and given focus. |
Figure 10 Tab Control |
An item's properties are defined in the TCITEM structure, and TCM_INSERTITEM, TCM_SETITEM, and TCM_
GETITEM make use of it. Images are assigned to the Tab control though an Image List. This Image List is associated with the control through TCM_SETIMAGELIST. Various messages are available to query the control for information on its items. the Se messages include TCM_GETITEMCOUNT, TCM_GETITEMRECT, TCM_GETROWCOUNT, TCM_GETCURFOCUS, and TCM_GETCURSEL.
Selecting an item in a Tab control causes it to gain focus. So why are there separate messages for getting the focused and selected item? The Tab control button style, TCS_BUTTONS, makes tabs appear as pushbuttons. Different messages and styles let you set the way tabs are displayed. TCS_FIXEDWIDTH forces all the tabs to be displayed with the same width. To force tabs to fill entire rows, use TCS_RIGHTJUSTIFY. TCS_SINGLELINE and TCS_MULTILINE determine whether multiple rows are used. You can also change the look of the tabs with TCM_SETMINTABWIDTH, TCM_SETPADDING, and TCM_SETITEMSIZE. As I stated earlier, Property Sheets use Tab controls. Property Sheets create child dialog boxes from your templates as siblings of its child Tab control. The Tab control's display area (the area of the control not occupied by tabs) must be sized to accommodate the Se dialog boxes. To query the control for display and window size information, use TCM_ADJUSTRECT. The Tab control now supports extended styles with the TCM_SETEXTENDEDSTYLE message. Two extended styles are available: TCS_EX_FLATSEPARATORS (draw separators between flat buttons while in button mode), and TCS_EX_REGISTERDROP (activate drop target request notifications). Several new regular styles have been added to the control. To display tabs at the bottom of the control, use TCS_BOTTOM. While in button mode, TCS_FLATBUTTONS cause the buttons to be drawn with the new flat look. TCS_MULTISELECT allows for multiple button selection. You can automatically highlight items under the cursor with TCS_HOTTRACK. TCS_RIGHT and TCS_VERTICAL have been added to support vertical tab display. To display tabs on opposite sides of the control, use TCS_ SCROLLOPPOSITE. Finally, tab items can now have states: TCIS_BUTTONPRESSED and TCIS_HIGHLIGHTED. Some Tab control problems are worth noting. If the control is disabled, nothing is grayed out. Also, do not use & characters in item labelsthey cause underlines to show under the adjacent letter (like accelerator keys). This may be what you want, but underlines don't show if the control is vertical. Finally, if you need to remove images from the associated Image List dynamically, don't get a handle to the underlying Image List and delete it using Image List functions. Instead use TCM_REMOVEIMAGE. This allows each tab item to retain its image, even though Image List indexes are changing. When you insert tabs, an index is required to place the new tab. Keep in mind that indexes are not dependent on display position. Also, indexes change as tabs are inserted and removed. Run Tab Control Spy and try the following message: |
|
A new tab will be inserted at the current display location
of index 6.
The hit testing feature is useful if more control is required over the Tab control: |
|
The control will report where the hit occurred.
For more insight on how the control works, try out some of the styles. TCS_TOOLTIPS enables tooltip support. Notice the TTN_NEEDTEXT notifications; the parent will receive the M for requested text. Try out TCS_BUTTONS as well. Look familiar?
To Be Continued...
|
From the September 1998 issue of Microsoft Systems Journal.