47.2.5 Using Custom Check Mark Bitmaps

A Windows video device driver provides a default check mark bitmap that Windows displays next to a menu item that is checked. You can customize an individual menu item by associating a pair of bitmaps that replace the default check mark bitmap for that item. Windows displays one bitmap when the item is checked and the other when it is unchecked. This section describes the steps involved in creating and using custom check mark bitmaps.

47.2.5.1 Creating Custom Check Mark Bitmaps

A custom check mark bitmap must be the same size as the default check mark bitmap. You can retrieve the default check mark bitmap's size by calling the GetMenuCheckMarkDimensions function. The low-order word of this function's return value specifies the width; the high-order word specifies the height.

You can provide a custom check mark bitmap as a bitmap resource. However, a bitmap resource won't be suitable for all screen resolutions and aspect ratios, so you'll need to use the StretchBlt function to size the bitmap as appropriate. Depending on the bitmap being sized, the result of the sizing may not be acceptable because stretching may distort the bitmap.

Instead of using a bitmap resource, you can use GDI functions to provide check mark bitmaps at runtime. This involves the following steps, which an application typically performs during the WM_CREATE message:

1.Creating a device context in memory that is compatible with the screen.

2.Creating a bitmap that is compatible with the display.

3.Selecting the bitmap into the device context.

4.Drawing or copying an image into the bitmap.

You can call the CreateCompatibleDC function to create a device context that is compatible with the screen. The function's hdc parameter can specify either NULL or the return value from the GetDC function. CreateCompatibleDC returns the handle of the compatible device context.

You can call the CreateCompatibleBitmap function to create an “empty” bitmap that is compatible with the screen. This function's nWidth and nHeight parameters set the size of the bitmap; they should specify the width and height information returned by GetMenuCheckMarkDimensions.

CreateCompatibleBitmap returns the handle of the bitmap. You pass this handle to the SelectObject function to select the bitmap into the compatible device context. Afterward, you can use the GDI drawing functions such as Rectangle and Ellipse to draw into the bitmap, or functions such as BitBlt and StretchBlt to copy an image into the bitmap.

GDI provides several other ways to create bitmaps. For more information, see Chapter 63, “Bitmaps.”

47.2.5.2 Associating the Bitmaps with a Menu Item

You associate a pair of check mark bitmaps with a menu item by passing the handles of the bitmaps to the SetMenuItemBitmaps function. The hbmUnchecked parameter identifies the unchecked bitmap; the hbmChecked parameter identifies the checked bitmap. If you want to remove one or both check marks for a menu item, you can set the hbmUnchecked or hbmChecked parameter, or both, to NULL.

47.2.5.3 Setting the Check Mark Attribute

The CheckMenuItem function sets a menu item's check mark attribute to either checked or unchecked. You can specify the MF_CHECKED flag to set the check mark attribute to checked, or MF_UNCHECKED to set it to unchecked.

47.2.5.4 Example: Simulating Check Boxes in a Menu

This section contains example code that shows how to simulate check boxes in a pop-up menu. The example contains a Character menu whose items allow the user to set the bold, italic, and underline attributes of the current font. When a font attribute is in effect, the example displays a selected check box next to the corresponding menu item; otherwise, it displays an empty check box to the item. The following illustration shows the checkboxes next to the menu items:

The example replaces the default check mark bitmap with two bitmaps: the selected check-box bitmap and the empty check-box bitmap. The selected check-box bitmap is displayed next to the Bold, Italic, or Underline menu item when the item's check mark attribute is set to MF_CHECKED. The empty check-box bitmap is displayed when the check mark attribute is set to MF_UNCHECKED.

Windows provides a predefined bitmap that contains the images used for check boxes and radio buttons (see the following illustration). The example isolates the selected and empty check boxes, copies them to two separate bitmaps, and then uses them as the checked and unchecked bitmaps for items in the Character menu.

Predefined Checkbox Bitmap

To retrieve the handle of the system-defined checkbox bitmap, the example calls the LoadBitmap function, specifying NULL as the hinst parameter and OBM_CHECKBOXES as the lpBitmapName parameter. Because the images in the bitmap are all the same size, the example can isolate them by dividing the bitmap's width and height by the number of images in its rows and columns.

The following portion of a resource-definition file shows how the menu items in the Character menu are defined. Initially, no font attributes are in effect, so the check mark attribute for the Regular item is set to checked and, by default, the check mark attribute of the remaining items is set to unchecked.

#include "men3.h"

MainMenu MENU

BEGIN

POPUP "&Character"

BEGIN

MENUITEM "&Regular", IDM_REGULAR, CHECKED

MENUITEM SEPARATOR

MENUITEM "&Bold", IDM_BOLD

MENUITEM "&Italic", IDM_ITALIC

MENUITEM "&Underline", IDM_ULINE

END

END

