MENU.C


/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1993 - 1998 Microsoft Corp.
* All rights reserved.
* This source code is only intended as a supplement to
* Microsoft Development Tools and/or WinHelp documentation.
* See these sources for detailed information regarding the
* Microsoft samples programs.
\******************************************************************************/

/***************************************************************************
* *
* PROGRAM : Menu.c *
* *
* PURPOSE : To give a demonstration of the use of popup menus, user *
* defined menus and menu functions. *
* *
* FUNCTIONS : WinMain() - Calls the initialization function *
* and enters the message loop. *
* *
* MenuInit() - Registers the app. window class. *
* *
* About() - Dialog function for the About.. *
* dialog. *
* *
* ShrinkBitmap() - Shrinks a 64x64 bitmap to a size *
* useable for a user-defined menu *
* checkmark. *
* *
* HandleCreate() - Creates a new menu and appends it *
* to the main menu *
* *
* HandlePaint() - Handles repainting the app's client*
* area *
* *
* HandleChangeColors()- Changes the state of the "colors" *
* menu item. *
* *
* HandleDrawItem() - Redraws the menu items in the *
* "colors" menu *
* *
* HandlePopupMenu() - handles display of the "floating" *
* popup. *
* *
* MenuWndProc() - Window function for the app. *
* *
* *
***************************************************************************/
#include "windows.h"
#include "menu.h"


HANDLE hInst;
HMENU hBigMenu;
HBITMAP hbmCheckOn;
HBITMAP hbmCheckOff;

/****************************************************************************
* *
* FUNCTION : WinMain(HANDLE, HANDLE, LPSTR, int) *
* *
* PURPOSE : Creates the main app. window, calls an initialization *
* function and enters the message loop. *
* *
****************************************************************************/
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{

HWND hWnd;
MSG msg; /* message */

UNREFERENCED_PARAMETER( lpCmdLine );

/* Register main window class if this is the first instance of the app. */
if (!hPrevInstance)
if (!MenuInit (hInstance))
return 0;

hInst = hInstance;

/* Create the app. window */
hWnd = CreateWindow ("menu",
"Menu Example",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
(HWND) NULL,
NULL,
hInstance,
(LPSTR) NULL);

if (!hWnd)
return 0;

ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);

while (GetMessage (&msg, NULL, 0, 0)){
/* Since we have no accelerators, no need to call
* TranslateAccelerator here.
*/
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return(msg.wParam);
}


/****************************************************************************
* *
* FUNCTION : MenuInit (hInstance) *
* *
* PURPOSE : Registers the main window class. *
* *
* RETURNS : TRUE - if RegisterClass() went off ok *
* FALSE - otherwise. *
* *
****************************************************************************/
BOOL NEAR PASCAL MenuInit (HANDLE hInstance)
{
HANDLE hMemory;
PWNDCLASS pWndClass;
BOOL bSuccess;

/* Initialize the menu window class */
hMemory = LocalAlloc(LPTR, sizeof(WNDCLASS));
if(!hMemory){
MessageBox(NULL, "<MenuInit> Not enough memory.", NULL, MB_OK | MB_ICONHAND);
return(FALSE);
}

pWndClass = (PWNDCLASS) LocalLock(hMemory);

pWndClass->style = 0;
pWndClass->lpfnWndProc = (WNDPROC) MenuWndProc;
pWndClass->hInstance = hInstance;
pWndClass->hIcon = LoadIcon (hInstance, "menu");
pWndClass->hCursor = LoadCursor (NULL, IDC_ARROW);
pWndClass->hbrBackground = GetStockObject (WHITE_BRUSH);
pWndClass->lpszMenuName = (LPSTR) "MenuMenu",
pWndClass->lpszClassName = (LPSTR) "menu";

bSuccess = RegisterClass (pWndClass);
LocalUnlock (hMemory);
LocalFree (hMemory);

return bSuccess;
}


/****************************************************************************
* *
* FUNCTION : About (hDlg, message, wParam, lParam) *
* *
* PURPOSE : Dialog function for the About menu... dialog. *
* *
****************************************************************************/
BOOL APIENTRY About(
HWND hDlg,
UINT message,
UINT wParam,
LONG lParam)

