SAMPLE: Owner-Draw: Processing WM_DRAWITEM to Draw Controls

Last reviewed: July 22, 1997
Article ID: Q64326
3.00 3.10 WINDOWS kbprg kbfile

The information in this article applies to:

  • Microsoft Windows Software Development Kit (SDK) for Windows versions 3.0 and 3.1

SUMMARY

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.

ODLIST is a file in the Microsoft Software Library that contains a sample application that demonstrates processing the WM_DRAWITEM message.

NOTE: This sample is applicable to Windows 3.1 only, NOT to Win32.

Download ODLIST.EXE, a self-extracting file, from the Microsoft Software Library (MSL) on the following services:

  • Microsoft Download Service (MSDL)

          Dial (206) 936-6735 to connect to MSDL
          Download ODLIST.EXE (size: 22650 bytes) 
    
  • Internet (anonymous FTP)

          ftp ftp.microsoft.com
          Change to the \SOFTLIB\MSLFILES directory
          Get ODLIST.EXE (size: 22650 bytes) 
    

MORE INFORMATION

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.

Message Traffic for an Item with Both Selection and Focus

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.

A Combo Box's Drop-Down List Box Does Not Receive the Focus

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.

Reusing Code for Equivalent Combinations of Actions and States

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 reference words: 3.00 3.10 softlib ODLIST.EXE
KBCategory: kbprg kbfile
KBSubcategory: UsrCtl
Keywords : kb16bitonly UsrCtl kbfile kbprg
Version : 3.00 3.10
Platform : WINDOWS


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: July 22, 1997
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.