The following example shows the relevant contents of the application's header file:

/* Menu-item identifiers */

#define IDM_REGULAR 0x1

#define IDM_BOLD 0x2

#define IDM_ITALIC 0x4

#define IDM_ULINE 0x8

/* Check mark flags */

#define CHECK 1

#define UNCHECK 2

/* Font-attribute mask */

#define ATTRIBMASK 0xe

/* Function prototypes */

LRESULT APIENTRY MainWndProc(HWND, UINT, WPARAM, LPARAM);

HBITMAP GetMyCheckBitmaps(UINT);

BYTE CheckOrUncheckMenuItem(BYTE, HMENU);

The following example shows the portions of the window procedure that create the check mark bitmaps, set the check mark attribute of the Bold, Italic, and Underline menu items, and destroy check mark bitmaps:

LRESULT APIENTRY MainWndProc(hwndMain, uMsg, wParam, lParam)

HWND hwndMain;

UINT uMsg;

WPARAM wParam;

LPARAM lParam;

{

static HBITMAP hbmpCheck; /* handle of checked bitmap */

static HBITMAP hbmpUncheck; /* handle of unchecked bitmap */

static HMENU hmenu; /* handle of main menu */

BYTE fbFontAttrib; /* font-attribute flags */

switch (uMsg) {

case WM_CREATE:

/*

* Call the application-defined GetMyCheckBitmaps

* function to get the predefined selected and

* empty check-box bitmaps.

*/

hbmpCheck = GetMyCheckBitmaps(CHECK);

hbmpUncheck = GetMyCheckBitmaps(UNCHECK);

/*

* Set the checked and unchecked bitmaps for the menu

* items.

*/

hmenu = GetMenu(hwndMain);

SetMenuItemBitmaps(hmenu, IDM_BOLD, MF_BYCOMMAND,

hbmpUncheck, hbmpCheck);

SetMenuItemBitmaps(hmenu, IDM_ITALIC, MF_BYCOMMAND,

hbmpUncheck, hbmpCheck);

SetMenuItemBitmaps(hmenu, IDM_ULINE, MF_BYCOMMAND,

hbmpUncheck, hbmpCheck);

return 0;

case WM_COMMAND:

switch (LOWORD(wParam)) {

/* Process the menu commands. */

case IDM_REGULAR:

case IDM_BOLD:

case IDM_ITALIC:

case IDM_ULINE:

/*

* CheckOrUncheckMenuItem is an

* application-defined function that sets the

* menu-item check marks and returns the

* user-selected font attributes.

*/

fbFontAttrib = CheckOrUncheckMenuItem(

(BYTE) LOWORD(wParam), hmenu);

.

. /* Set the font attributes. */

.

return 0;

.

. /* Process other command messages. */

.

default:

break;

}

break;

.

. /* Process other window messages. */

.

case WM_DESTROY:

/* Destroy checked and unchecked bitmaps. */

DeleteObject(hbmpCheck);

DeleteObject(hbmpUncheck);

PostQuitMessage(0);

break;

default:

return (DefWindowProc(hwndMain, uMsg, wParam, lParam));

}

return NULL;

}

HBITMAP GetMyCheckBitmaps(fuCheck)

UINT fuCheck; /* CHECK or UNCHECK flag */

{

COLORREF crBackground; /* background color */

HBRUSH hbrBackground; /* background brush */

HBRUSH hbrTargetOld; /* original background brush */

HDC hdcSource; /* source device context */

HDC hdcTarget; /* target device context */

HBITMAP hbmpCheckboxes; /* handle of checkbox-bitmap */

BITMAP bmCheckbox; /* structure for bitmap data */

HBITMAP hbmpSourceOld; /* handle of original source bitmap */

HBITMAP hbmpTargetOld; /* handle of original target bitmap */

HBITMAP hbmpCheck; /* handle of check mark bitmap */

RECT rc; /* rectangle for checkbox bitmap */

DWORD dwCheckXY; /* dimensions of check mark bitmap */

WORD wBitmapX; /* width of check mark bitmap */

WORD wBitmapY; /* height of check mark bitmap */

/*

* Get the menu-background color and create a solid brush of

* that color.

*/

crBackground = GetSysColor(COLOR_MENU);

hbrBackground = CreateSolidBrush(crBackground);

/*

* Create memory device contexts for the source and

* destination bitmaps.

*/

hdcSource = CreateCompatibleDC((HDC) NULL);

hdcTarget = CreateCompatibleDC(hdcSource);

/*

* Get the size of the Windows default check mark bitmap and

* create a compatible bitmap of the same size.

*/

dwCheckXY = GetMenuCheckMarkDimensions();

wBitmapX = LOWORD(dwCheckXY);

wBitmapY = LOWORD(dwCheckXY);

hbmpCheck = CreateCompatibleBitmap(hdcSource, wBitmapX,

wBitmapY);

/*

* Select the background brush and bitmap into the target DC.

*/

hbrTargetOld = SelectObject(hdcTarget, hbrBackground);

hbmpTargetOld = SelectObject(hdcTarget, hbmpCheck);

/*

* Use the selected brush to initialize the background color

* of the bitmap in the target device context (DC).

*/

PatBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY, PATCOPY);