{
switch (message){
case WM_INITDIALOG:
return(TRUE);

case WM_COMMAND:
// LOWORD added for portability
if (LOWORD(wParam) == IDOK){
EndDialog(hDlg,0);
return(TRUE);
}
break;
}
return(FALSE);
UNREFERENCED_PARAMETER(lParam);
}


/****************************************************************************
* *
* FUNCTION : ShrinkBitmap(hwnd, hbm) *
* *
* PURPOSE : This function shrinks a 64x64 bitmap into a bitmap useable *
* for the user-defined checkmark for menu items. This can be *
* easily generalized to shrink bitmaps of any size. *
* *
* RETURNS : HBITMAP - A handle to the new bitmap. *
* *
****************************************************************************/
HBITMAP APIENTRY ShrinkBitmap (
HWND hwnd,
HBITMAP hbm)
{
HDC hdc;
HDC hmemorydcNew;
HDC hmemorydcOld;
LONG checkMarkSize;
HBITMAP hCheckBitmap;
HBITMAP hOldBitmapSave;
HBITMAP hNewBitmapSave;

hdc = GetDC (hwnd);

/* Create DCs for the source (old) and target (new) bitmaps */
hmemorydcNew = CreateCompatibleDC (hdc);
hmemorydcOld = CreateCompatibleDC (hdc);

/* Determine the dimensions of the default menu checkmark and
* create a target bitmap of the same dimensions
*/
checkMarkSize = GetMenuCheckMarkDimensions ();
hCheckBitmap = CreateCompatibleBitmap (hdc,
LOWORD (checkMarkSize),
HIWORD (checkMarkSize));

/* Select the source bitmap and the target bitmap into their
* respective DCs.
*/
hOldBitmapSave = SelectObject (hmemorydcNew, hCheckBitmap);
hNewBitmapSave = SelectObject (hmemorydcOld, hbm);

/* Shrink the source bitmap into the target DC */
StretchBlt (hmemorydcNew,
0,
0,
LOWORD(checkMarkSize),
HIWORD(checkMarkSize),
hmemorydcOld,
0,
0,
64,
64,
SRCCOPY);

/* De-select the bitmaps and clean up .. */
SelectObject (hmemorydcNew, hOldBitmapSave);
SelectObject (hmemorydcOld, hNewBitmapSave);
DeleteDC (hmemorydcNew);
DeleteDC (hmemorydcOld);
ReleaseDC (hwnd, hdc);

/* .. and return a handle to the target bitmap */
return hCheckBitmap;
}


/****************************************************************************
* *
* FUNCTION : HandleCreate ( hwnd ) *
* *
* PURPOSE : Creates a new (empty) menu and appends to it the "State" *
* menu items. It sets up the user-defined checkmarks for the *
* menu. It then inserts this menu into the main menu bar. *
* *
****************************************************************************/
VOID APIENTRY HandleCreate (HWND hwnd)
{
HMENU hMenu;
HMENU hWndMenu;

/* Create a new menu into the menubar on the fly */
hMenu = CreateMenu ();
if (!hMenu)
return;

/* Append the state menu items to it */
AppendMenu (hMenu, MF_STRING, IDM_STATE1, "South Dakota");
AppendMenu (hMenu, MF_STRING, IDM_STATE2, "Washington");
AppendMenu (hMenu, MF_STRING, IDM_STATE3, "California");
if (!AppendMenu (hMenu, MF_STRING, IDM_STATE4, "Oregon")){
/* It is unlikely the other appends will fail and this will succeed.
* So just check this one. And if it fails, Destroy the menu for
* good measure and return.
*/
DestroyMenu(hMenu);
return;
}
hbmCheckOn = ShrinkBitmap (hwnd, LoadBitmap (hInst, "checkon"));
hbmCheckOff = ShrinkBitmap (hwnd, LoadBitmap (hInst, "checkoff"));

/* Set up the user-defined check marks */
SetMenuItemBitmaps (hMenu, 0, MF_BYPOSITION, hbmCheckOff, hbmCheckOn);
SetMenuItemBitmaps (hMenu, 1, MF_BYPOSITION, hbmCheckOff, hbmCheckOn);
SetMenuItemBitmaps (hMenu, 2, MF_BYPOSITION, hbmCheckOff, hbmCheckOn);
SetMenuItemBitmaps (hMenu, 3, MF_BYPOSITION, hbmCheckOff, hbmCheckOn);

/* Now insert the menu into the main menu bar. */
hWndMenu = GetMenu (hwnd);
InsertMenu (hWndMenu, 2, MF_POPUP|MF_BYPOSITION, (DWORD)hMenu, "States");

return;
}

