ID: Q64326
At the heart of owner-draw controls is the processing of WM_DRAWITEM messages. A WM_DRAWITEM message is generated in response to an action by the user. When an application processes this message, it paints a button, a menu, a list box item, or a combo box item. The application must perform the drawing of the specified control or item because the application is free to give the control any appearance; Windows does not interfere in the processing of owner-draw controls.
The following file is available for download from the Microsoft Software Library:
~ OdList.exe (size: 22650 bytes)
For more information about downloading files from the Microsoft Software
Library, please see the following article in the Microsoft Knowledge Base:
ARTICLE-ID: Q119591
TITLE : How to Obtain Microsoft Support Files from Online Services
The lParam that accompanies the WM_DRAWITEM message is a long (or far)
segment:offset pointer to a DRAWITEMSTRUCT data structure. For convenience
in accessing the members of the structure, the lParam is usually assigned
to a LPDRAWITEMSTRUCT variable called "lpdis" as in the following example:
...
LPDRAWITEMSTRUCT lpdis;
...
switch (message)
{
case WM_DRAWITEM:
lpdis = (LPDRAWITEMSTRUCT)lParam;
...
After handling the WM_DRAWITEM message, Windows can move the
DRAWITEMSTRUCT in memory. This movement may occur even if the
application's code and data segments are FIXED. Therefore, do not keep
and reuse the value of the lpdis variable to process later WM_DRAWITEM
messages, because the far pointer may no longer be valid. Each
WM_DRAWITEM message will be accompanied by a valid lParam, which the
application should use.
An owner-draw control can be created as the child of a dialog box or of a pop-up or overlapped window. The parent dialog box or window procedure will receive a separate WM_DRAWITEM message for each owner- draw button and for each visible item in other controls. This message is sent under any of the following four circumstances:
1. When each item is first drawn.
2. When each item is redrawn.
3. When each item's selection state changes (selected or deselected).
4. When each item's focus state changes (receives or loses the focus).
For a list box with numerous visible items, Windows will send a substantial number of WM_DRAWITEM messages. Therefore, it is good practice to be as time efficient as possible when processing these messages.
During the processing of a WM_DRAWITEM message, be careful not to call a function such as MessageBox() that might draw over the control. Violating this caution may cause Windows to become unstable.
The members of the DRAWITEMSTRUCT data structure, documented on pages 7-36 to 7-38 of the "Microsoft Windows SDK Programmer's Reference Volume 2," indicate which item in which control should be drawn, and what the status of that item is. Generally, a control item is drawn differently when it has the focus, or when it is selected, than when it has neither of these attributes.
Application code should be prepared to perform three actions. The "itemAction" field of the DRAWITEMSTRUCT indicates the proper action to take. The values of the itemAction field are as follows:
Value Description
-----------------------------------------------------------------------
ODA_FOCUS The item's focus state has changed (gained or lost
the focus) since it was last drawn.
ODA_SELECT The item's selection state has changed (selected or
unselected) since it was last drawn.
ODA_DRAWENTIRE The item has not been changed. This is a request to
the application to redraw the item without
change. The "itemState" bits, described below,
specify the attributes that the item should have.
In addition to the three actions above, there are several states in which
each control may exist. These states are indicated by the "itemState" field
of the DRAWITEMSTRUCT. The two states discussed here are the most typical
and are the only states discussed in this article. The other states may be
handled in a similar manner. The discussed values of the itemState field
are as follows:
Value Description
-----------------------------------------------------------------------
ODS_FOCUS The item has the input focus.
ODS_SELECTED The item is selected.
If neither of these bits is set, the control or item is in its
"normal" state.
The most frequently encountered case is to have itemAction equal to ODA_DRAWENTIRE and neither of the monitored ODS_* bits set. If the data to be displayed is a string of text, call TextOut() or TabbedTextOut() to draw the text on the screen. Nontext data can be displayed with GDI functions such as BitBlt() or Rectangle(). An application can combine these functions as appropriate.
Identifying String vs. Nonstring Items
As discussed above, each WM_DRAWITEM message is accompanied by a far pointer to a DRAWITEMSTRUCT. For owner-draw list boxes created with the LBS_HASSTRINGS style, and for owner-draw combo boxes that have been created with both the CBS_HASSTRINGS and CBS_DROPDOWNLIST styles, the itemID field in this structure will be an index that refers to the appropriate string. The itemData field will NOT be a far pointer to the string. To retrieve a pointer to the string, send a message to the list box or combo box. The wParam of the message is set to the itemID from the structure. The following code assumes that the list box or combo box in question is in a dialog box:
LPSTR lpItem;
To a list box:
lpItem = SendDlgItemMessage(hDlg, LBID, LB_GETTEXT,
lpdis->itemID, 0L);
To a combo box:
lpItem = SendDlgItemMessage(hDlg, CBID, CB_GETLBTEXT,
lpdis->itemID, 0L);
LBID and CBID are the integer identifiers of the list box and combo
box, respectively, within the dialog box.
If the list box or combo box does not have one of the styles described above, the 32-bit itemData field provides information to identify the item.
An application will receive two WM_DRAWITEM messages for an item that is both selected and has the focus.
For example, for an item in a single-selection list box that has the focus and is selected, the application will first receive a WM_DRAWITEM message with the ODA_DRAWENTIRE or ODA_SELECT action and the ODS_SELECTED state. Then, the application will receive a second WM_DRAWITEM message with the ODA_DRAWENTIRE or ODA_FOCUS action and the ODS_FOCUS state. Therefore, it is important to handle the selection state separately from the focus state.
Handling the two attributes individually is especially important because some functions, such as DrawFocusRect(), remove the effect of one call with a second call. DrawFocusRect() creates the dashed rectangle border around the text of a control with the focus.
A problem occurs when DrawFocusRect() is called during the processing of a selection state message to draw the focus rectangle. If DrawFocusRect() is called again during the processing of the focus state message, the rectangle is removed, which is highly undesirable.
In Windows 3.0, if at the end of processing the WM_DRAWITEM message with ODS_FOCUS set, the dialog box procedure returns FALSE, the Dialog Manager code will call DrawFocusRect(). This call may serve as a safety feature in the application, or it may introduce a problem. However, this undocumented behavior may change in future versions of Windows and is not guaranteed.
Three points in summary:
1. The focus state should be handled separately from the selection
state.
2. The application must do all necessary drawing itself.
3. The dialog procedure returns TRUE when processing is finished.
Only the static area of a CBS_DROPDOWNLIST owner-draw combo box will receive the focus action or state; the dropped-down list box does not receive the focus.
The static text area, which displays the selected list box item, is part of the parent combo box window. Indicate that the combo box has received the focus by drawing the static area appropriately. The static area can be identified by its itemID of -1 (0xffff). The static area can also be identified by the fact that its left side, rcItem.left, is intentionally greater than zero to offset the selected item to the right a few pixels, but the left side of the list box items are zero.
The list box is a child window of the combo box. List box items can receive the selection action or state when they are actually selected, and also when they are scrolled over with the mouse cursor or with the keyboard. Upon receipt of the WM_DRAWITEM message, draw the currently selected item appropriately.
During the design phase for the application, both the actions and states should be considered. To save work, note that some action/state combinations are effectively equivalent. Equivalent states should result in the item being drawn the same way, typically by a call to a function in the application.
An example of common equivalent action/state combinations is ODA_DRAWENTIRE and ODS_FOCUS versus ODA_FOCUS and ODS_FOCUS. In either case, the application would draw the control in its focus state. Similarly, ODA_DRAWENTIRE and ODS_SELECTED versus ODA_SELECT with ODS_SELECTED causes the control to be drawn in its selected state.
A common method for processing owner-draw items is to draw the control in its "normal" state, modify the display of the control based on its selection state, and then modify the display of the control based upon its focus state, as appropriate.
Below is an example of the common method. The following code fragment could be inserted into the dialog box procedure of a dialog box that contains an owner-draw list box with the LBS_HASSTRINGS style. This code processes the WM_DRAWITEM and WM_MEASUREITEM messages:
/********************************************************************
* O W N E R - D R A W M E S S A G E P R O C E S S I N G *
* *
* Do all drawing for owner-draw list box string items. *
* Return TRUE when drawing completed. *
* Or else break and return FALSE to do nothing. *
********************************************************************/
LPDRAWITEMSTRUCT lpdis; // pointer for drawing struct
LPMEASUREITEMSTRUCT lpmis; // pointer for item measurement
HDC hDC; // dialog's device context
TEXTMETRIC tm; // for system font height
char szBuf[80]; // buffer for list box string
:
:
switch (message)
{
case WM_DRAWITEM:
/* Get convenient pointer to the DRAWITEMSTRUCT */
lpdis = (LPDRAWITEMSTRUCT)lParam;
/*
* If no items in list box yet, or combo box's static area have
* the focus, indicate focus for specified rectangle.
*/
if (lpdis->itemID == -1)
{
HandleFocusState(lpdis); // function below
return TRUE;
}
/*
* Draw a list box item in its normal state. Then check if it is
* selected or has the focus state, and call functions to handle
* drawing for these states if necessary.
*/
if (lpdis->itemAction & ODA_DRAWENTIRE)
{
/* Get text of string referred to by itemID */
SendMessage(lpdis->hwndItem, LB_GETTEXT, lpdis->itemID,
(LONG)(LPSTR)szBuf);
/*
* If the items were not strings, lpdis->itemData provides
* a 32-bit index to identify the item.
*/
// call TextOut or TabbedTextOut to display "szBuf"
:
:
// If item selected, do additional drawing -- invert rect
if (lpdis->itemState & ODS_SELECTED)
HandleSelectionState(lpdis); // function below
// if item has focus, do additional drawing -- dashed border
if (lpdis->itemState & ODS_FOCUS)
HandleFocusState(lpdis);
return TRUE;
} // lpdis->itemAction & ODA_DRAWENTIRE
/*
* If a list box item was just selected or unselected, call
* function (which could check if ODS_SELECTED bit is set) and
* draw item in selected or unselected state.
*/
if (lpdis->itemAction & ODA_SELECT)
{
HandleSelectionState(lpdis);
return TRUE;
}
/*
* If a list box item just gained or lost the focus, call
* function (which could check if ODS_FOCUS bit is set) and draw
* item in its focus or nonfocus state.
*/
if (lpdis->itemAction & ODA_FOCUS)
{
HandleFocusState(lpdis);
return TRUE;
}
break; // case WM_DRAWITEM
/* Must specify owner-draw list box item height. */
case WM_MEASUREITEM:
lpmis = (LPMEASUREITEMSTRUCT)lParam; /* for convenience */
/* get and use height of system font for drawing */
hDC = GetDC(hDlg); /* get device context */
GetTextMetrics(hDC, &tm); /* get text information */
lpmis->itemHeight = tm.tmHeight; /* required to set height */
ReleaseDC(hDlg, hDC); /* release device context */
break;
:
:
} // switch
return FALSE;
}
/********************************************************************
* FUNCTION : HandleSelectionState(LPDRAWITEMSTRUCT) *
* *
* PURPOSE : Handles selection of list box item *
********************************************************************/
void HandleSelectionState(LPDRAWITEMSTRUCT lpdis)
{
/*
* This function could test the ODS_SELECTED bit in the itemState
* field. If true, draw the selected state, otherwise draw the non-
* selected state. However, because a second call to InvertRect()
* restores the rectangle to its original state, make the call whether
* the item is selected or unselected.
*/
InvertRect(lpdis->hDC, (LPRECT)&lpdis->rcItem);
}
/********************************************************************
* FUNCTION : HandleFocusState(LPDRAWITEMSTRUCT) *
* *
* PURPOSE : Handles focus state of list box item *
********************************************************************/
void HandleFocusState(LPDRAWITEMSTRUCT lpdis)
{
/*
* This function could test the ODS_FOCUS bit in the itemState field.
* If true, draw the selected state, otherwise draw the non-selected
* state. However, because a second call to DrawFocusRect() restores
* the rectangle to its original state, make the call whether the item
* has the focus or not.
*/
// New to Windows 3.0, this function draws a black dashed line
// on the border of the specified rectangle
DrawFocusRect(lpdis->hDC, (LPRECT) &lpdis->rcItem);
}
Additional query words:
Keywords : kbcode kbfile kbsample kb16bitonly kbCtrl kbListBox kbSDKPlatform kbGrpUser
Last Reviewed: January 3, 1999