/*

* Load the predefined checkbox bitmaps and select it

* into the source DC.

*/

hbmpCheckboxes = LoadBitmap((HANDLE) NULL,

(LPTSTR) OBM_CHECKBOXES);

hbmpSourceOld = SelectObject(hdcSource, hbmpCheckboxes);

/*

* Fill a BITMAP structure with information about the

* checkbox bitmaps, and then find the upper-left corner of

* the unchecked checkbox or the checked checkbox.

*/

GetObject(hbmpCheckboxes, sizeof(BITMAP), &bmCheckbox);

if (fuCheck == UNCHECK) {

rc.left = 0;

rc.right = (bmCheckbox.bmWidth / 4);

}

else {

rc.left = (bmCheckbox.bmWidth / 4);

rc.right = (bmCheckbox.bmWidth / 4) * 2;

}

rc.top = 0;

rc.bottom = (bmCheckbox.bmHeight / 3);

/*

* Copy the appropriate bitmap into the target DC. If the

* checkbox bitmap is larger than the default check mark

* bitmap, use StretchBlt to make it fit. Otherwise, just

* copy it.

*/

if (((rc.right - rc.left) > (int) wBitmapX) ||

((rc.bottom - rc.top) > (int) wBitmapY))

StretchBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY,

hdcSource, rc.left, rc.top, rc.right - rc.left,

rc.bottom - rc.top, SRCCOPY);

else

BitBlt(hdcTarget, 0, 0, rc.right - rc.left,

rc.bottom - rc.top,

hdcSource, rc.left, rc.top, SRCCOPY);

/*

* Select the old source and destination bitmaps into the

* source and destination DCs, then delete the DCs and

* the background brush.

*/

SelectObject(hdcSource, hbmpSourceOld);

SelectObject(hdcTarget, hbrTargetOld);

hbmpCheck = SelectObject(hdcTarget, hbmpTargetOld);

DeleteObject(hbrBackground);

DeleteObject(hdcSource);

DeleteObject(hdcTarget);

/* Return the handle of the new check mark bitmap. */

return hbmpCheck;

}

BYTE CheckOrUncheckMenuItem(bMenuItemID, hmenu)

BYTE bMenuItemID;

HMENU hmenu;

{

DWORD fdwMenu;

static BYTE fbAttributes;

switch (bMenuItemID) {

case IDM_REGULAR:

/*

* Whenever the Regular menu item is selected, add a

* check mark to it and remove check marks from any

* font-attribute menu items.

*/

CheckMenuItem(hmenu, IDM_REGULAR, MF_BYCOMMAND |

MF_CHECKED);

if (fbAttributes & ATTRIBMASK) {

CheckMenuItem(hmenu, IDM_BOLD, MF_BYCOMMAND |

MF_UNCHECKED);

CheckMenuItem(hmenu, IDM_ITALIC, MF_BYCOMMAND |

MF_UNCHECKED);

CheckMenuItem(hmenu, IDM_ULINE, MF_BYCOMMAND |

MF_UNCHECKED);

}

fbAttributes = IDM_REGULAR;

return fbAttributes;

case IDM_BOLD:

case IDM_ITALIC:

case IDM_ULINE:

/*

* Toggle the check mark for the selected menu item and

* set the font attribute flags appropriately.

*/

fdwMenu = GetMenuState(hmenu, (UINT) bMenuItemID,

MF_BYCOMMAND);

if (!(fdwMenu & MF_CHECKED)) {

CheckMenuItem(hmenu, (UINT) bMenuItemID,

MF_BYCOMMAND | MF_CHECKED);

fbAttributes |= bMenuItemID;

} else {

CheckMenuItem(hmenu, (UINT) bMenuItemID,

MF_BYCOMMAND | MF_UNCHECKED);

fbAttributes ^= bMenuItemID;

}

/*

* If any font attributes are currently selected,

* remove the check mark from the Regular menu item;

* if no attributes are selected, add a check mark

* to the Regular item.

*/

if (fbAttributes & ATTRIBMASK) {

CheckMenuItem(hmenu, IDM_REGULAR,

MF_BYCOMMAND | MF_UNCHECKED);

fbAttributes &= (BYTE) ~IDM_REGULAR;

} else {

CheckMenuItem(hmenu, IDM_REGULAR,

MF_BYCOMMAND | MF_CHECKED);

fbAttributes = IDM_REGULAR;

}

return fbAttributes;

}

}