/****************************************************************************
* *
* FUNCTION : HandlePaint ( hwnd ) *
* *
* PURPOSE : Handles the repainting of the main app's client area. *
* *
****************************************************************************/
VOID APIENTRY HandlePaint (HWND hwnd)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;

hdc = BeginPaint (hwnd, (LPPAINTSTRUCT)&ps);

/* Center the text in the client area */
GetClientRect (hwnd, (LPRECT)&rc);
DrawText (hdc,
"Down click in the window for a popup menu",
41,
(LPRECT)&rc,
DT_CENTER | DT_WORDBREAK);
EndPaint(hwnd, (LPPAINTSTRUCT)&ps);
}


/****************************************************************************
* *
* FUNCTION : HandleChangeColors (hwnd) *
* *
* PURPOSE : Toggles the state of the Owner Draw item in the Colors *
* menu. If it is on, the "Black", "Blue", "Red", and "Green" *
* individual menu text items are modified so that they will *
* contain bands of color. Otherwise, the colors are replaced *
* by the text. *
* *
****************************************************************************/
VOID APIENTRY HandleChangeColors(HWND hwnd)
{
HMENU hMenu;
BOOL fOwnerDraw;

/* Get a handle to the Colors menu. This is at position 1. */
hMenu = GetSubMenu (GetMenu (hwnd), IDCOLORS_POS);

/* Get the current state of the item */
fOwnerDraw = GetMenuState ( hMenu,
IDM_COLOROWNERDR, MF_BYCOMMAND) & MF_CHECKED;

/* Toggle the state of the item. */
CheckMenuItem ( hMenu,
IDM_COLOROWNERDR,
MF_BYCOMMAND | (fOwnerDraw ? MF_UNCHECKED : MF_CHECKED));

if (!fOwnerDraw){
/* Change the items to owner-draw items. Pass the RGB value for the
* color as the application-supplied data. This makes it easier for
* us to draw the items.
*/
ModifyMenu(hMenu,
IDM_BLACK,
MF_OWNERDRAW | MF_BYCOMMAND,
IDM_BLACK,
(LPSTR)RGB (0,0,0));

ModifyMenu(hMenu,
IDM_BLUE,
MF_OWNERDRAW | MF_BYCOMMAND,
IDM_BLUE,
(LPSTR)RGB (0,0,255));

ModifyMenu(hMenu,
IDM_RED,
MF_OWNERDRAW | MF_BYCOMMAND,
IDM_RED,
(LPSTR)RGB (255,0,0));

ModifyMenu(hMenu,
IDM_GREEN,
MF_OWNERDRAW | MF_BYCOMMAND,
IDM_GREEN,
(LPSTR)RGB (0,255,0));
}
else {
/* Change the items to normal text items. */
ModifyMenu(hMenu, IDM_BLACK, MF_BYCOMMAND, IDM_BLACK, "Black");

ModifyMenu(hMenu, IDM_BLUE, MF_BYCOMMAND, IDM_BLUE, "Blue");

ModifyMenu(hMenu, IDM_RED, MF_BYCOMMAND, IDM_RED, "Red");

ModifyMenu(hMenu, IDM_GREEN, MF_BYCOMMAND, IDM_GREEN, "Green");
}
}


/****************************************************************************
* *
* FUNCTION : HandleDrawItem ( hwnd, lpdis) *
* *
* PURPOSE : Called in response to a WM_DRAWITEM message, i.e. when the *
* colors menu is being modified to an owner-draw menu, or *
* one of the items is selected. It sizes the checkmark bitmap*
* to fit next to a color band and draws the color bands and *
* the checkmark on the popup menu. *
* *
****************************************************************************/
VOID APIENTRY HandleDrawItem(
HWND hwnd,
LPDRAWITEMSTRUCT lpdis)

