Vinod is a program manager for Microsoft Office. He can be reached at One Microsoft Way, Redmond, WA 98052.
Windows 95 includes several GUI building blocks, collectively called "common controls." These controls are reusable, pretested, standardized software components ("objects") for constructing GUIs. In fact, the Windows 95 user interface itself uses these controls extensively, primarily in the Explorer (an application that groups together the functionality of the Windows 3.1 "managers"--File Manager, Program Manager, Control Panel, and Print Manager).
Common controls are a set of standard GUI components supported by the common-control DLL included with Windows 95. Each common control is a child window that an application uses in conjunction with other windows to perform input and output tasks. Many of these controls--toolbars, tree controls, spin boxes, and progress indicators--have been used in applications for some time.
The common-control DLL defines window classes for common controls. The window class and corresponding window procedure for each control determine its appearance, properties, and functionality. To ensure that the common-control DLL is loaded, you need to call InitCommonControls. You typically create a common control by specifying the name of the window class in a call to CreateWindow or CreateWindowEx.
Because common controls are windows, an application can manipulate them using standard Windows messages, such as WM_SETTEXT or WM_GETFONT. In addition, the window class of each common control supports a set of control messages that an application can use to manipulate the control. An application can use any of the message sending or posting functions to pass messages to the control. Some common controls have a set of macros that an application can use instead of the sending or posting functions.
Common controls send notification messages to the parent window when events (user input, for example) occur in the control. The application relies on these notification messages to determine what action the user wants it to take. Most common controls send notification messages in the form of WM_NOTIFY messages. Figure 1 illustrates the interaction between the application window and controls.
Typically, the lParam parameter of the WM_NOTIFY message is the address of an NMHDR structure; see Example 1. The structure contains a notification code and identifies the common control that sent the notification message. Each common control has a corresponding set of notification codes. The common-control library also provides notification codes that can be sent by more than one type of common control. Table 1 describes the shared codes.
Image lists are bitmap managers coupled with APIs. They provide a means of managing and drawing these bitmaps, allowing the app to get rid of a lot of memory device contexts (DCs), bitmap handles, and possibly BitBlt code. Image lists have complex drawing mechanisms that can easily handle tedious graphics operations such as drawing transparent bitmaps with masks. The graphical elements for implementing drag-and-drop are provided.
The internal means of storage used by the image list is a bitmap, and each image is simply a portion of a bitmap. The image list always stores individual images in one row.
Image lists are either nonmasked or masked. Nonmasked image lists consist of a color bitmap that contains one or more images. Masked image lists consist of two bitmaps of equal size: a color bitmap containing the images and a monochrome bitmap containing a mask for each image in the first bitmap. When a nonmasked image is drawn, it is copied into the target DC. When a masked image is drawn, the bits of the image are combined with the bits of the mask, typically to produce transparent areas in the bitmap where the background color of the target DC shows through. You can specify several drawing styles when drawing a masked image (for example, the image can be dithered to indicate a selected object).
A toolbar control is a control window that contains one or more buttons; see Figure 2(a). Typically, the buttons in a toolbar are made to correspond to items in the application's menu, providing an additional, more-direct way for the user to access an application's commands. Windows 95 provides an optional toolbar control in all its Explorer windows.
The toolbar control supports docking and windowing functionality. In the docked state, toolbars are aligned to an edge of the parent window. In a windowed state, toolbars display the controls in a sizable palette window that includes a title bar so users can move the control bar around.
Toolbar controls have built-in customization features, including a system-defined customization dialog box that lets users insert, delete, or rearrange toolbar buttons. An app determines whether the customization features are available to users and controls the extent to which the user may customize the toolbar.
The CreateToolbarEx function creates a toolbar control and adds an initial set of buttons to it. You can also create a toolbar control using CreateWindow or CreateWindowEx by specifying the toolbar-control window class. This method creates a toolbar that initially contains no buttons. You add buttons by using the TB_ADDBUTTONS or TB_INSERTBUTTON message.
The window procedure for a toolbar control automatically sets the size and position of the toolbar window. The height is based on the height of the buttons in the toolbar. The width is the same as the width of the parent window's client area. The CCS_TOP and CCS_BOTTOM styles determine whether the toolbar lies along the top or bottom of the client area; CCS_TOP is the default.
The toolbar window procedure automatically adjusts the size of the toolbar control whenever it receives a WM_SIZE or TB_AUTOSIZE message. An app should send either of these messages whenever the size of the parent window changes or after sending a message that requires the size of the toolbar to be adjusted.
Only one class-specific style is associated with toolbar controls: TBSYTLE_TOOLTIPS. When you specify this style, the toolbar creates and manages a tooltip--a small pop-up window that contains a line of text describing a toolbar button; see Figure 2(b). The tooltip is displayed only when the user leaves the cursor on a toolbar button for approximately one second. It then appears near the cursor.
An app that needs to send messages directly to the tooltip control can retrieve its handle using the TB_GETTOOLTIPS message. An application can replace the tooltip control of a toolbar using TB_SETTOOLTIPS.
Each button in a toolbar control can include a bitmapped image. A toolbar stores the information that it needs to draw the images in an internal list. When calling CreateToolbarEx, you specify a monochrome or color bitmap that contains the initial images, and the toolbar adds the information to the internal list of images. You can add additional images by using the TB_ADDBITMAP message. Each image has a zero-based index (you use an image's index to associate the image with a button). Windows 95 assumes that all of a toolbar control's bitmapped images are the same size. You specify the size when you create the toolbar using CreateToolbarEx. If you use CreateWindow or CreateWindowEx to create a toolbar, the size of the images is set to the default dimensions of 16'15 pixels. You can use TB_SETBITMAPSIZE to change the dimensions of the bitmapped images, but you must do it before adding any images to the internal list of images.
Each button can display a string in addition to, or instead of, an image. A toolbar control maintains an internal list that contains all of the strings available to toolbar buttons. You add strings to the internal list using TB_ADDSTRING, specifying the address of the buffer containing the strings to add.
A button's style determines how the button appears and how it responds to user input. The TBSTYLE_BUTTON style creates a toolbar button that behaves like a standard push button. A button that has the TBSTYLE_CHECK style is similar to a standard push button, except that it toggles between the pressed and nonpressed states each time the user clicks it. You can create groups of toolbar buttons by using the TBSTYLE_GROUP or TBSTYLE_CHECKGROUP style, causing a button to stay pressed until the user chooses another button in the group. The TBSTYLE_SEP style creates a separator between buttons; a button with this style does not receive user input.
Each button in a toolbar control has a current state. The toolbar updates a button's state to reflect user actions, such as clicking the button. The state indicates whether the button is currently pressed or not pressed, enabled or disabled, hidden or visible, and so on. An application sets a button's initial state when adding the button to the toolbar, and can change and query the state by sending messages to the toolbar. An app can use TB_GETSTATE and TB_SETSTATE to query and set the state of buttons; additional messages query or set a particular button state.
Each button has a command identifier associated with it. When users select a button, the toolbar control sends the parent window a WM_COMMAND message that includes the command identifier of the button (IDM_CUT, IDM_PASTE, and so on). The parent window examines the command identifier and carries out the command.
A toolbar control keeps track of its buttons by assigning each button a zero-based position index. An application must specify the index of a button when sending messages to retrieve information about the button or set the button's attributes. Position indexes are updated as buttons are inserted and removed. An app can retrieve the current position index of a button by using TB_COMMANDTOINDEX. The message specifies the command identifier of a button, and the toolbar window uses the identifier to locate the button and return its position index.
All buttons in a toolbar control are the same size. CreateToolbarEx requires you to set the initial size of the buttons when you create the toolbar. When you use CreateWindow or CreateWindowEx to create a toolbar, the initial size is set to the default dimensions of 24'22 pixels. You can use TB_SETBUTTONSIZE to change the button size, but you must do so before adding any buttons to the toolbar. TB_GETITEMRECT retrieves the current dimensions of the buttons. When you add a string to the toolbar that is longer than any current toolbar string, the width is automatically set to accommodate the longest string in the toolbar.
You can give a toolbar control built-in customization features by specifying the CCS_ADJUSTABLE style. The customization features allow users to drag a button to a new position or remove it by dragging it off the toolbar. A Customize Toolbar dialog box that allows users to add, delete, and rearrange toolbar buttons is included. An app can display the dialog box by sending a TB_CUSTOMIZE message to the control.
A status bar is a horizontal window at the bottom of a parent window that displays contextual information, usually about the current state of what is being viewed in the window; see Figure 2(c). The status window can be divided into parts to display more than one type of information at once. Status bars are commonly used for descriptive messages about a selected menu or toolbar button, keyboard state, and time. Windows 95 includes an optional status bar in each of its Explorer windows.
The default position of a status window is along the bottom of the parent window, but you can specify the CCS_TOP style. SBARS_SIZEGRIP will include a sizing grip at the right end of the status window that is used to resize the parent window.
A status window can have different parts, each displaying a different line of text. You divide a status window into parts by sending the window an SB_SETPARTS message. A status window can have a maximum of 255 parts (although applications typically use far fewer). You can retrieve a count of the parts and their coordinates in a status window by sending the window an SB_GETPARTS message. To set the text of any part of a status window, send the SB_SETTEXT message; to retrieve it, use SB_GETTEXTLENGTH and SB_GETTEXT.
You can define individual parts of a status window to be owner drawn. Then, for example, you can display a bitmap rather than text, or draw text using a different font. To define a window part as owner drawn, send SB_SETTEXT to the status window, specifying the part and the SBT_OWNERDRAW drawing technique. When SBT_OWNERDRAW is specified, the lParam parameter is a 32-bit application-defined value that the application can use when drawing the part. For example, you can specify a font handle, a bitmap handle, a pointer to a string, and so on. When a status window needs to draw an owner-drawn part, it sends the WM_DRAWITEM message to the parent window, which is responsible for drawing the part.
You can put a status window into "simple mode" by sending it an SB_SIMPLE message. This is useful for displaying help text for menu items while users scroll through a menu. The string that a status window displays while in simple mode is maintained separately from the strings it displays otherwise. This means you can put the window in simple mode, set its text, and switch back to nonsimple mode without modifying the text used in either mode, which is convenient. Simple-mode status windows, however, do not support owner drawing.
A trackbar control is a window containing a slider and optional tick marks; see Figure 2(d). When users move the slider, the control sends notification messages to indicate the change. This is useful when you want users to select a discrete value or a set of consecutive values in a range. In Windows 95, trackbars are used in several control-panel applets--to allow users to set mouse-pointer speed, keyboard repeat rate, desktop area, and so on.
Trackbar controls can have either a vertical or horizontal orientation. They can have tick marks on either side, both sides, or neither. They can also be used to specify a range of consecutive values. These properties are controlled by using trackbar styles, which you specify when you create the trackbar.
Trackbar controls notify their parent windows of user actions by sending WM_HSCROLL messages, indicating their similarity with scrollbars.
A progress-bar control is a window that an application can use to indicate the progress of a lengthy operation such as file transfer or file copying. It consists of a rectangle that is gradually filled, from left to right, with the system highlight color as an operation progresses; see Figure 2(e).
A progress-bar control has a range and a current position. The range represents the entire duration of the operation, and the current position represents the progress the application has made toward completing it. The window procedure uses the range and the current position to determine the percentage of the progress bar to fill with the highlight color and to determine the text, if any, to display within the progress bar. Because the range and current position values are expressed as unsigned integers, the highest possible range or current position value is 65,535. If you do not set the range values, the system sets the minimum value to 0 and the maximum value to 100.
A progress-bar control provides messages that you can use to set the current position. PBM_SETPOS sets the position to a given value. PBM_DELTAPOS advances the position by adding a specified value to the current position. PBM_SETSTEP allows you to specify an increment for a progress-bar control. Subsequently, whenever you send the PBM_STEPIT message to the progress bar, the current position advances by the increment that you specified. The step increment default is 10.
A tab control is analogous to dividers in a notebook or labels in a file cabinet; see Figure 2(f). Using a tab control, an app can define multiple pages for the same area of a window or dialog box. Each page consists of a set of information or a group of controls that the app displays when users select the corresponding tab.
An app displays the current page in the display area. Typically, an app creates a child window or dialog box, setting the window size and position to fit the display area. Given the constraining window rectangle for a tab control, you can calculate the bounding rectangle of the tab control's display area using TCM_ADJUSTRECT.
You can control specific characteristics of tab controls by specifying tab-control styles. You can cause the tabs to look like buttons by specifying the TCS_BUTTONS style. Tabs in this type of tab control should serve the same function as button controls. That is, clicking a tab should carry out a command instead of displaying a page. Because the display area in a button tab control is typically not used, no border is drawn around it.
By default, a tab control displays only one row of tabs. If not all tabs can be shown at once, the tab control displays an up-down control so that the user can scroll additional tabs into view. You can cause a tab control to display multiple rows of tabs, if necessary, by specifying the TCS_MULTILINE style. Tabs are left-aligned within each row unless you specify TCS_RIGHTJUSTIFY.
A tab control automatically sizes each tab to fit its icon, if any, and its label. TCS_FIXEDWIDTH sizes all the tabs to fit the widest label, or you can assign a specific width and height by using TCM_SETITEMSIZE. Within each tab, by default, the control centers the icon and label with the icon to the left of the label, but certain styles let you modify this default behavior.
When users select a tab, a tab control sends its parent window notification messages in the form of WM_NOTIFY messages. The TCN_SELCHANGING notification message is sent before the selection changes, and TCN_SELCHANGE is sent afterward. You can process TCN_SELCHANGING to save the state of the outgoing page, or to prevent the selection from changing, maybe even put up a message box. TCN_SELCHANGE is typically processed by the app to display the incoming page in the display area. This might entail changing the information displayed in a child window. More often, each page consists of a child window or dialog box. In this case, an app might process this notification by destroying or hiding the outgoing child window or dialog box and by creating or showing the incoming child window or dialog box.
Each tab can have an associated icon, which is specified by an index into an image list for the tab control. When created, a tab control has no image list associated with it. An app can create one using ImageList_Create and then assign it to a tab control with TCM_SETIMAGELIST. To retrieve the handle of the image list currently associated with a tab control, use TCM_GETIMAGELIST.
You can add images to a tab control's image list just as you would to any other. However, an application should remove images using the TCM_REMOVEIMAGE message instead of ImageList_Remove. This message ensures that each tab remains associated with the same image. When a tab control is destroyed, it destroys any image list associated with it unless you specify the TCS_SHAREIMAGELISTS window style. This is useful if you want to assign the same image list to multiple common controls.
If a tab control has the TCS_OWNERDRAWFIXED style, the parent window must paint tabs by processing the WM_DRAWITEM message. The tab control sends this message whenever a tab needs to be painted. And as with toolbar buttons, you can use a tooltip control to provide a brief description for each tab in a tab control. A tab control that has the TCS_TOOLTIPS style creates a tooltip control when it is created, and when the tab control is destroyed, it destroys the tooltip control.
A list-view control is a window that displays a collection of items, each consisting of an icon and a label. List views provide ways of arranging and displaying individual items; see Figure 3.
List-view controls can display their contents in four different views: large icon, small icon, list, and report. You can change the view type after a list-view control is created. To retrieve and change the window style, use GetWindowLong and SetWindowLong. To control item arrangement in icon or small icon view, specify LVS_ALIGNTOP (the default style), LVS_ALIGNBOTTOM, LVS_ALIGNLEFT, or LVS_ALIGNRIGHT. You can change the alignment after a list-view control is created. LVS_ALIGNMASK isolates the window styles that specify the alignment of items. Additional window styles control other options.
The icons for list-view items are contained in image lists, which you create and assign to the list-view control. One image list contains the full-sized icons used in icon view; another contains smaller versions of the same icons for use in other views. You can also specify a third image list that contains state images, which are displayed next to an item's icon to indicate an application-defined state.
You assign an image list to a list-view control using the LVM_SETIMAGELIST message, specifying whether the image list contains large icons, small icons, or state images. You can use GetSystemMetrics to determine appropriate dimensions for large and small icons in the system, and ImageList_Create to create the image lists. You can retrieve the handle of an image list currently assigned to a list-view control using LVM_GETIMAGELIST.
The large- and small-icon image lists typically contain icons for each type of list-view item. Obviously, you need not create both of these image lists if only one icon size is used. If you create both image lists, they must contain the same images in the same order because a single index is used to identify a list-view item's icon in both image lists. The large- and small-icon image lists can also contain overlay images designed to be superimposed on item icons. If a state-image list is specified, a list-view control reserves space to the left of each item's icon for a state image. An application can use state images (such as checked and cleared check boxes) to indicate application-defined item states.
By default, a list-view control destroys the image lists assigned to it when it is destroyed, but if it has the LVS_SHAREIMAGELISTS window style, the app is responsible for destroying the image lists when they are no longer in use. You should specify this style if you assign the same image lists to multiple list-view controls; otherwise, more than one control might try to destroy the same image list.
Each item in a list-view control consists of an icon, label, current state, and application-defined value. One or more subitems can also be associated with each item. A subitem is a string that, in report view, can be displayed in a column to the right of an item's icon and label (Size, Kind, and Modified fields in Figure 3). All items in a list view have the same number of subitems. Using list-view messages, you can add, modify, retrieve information about, and delete items. You can also find items with specific attributes.
A callback item is a list-view item for which the application, rather than the control, stores the text, icon, or both. Although a list-view control can store these attributes for you, you may want to use callback items if your app already maintains some of this information. The callback mask specifies which item-state bits are maintained by the application, and it applies to the whole control rather than to a specific item. The callback mask is zero by default, meaning that the control tracks all item states. If an application uses callback items or specifies a nonzero callback mask, it must be able to supply list-view item attributes on demand. A list-view control requests any information it needs to display an item by sending its owner window an LVN_GETDISPINFO notification message. If item attributes or state bits maintained by the app change, the list-view control sends its owner window an LVN_SETDISPINFO notification that enables the application to update its information. If you change a callback item's attributes, you can use LVM_UPDATE to force the control to repaint the item. This message also arranges the list-view control if it has the LVS_AUTOARRANGE style. You can use LVM_REDRAWITEMS to redraw a range of items by invalidating the corresponding portions of the list view's client area.
Columns control the way items and their subitems are displayed in report view. Each column has a title and width, and it is associated with a specific subitem, subitem zero being the item's icon and label. Unless the LVS_NOCOLUMNHEADER window style is specified, column headers appear in report view. Users can click a column header, causing LVN_COLUMNCLICK to be sent to the parent window. Typically, the parent window sorts the list view by the specified column when this occurs.
You can use list-view messages to arrange and sort items and to find items based on their attributes or positions. Arranging repositions items to align on a grid, but the indexes of the items do not change. Sorting changes the sequence of items (and their corresponding indexes) and then repositions them accordingly. You can arrange items only in icon and small icon views, but you can sort items in any view. To arrange items, use LVM_ARRANGE. You can ensure that items are arranged at all times by specifying LVS_AUTOARRANGE.
To sort items, use LVM_SORTITEMS to specify an application-defined callback function that is called to compare the relative order of any two items. By supplying an appropriate comparison function, you can sort items by label, subitem, or any other property. You can ensure that a list-view control is always sorted by specifying the LVS_SORTASCENDING or LVS_SORTDESCENDING window style. You cannot supply a comparison function when using these window styles. The list view sorts the items by label in ascending or descending order.
You can find a list-view item with specific properties by using LVM_FINDITEM. You can find a list-view item that is in a specified state and bears a specified geometrical relationship to a given item by using the LVM_GETNEXTITEM message. For example, you can retrieve the next selected item to the right of a specified item.
Every list-view item has a position and size, which you can retrieve and set using messages. You can also determine which item, if any, is at a specified position. The position of list-view items is specified in view coordinates, which are client coordinates offset by the scroll position.
Unless the LVS_NOSCROLL window style is specified, a list-view control can be scrolled to show more items than can fit in the client area of the control.
A list-view control that has the LVS_EDITLABELS window style enables users to edit item labels in place by clicking the label of an item that has the focus. An application can begin editing automatically using LVM_EDITLABEL. The list-view control notifies the parent window when editing begins and when it is canceled or completed. When editing is completed, the parent window is responsible for updating the item's label, if appropriate. During label editing, you can get the handle of the edit control used for label editing by using LVM_GETEDITCONTROL. To limit the amount of text users can enter, send the edit control EM_LIMITTEXT. You can even subclass the edit control to mask its input.
A tree-view control is a window that displays a hierarchical list of items, such as the files and directories on a disk; see Figure 4. Each item consists of a label and an optional bitmapped image and can have a list of associated subitems ("child items") that users can expand or collapse.
An item that has one or more child items is called a "parent item." A child item is displayed below its parent item and indented to indicate that it is subordinate to the parent. An item that has no parent is at the top of the hierarchy and is a root item.
Tree-view controls have a number of styles. The TVS_HASLINES style enhances the graphic representation of a tree-view control's hierarchy by drawing lines that link child items to their corresponding parent item. This style does not link items at the root of the hierarchy. To do so, you need to combine the TVS_HASLINES and TVS_LINESATROOT styles.
Users can expand or collapse a parent item's list of child items by double-clicking the parent item. A tree view that has the TVS_HASBUTTONS style adds a button to the left side of each parent item, on which users can also click to expand or collapse the child items. TVS_HASBUTTONS does not add buttons to items at the root of the hierarchy. To do so, you must combine TVS_HASLINES, TVS_LINESATROOT, and TVS_HASBUTTONS. Tree controls in Windows 95's Explorer windows have these three styles turned on, and use plus and minus icons to indicate if a parent item can be expanded or collapsed. As with list-view controls, a TVS_EDITLABELS style lets users edit the labels of tree-view items.
You add an item to a tree-view control by sending it the TVM_INSERTITEM message. At any given time, the state of a parent item's list of child items can be either expanded or collapsed. When the state is expanded, the child items are displayed below the parent item; when it is collapsed, they are not. The list automatically toggles between the expanded and collapsed states when the user double-clicks the parent item or the button associated with it. An application can expand or collapse the child items by using TVM_EXPAND. A tree-view control sends the parent window a TVN_ITEMEXPANDING message when a parent item's list of child items is about to be expanded or collapsed. The notification gives an application the opportunity to prevent the change or to set any attributes of the parent item that depend on the state of the list of child items. After changing the state of the list, the tree view sends the parent window a TVN_ITEMEXPANDED message.
When a list of child items is expanded, it is indented relative to the parent item. You can set the amount of indentation by using TVM_SETINDENT or retrieve the current amount by using TVM_GETINDENT.
A tree-view control allocates memory for storing each item; the text of the item labels takes up a significant portion of this memory. If your application maintains a copy of the strings in the tree-view control, you can decrease the memory requirements of the control by specifying the LPSTR_TEXTCALLBACK value instead of passing actual strings to the tree view. Using LPSTR_TEXTCALLBACK causes the tree view to retrieve the text of an item's label from the parent window whenever the item needs to be redrawn. To retrieve the text, the tree view sends a TVN_GETDISPINFO message, and the parent window must provide the appropriate information.
An item's initial position is set when the item is added to the tree-view control using TVM_INSERTITEM, which includes a TV_INSERTSTRUCT structure that specifies the handle of the parent item and the handle of the item after which the new item is to be inserted. The second handle must identify either a child item of the given parent or TVI_FIRST, TVI_LAST, or TVI_SORT. The tree-view control places the new item at the beginning or end of the given parent item's list of child items when TVI_FIRST and TVI_LAST are specified. The tree-view control inserts the new item into the list of child items in alphabetical order based on the text of the item labels when TVI_SORT is specified.
You can put a parent item's list of child items into alphabetical order by using TVM_SORTCHILDREN, which includes a parameter that specifies whether all levels of child items descending from the given parent item are also put into alphabetical order.
TVM_SORTCHILDRENCB allows you to sort child items based on criteria that you define; when you use this message, you specify an application-defined callback function that the tree-view control can call to determine the relative order of two child items.
A tree-view control notifies the parent window when the user wants to begin dragging an item. The parent window receives a TVN_BEGINDRAG message when users begin dragging an item with the left mouse button and a TVN_BEGINRDRAG message when users begin dragging with the right button. An application can prevent a tree-view control from sending these notifications by giving control the TVS_DISABLEDRAGDROP style. You obtain an image to display during a dragging operation by using TVM_CREATEDRAGIMAGE. The tree-view control creates a dragging bitmap based on the label of the item being dragged. Then the tree view creates an image list, adds the bitmap to it, and returns the handle of the image list. You must provide the code that actually drags the item. This typically involves using the dragging capabilities of the image-list functions and processing the WM_MOUSEMOVE and WM_LBUTTONUP (or WM_RBUTTONUP) messages sent to the parent window after the drag operation has begun.
If items in a tree-view control are to be the targets of a drag-and-drop operation, you need to know when the mouse cursor is on a target item. You can find this out with the TVM_HITTEST message. To indicate that an item is the target of a drag-and-drop operation, use the TVM_SETITEM message to set the state to the TVIS_DROPHILITED value. An item that has this state is drawn in the style used to indicate a drag-and-drop target.
A property sheet is a window that allows the user to view and edit the properties of an item; see Figure 5. For example, a spreadsheet application can use a property sheet to allow users to set the font and other formatting properties of a cell. A property sheet contains one or more overlapping child windows called "pages," each containing control windows for setting a group of related properties. Each page has a tab that users can select to bring the page to the foreground of the property sheet.
A property sheet and the pages it contains are actually dialog boxes. The property sheet is a system-defined modeless dialog box that manages the pages and provides a common container for them. It includes a frame, title bar, system menu, and the buttons OK, Cancel, Apply Now, and Help. The dialog-box procedures for the pages receive notification messages when the user selects the buttons. Each page in a property sheet is an application-defined modeless dialog box that manages the control windows used to view and edit the properties of an item. You provide the dialog-box template used to create each page as well as the dialog-box procedure that manages the controls and sets the properties of the corresponding item.
A property sheet sends notification in the form of WM_NOTIFY messages to the dialog-box procedure for a page when the page is gaining or losing the activation and when the user chooses an OK, Cancel, Apply Now, or Help button. The lParam parameter points to an NMHDR structure that includes the window handle of the property-sheet dialog box.
A property sheet must contain at least one page, but no more than the value of MAXPROPSHEETPAGES. Each page has a zero-based index that the property sheet assigns according to the order in which the page is added to the property sheet. These indexes are used in messages that you send to the property sheet. Each page has a corresponding icon and label; the property sheet creates a tab for each page and displays the icon and label in the tab. If a property sheet contains only one page, the tab for the page is not displayed. The dialog-box procedure for a page must not call the EndDialog function. Doing so will destroy the entire property sheet, not just the page.
Before creating a property sheet, you must define one or more pages. This involves filling a PROPSHEETPAGE structure with information about the page, icon, label, dialog-box template, dialog-box procedure, and so on, and then specifying the address of the structure in a call to CreatePropertySheetPage. The function returns a handle of the HPROPSHEETPAGE type that uniquely identifies the page. To create a property sheet, you specify the address of a PROPSHEETHEADER structure in a call to PropertySheet; the structure defines the icon and title for the property sheet and includes a pointer to an array of HPROPSHEETPAGE handles. When PropertySheet creates the property sheet, it includes the pages identified in the array. The order of the array determines the order of the pages in the property sheet. Alternatively, you can specify an array of PROPSHEETPAGE structures instead of an array of HPROPSHEETPAGE handles when creating a property sheet. In this case, PropertySheet creates handles for the pages before adding them to the property sheet.
When a page is created, the dialog-box procedure for the page receives a WM_INITDIALOG message. The message's lParam parameter points to the PROPSHEETPAGE structure used to create the page. The dialog box can save the information in the structure and use it later to modify the page.
The PropertySheet function automatically sets the size and initial position of a property sheet. The position is based on the position of the owner window, and the size is based on the largest page specified in the array of pages when the property sheet was created. An application can add a page after creating a property sheet by using the PSM_ADDPAGE message, but the size of the property sheet cannot change after it has been created, so the new page must be no larger than the largest page currently in the property sheet. An app removes a page by using the PSM_REMOVEPAGE message. When you define a page, you can specify the address of a ReleasePropSheetPageProc callback function that the property sheet calls when it is removing the page. ReleasePropSheetPageProc lets you perform cleanup operations for individual pages.
When a property sheet is destroyed, it automatically destroys all of the pages that have been added to the property sheet. The pages are destroyed in reverse order from that specified in the array used to create the pages. To destroy a page created by CreatePropertySheetPage but not added to the property sheet, use DestroyPropertySheetPage.
You specify the title of a property sheet in the PROPSHEETHEADER structure used to create the property sheet. An application can change the title after a property sheet is created by using the PSM_SETTITLE message. By default, a property sheet uses the name string specified in the dialog-box template as the label for a page.
A property sheet can have only one active page at a time: the page at the foreground of the overlapping stack of pages. Users activate a page by selecting its tab; an application activates a page by using the PSM_SETCURSEL message. The property sheet sends the PSN_KILLACTIVE message to the page that is about to lose the activation. In response, the page should validate any changes that the user has made to the page. If the page has invalid property settings and requires additional user input before losing the activation, it should prevent deactivation and also display a message box that describes the problem and recommended action. The property sheet sends the PSN_SETACTIVE message to the page gaining the activation before the page is visible. The page should respond by initializing its control windows.
The PSN_HASHELP notification requires a page to indicate whether it supports the Help button; otherwise, the button is disabled. When users choose the Help button, the active page receives the PSN_HELP notification message. The page should respond by displaying help information, typically by calling the WinHelp function.
The OK and Apply Now buttons are similar; both direct a property sheet's pages to validate and apply the property changes that users have made. The only difference is that the OK button causes the property sheet to be destroyed after the changes are applied and the Apply Now button does not.
When users choose the OK or Apply Now buttons, the property sheet sends the PSN_KILLACTIVE notification to the active page, giving it an opportunity to validate the user's changes. If the changes are valid, the property sheet sends the PSN_APPLY notification to each page, directing them to apply the new properties to the corresponding item. If users' changes are not valid, the page can display a dialog box informing users of the problem. The Apply Now button is initially disabled when a page becomes active, indicating that there are not yet any property changes to apply. When the page receives user input through one of its controls indicating that the user has edited a property, the page should send the PSM_CHANGED message to the property sheet. The message causes the property sheet to enable the Apply Now button. If the user subsequently chooses the Apply Now button, the page should reinitialize its controls and then send the PSM_UNCHANGED message to disable again the Apply Now button.
The property sheet sends the PSN_RESET notification message to all pages when the user chooses the Cancel button, indicating that the property sheet is about to be destroyed. A page should use the notification to perform cleanup operations.
A "wizard" is a special type of property sheet that consists of a sequence of dialog boxes that guide the user through the steps of an operation. Windows 95 uses wizard property sheets to help you set up printers, modems, and other devices. In a wizard property sheet, the pages do not have tabs, and only one page is visible at a time. Also, instead of having OK and Apply Now buttons, a wizard property sheet has a Back button, Next or Finish button, and Cancel button. Use the PSM_SETWIZBUTTONS message with the PSWIZB_BACK, PSWIZB_NEXT, and PSWIZB_FINISH flags to tell the property sheet which buttons to enable. As with standard property sheets, the Help button is included if the page indicates that it has one in response to the PSN_HASHELP notification.
You create and initialize a wizard property sheet just as you would a standard property sheet, except that you must include the PSH_WIZARD value in the dwFlags member of the PROPSHEETHEADER structure. The system ignores the property-sheet caption member; instead, it puts the label of the current page in the title bar of the property sheet. When the user switches from one page to the next, the system updates the title using the label of the current page.
Use the WIZ_CXDLG and WIZ_CYDLG values to set the sizes of the pages in your wizard property sheet. Doing so ensures that the pages conform to the standard page size for wizards.
The dialog-box procedure for a page in a wizard property sheet receives all of the same notification messages as that in a standard property-sheet page, plus three additional messages: PSN_WIZBACK, PSN_WIZNEXT, and PSN_WIZFINISH. These are received when users choose the Back, Next, or Finish button. When users choose the Back or Next buttons, the property sheet automatically advances to the previous or next page. The system automatically destroys the wizard property sheet when users click the Finish button.
You can use a column-heading control to display properties of a selected object in a multicolumn list; see Figure 6(a). The control allows you to define the displayed property and the sort order based on the property for items in the list.
Spin boxes are text boxes that accept a limited set of discrete ordered input values; see Figure 6(b). The buttons on the control allow users to increment or decrement values in the text box.
Users can type a text value directly into the control or use the buttons to change the value. Pressing the arrow keys also changes the value. You can use a single set of spin-box buttons to edit a sequence of related text boxes; for example, time as expressed in hours, minutes, and seconds. The buttons affect only the text box that currently has the input focus.
A rich-text box supports the same basic text editing support as a standard text box, and in addition, it supports individual character font and paragraph formatting properties; see Figure 6(c).
Windows 95 common controls are standardized, well-tested GUI building blocks that give your applications a consistent look and feel that blends smoothly with the Windows 95 user interface. Tightly integrated with wrapper classes in MFC, they can be used in an object-oriented way for easy customization, letting you build new features on top of them while avoiding costly reimplementation of built-in functionality.
Figure 1 Interaction between applications and common controls.
Figure 3 List-view control in report view. Figure 4 Tree-view control. Figure 5 Property sheet.
typedef struct tagNMHDR {
HWND hwndFrom; // handle of control sending message
UINT idFrom; // identifier of control sending message
UINT code; // notification code; see Table 1
} NMHDR;
Code Description
NM_CLICK User has clicked the left mouse button within the control.
NM_DBLCLK User has double-clicked the left mouse button within the control.
NM_ENDWAIT The control has completed a lengthy operation.
NM_KILLFOCUS The control has lost the input focus.
NM_OUTOFMEMORY The control could not complete an operation because
not enough memory is available.
NM_RCLICK User has clicked the right mouse button within the control.
NM_RDBLCLK User has double-clicked the right mouse button within the control.
NM_RETURN Control has the input focus, and the user has pressed the ENTER key.
NM_SETFOCUS Control has received the input focus.
NM_STARTWAIT Control has started a lengthy operation.
Copyright © 1995, Dr. Dobb's Journal