{
HDC hdcBitmap;
HBITMAP hbmSave;
HBRUSH hbr;
RECT rc;
LONG checkMarkSize;
DWORD textColorSave;
DWORD bkColorSave;

/* Get the size of the checkmark so we can leave room for it since we
* want to be able to check the selected color.
*/
checkMarkSize = GetMenuCheckMarkDimensions ();

if (lpdis->itemAction == ODA_SELECT ||
lpdis->itemAction == ODA_DRAWENTIRE){

CopyRect ((LPRECT)&rc, (LPRECT)&lpdis->rcItem);
InflateRect ((LPRECT)&rc, (-2 - LOWORD(checkMarkSize)), -2);

if (lpdis->itemState & ODS_SELECTED)
{
/* Item has been selected -- hilite with a gray frame */
hbr = GetStockObject (GRAY_BRUSH);
FrameRect (lpdis->hDC, (LPRECT)&rc, hbr);
}
else if (lpdis->itemAction == ODA_SELECT)
{
/* Item has been de-selected -- remove gray frame */
hbr = CreateSolidBrush (GetSysColor (COLOR_MENU));
FrameRect (lpdis->hDC, (LPRECT)&rc, hbr);
DeleteObject (hbr);
}
}

if (lpdis->itemAction == ODA_DRAWENTIRE){

/* Paint the color item in the color requested. */
hbr = CreateSolidBrush (lpdis->itemData);
CopyRect ((LPRECT)&rc, (LPRECT)&lpdis->rcItem);
InflateRect ((LPRECT)&rc, -10-LOWORD(checkMarkSize), -10);
FillRect (lpdis->hDC, (LPRECT)&rc, hbr);
DeleteObject (hbr);

if (lpdis->itemState & ODS_CHECKED){
/* Draw the check mark if the item is checked. */
hdcBitmap = CreateCompatibleDC (lpdis->hDC);
hbmSave = SelectObject (hdcBitmap, hbmCheckOn);

textColorSave = SetTextColor (lpdis->hDC, 0x00000000L);
bkColorSave = SetBkColor (lpdis->hDC, 0x00FFFFFFL);

/* Use Magic bitblt op so that monochrome bitmaps preserve
background and foreground colors. */
BitBlt (lpdis->hDC,
lpdis->rcItem.left,
lpdis->rcItem.top+
(MEASUREITEMHEIGHT - HIWORD (checkMarkSize)) / 2,
LOWORD (checkMarkSize),
HIWORD (checkMarkSize),
hdcBitmap,
0,
0,
ROP_PSDPxax);

/* Restore colors and bitmap and clean up */
SetTextColor (lpdis->hDC, textColorSave);
SetBkColor (lpdis->hDC, bkColorSave);
SelectObject (hdcBitmap, hbmSave);
DeleteDC (hdcBitmap);

}
}
UNREFERENCED_PARAMETER(hwnd);
}

/****************************************************************************
* *
* FUNCTION : HandlePopupMenu (hwnd, point) *
* *
* PURPOSE : Handles the display of the "floating" popup that appears * *
* on a mouse click in the app's client area. *
* *
****************************************************************************/
VOID APIENTRY HandlePopupMenu (
HWND hwnd,
POINT point)

{
HMENU hMenu;
HMENU hMenuTrackPopup;

/* Get the menu for the popup from the resource file. */
hMenu = LoadMenu (hInst, "PopupMenu");
if (!hMenu)
return;

/* Get the first menu in it which we will use for the call to
* TrackPopup(). This could also have been created on the fly using
* CreatePopupMenu and then we could have used InsertMenu() or
* AppendMenu.
*/
hMenuTrackPopup = GetSubMenu (hMenu, 0);

/* Convert the mouse point to screen coordinates since that is what
* TrackPopup expects.
*/
ClientToScreen (hwnd, (LPPOINT)&point);

/* Draw and track the "floating" popup */
TrackPopupMenu (hMenuTrackPopup, 0, point.x, point.y, 0, hwnd, NULL);

/* Destroy the menu since were are done with it. */
DestroyMenu (hMenu);
}

/****************************************************************************
* *
* FUNCTION : MenuWndProc (hWnd, message, wParam, lParam) *
* *
* PURPOSE : Window function for the main app. window. Processes all the*
* menu selections and oter messages. *
* *
****************************************************************************/
LONG APIENTRY MenuWndProc (
HWND hWnd,
UINT message,
UINT wParam,
LONG lParam)

{
HMENU hMenu;
RECT rc;
POINT pt;


switch (message){
case WM_SYSCOMMAND:
/* Show the About ... dialog */
if (wParam == ID_ABOUT){
DialogBox (hInst,
"AboutBox",
hWnd,
About);

break;
}
else
return DefWindowProc (hWnd, message, wParam, lParam);

case WM_COMMAND:
// LOWORD added for portability
switch (LOWORD(wParam)){
case IDM_EXIT:
DestroyWindow (hWnd);
break;

case IDM_ABOUT:
/* Bring up the About.. dialog box */
DialogBox (hInst,
"AboutBox",
hWnd,
About);

break;

case IDM_COLOROWNERDR:
/* Change colors in color menu depending on state of this
menu item. */
HandleChangeColors (hWnd);
break;

case IDM_STATE1:
case IDM_STATE2:
case IDM_STATE3:
case IDM_STATE4:
/* Get a handle to the states menu... */
hMenu = GetSubMenu (GetMenu (hWnd), IDSTATES_POS);

/* Uncheck all the items. */
CheckMenuItem (hMenu, IDM_STATE1,
MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_STATE2,
MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_STATE3,
MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_STATE4,
MF_BYCOMMAND | MF_UNCHECKED);

/* ...and just check the selected one.*/
CheckMenuItem (hMenu, (WORD)wParam,
MF_BYCOMMAND | MF_CHECKED);
break;

case IDM_BLACK:
case IDM_RED:
case IDM_BLUE:
case IDM_GREEN:
/* Get a handle to the Colors menu. */
hMenu = GetSubMenu (GetMenu (hWnd),IDCOLORS_POS);

/* Uncheck all the items. */
CheckMenuItem (hMenu, IDM_BLACK,
MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_RED,
MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_BLUE,
MF_BYCOMMAND | MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_GREEN,
MF_BYCOMMAND | MF_UNCHECKED);

/* ...and just check the selected one.*/
CheckMenuItem (hMenu, (WORD)wParam,
MF_BYCOMMAND | MF_CHECKED);
break;

case IDM_FONT:
/* Messages sent to us from TrackPopupMenu when
* items are selected from the "floating" popups
*/
MessageBox (hWnd,
"A font was selected",
"Popup Menu Alert",
MB_APPLMODAL|MB_OK);
break;

case IDM_SIZE:
MessageBox (hWnd,
"A size was selected",
"Popup Menu Alert",
MB_APPLMODAL|MB_OK);
break;

case IDM_STYLE:
MessageBox (hWnd,
"A style was selected",
"Popup Menu Alert",
MB_APPLMODAL|MB_OK);
break;
}
break;

case WM_SIZE:
if (lParam){
/* If window is being sized to a non zero value...
* invalidate it's client area.
*/
InvalidateRect (hWnd, NULL, TRUE);
}
break;

case WM_PAINT:
HandlePaint (hWnd);
break;

case WM_MEASUREITEM:
/* Use the same width for all items. We could examine the item id
and use different widths/heights for each item. */
((LPMEASUREITEMSTRUCT)lParam)->itemWidth = MEASUREITEMWIDTH;
((LPMEASUREITEMSTRUCT)lParam)->itemHeight = MEASUREITEMHEIGHT;
return TRUE;

case WM_DRAWITEM:
/* Redraw the "colors" menu in normal/ownerdrawmode */
HandleDrawItem (hWnd,(LPDRAWITEMSTRUCT)lParam);
return TRUE;
break;

case WM_CREATE:
/* Create the menu */
HandleCreate (hWnd);
break;

case WM_DESTROY:
/* Delete the on/off bitmaps so that they don't waste memory. */
DeleteObject (hbmCheckOn);
DeleteObject (hbmCheckOff);

PostQuitMessage (0);
break;

case WM_LBUTTONDOWN:
/* Draw the "floating" popup in the app's client area */
GetClientRect (hWnd, (LPRECT)&rc);

// Temporary porting macro
LONG2POINT(lParam, pt);
if (PtInRect ((LPRECT)&rc, pt))
HandlePopupMenu (hWnd, pt);
break;

default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return(0);
}