Chapter 9  Menus and Accelerators
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Menus are an important part of the consistent user interface that Windows
programs offer. Adding a menu to your program is a relatively easy part of
Windows programming: You simply define the structure of the menu in your
resource script and assign a unique ID number to each menu item. You specify
the name of the menu in the window class structure. When the user chooses a
menu item, Windows sends your program a WM_COMMAND message containing that
ID. But we won't stop with that simple example. One of the more interesting
things you can do with menus is display bitmaps in the menu rather than
character strings, so we'll take a detailed look at how that is done.

This chapter also covers "keyboard accelerators." These are key combinations
that are used primarily to duplicate menu functions.

MENUS

A window's menu bar is displayed immediately below the caption bar. This
menu bar is sometimes called a program's "main menu" or the "top-level
menu." Items listed in the top-level menu almost always invoke a drop-down
menu, which is called either a "popup menu" or a "submenu." Beginning with
Windows 3, you can define multiple nestings of popups: that is, an item on a
popup menu can invoke another popup menu. Sometimes items in popup menus
invoke a dialog box for more information. (Dialog boxes are covered in
Chapter 10.) Most parent windows have, to the far left of the caption bar, a
box containing a single line. This box invokes the system menu, which is
really another popup menu.

Menu items in popups can be "checked," which means that Windows draws a
small check mark to the left of the menu text. The use of check marks lets
the user choose different program options from the menu. These options can
be mutually exclusive, but they don't have to be. Top-level menu items
cannot be checked.

Menu items in the top-level menu or in popup menus can be "enabled,"
"disabled," or "grayed." The words "active" and "inactive" are sometimes
used synonymously with "enabled" and "disabled." Menu items flagged as
enabled or disabled look the same to the user, but a grayed menu item is
displayed in gray text.

From the perspective of the user, enabled, disabled, and grayed menu items
can all be "selected" (highlighted). That is, the user can click the mouse
on a disabled menu item, or move the reverse-video cursor bar to a disabled
menu item, or trigger the menu item using the item's key letter. However,
from the perspective of your program, enabled, disabled, and grayed menu
items function differently. Windows sends your program a WM_COMMAND message
only for enabled menu items. You use disabled and grayed menu items for
options that are not currently valid. If you want to let the user know the
option is not valid, make it grayed.

Menu Structure

When you create or change menus in a program, it's useful to think of the
top-level menu and each popup menu as being separate menus. The top-level
menu has a menu handle, each popup menu within a top-level menu has its own
menu handle, and the system menu (which is also a popup) has a menu handle.

Each item in a menu is defined by three characteristics: The first
characteristic is what appears in the menu. This is either a text string or
a bitmap. The second characteristic is either an ID number that Windows
sends to your program in a WM_COMMAND message or a popup menu that Windows
displays when the user chooses that menu item. The third characteristic
describes the attribute of the menu item, including whether the item is
disabled, grayed, or checked.


The Menu Template

You can create a menu in three different ways. The most common (and the
easiest) is to define the menu in your resource script in the form of a menu
template. This example shows all the different options you can use in this
template.

MyMenu MENU [load option] [memory option]
     {
     MENUITEM "&One",   1
     POPUP "&Two"
          {
          MENUITEM "&Ten",       10, CHECKED
          MENUITEM "&Eleven",    11
          MENUITEM SEPARATOR
          MENUITEM "T&welve",    12, INACTIVE
          MENUITEM "T&hirteen",  13
          MENUITEM "&Fourteen",  14, MENUBREAK
          MENUITEM "F&ifteen",   15,
          MENUITEM "&Sixteen",   16, MENUBARBREAK
          POPUP    "Se&venteen", 17,
               {
               MENUITEM "&Twenty",      20
               MENUITEM "T&wenty-One",  21
               MENUITEM "Tw&enty-Two",  22
               }
          MENUITEM "Ei&ghteen",  18, GRAYED
          }
     MENUITEM "Th&ree",  3
     MENUITEM "&Four",   4, INACTIVE
     MENUITEM "Fi&ve",   5
     MENUITEM "Si&x",    6, MENUBREAK
     MENUITEM "&Seven",  7,
     MENUITEM "&Eight",  8, GRAYED
     MENUITEM "\a&Help", 9, HELP
     }

This particular menu template defines a top-level menu that displays the
labels "One" through "Eight" and "Help," as shown in Figure 9-1 on the
following page. Only the second item invokes a popup menu. The popup menu
displays the labels "Ten" through "Eighteen," as shown in Figure 9-2 on the
following page. The little arrow to the right of the "Seventeen" option
indicates that it invokes yet another popup menu.

MyMenu is the name of the menu. This name performs the same function as the
names of icon, cursor, and bitmap resources discussed in Chapter 8. As with
other resources, the load option on the MENU statement can be either PRELOAD
(in which case Windows loads the resource into memory when the program is
executed) or LOADONCALL (in which case Windows loads the resource into
memory only when it is needed). The default is LOADONCALL. The memory
options are FIXED, MOVEABLE, and DISCARDABLE. The default is MOVEABLE and
DISCARDABLE. Discardable menus must also be moveable. Although we'll change
menus in some of the programs shown later, don't worry that the menu
resource is discardable. Windows makes a copy of the menu for your program
to use and change.

  (Figure 9-1. may be found in the printed book.)

  (Figure 9-2. may be found in the printed book.)

The top-level menu is enclosed in left and right brackets. (You can use
BEGIN and END statements instead if you wish.) The two types of statements
allowed within these brackets are:

MENUITEM "text", wID, options

and:

POPUP "text", options

The text displayed for each menu must be enclosed in double quotation marks.
An ampersand (&) causes the character that follows it to be underlined when
Windows displays the menu. This is also the character Windows searches for
when you select a menu item using the Alt key. If you don't include an
ampersand in the text, no underline will appear, and Windows will use
instead the first letter of the text for Alt-key searches.

The options on the MENUITEM and POPUP statements that appear in the
top-level menu list are as follows:

  þ   GRAYED--The menu item is inactive, and it does not generate a
      WM_COMMAND message. The text is grayed.

  þ   INACTIVE--The menu item is inactive, and it does not generate a
      WM_COMMAND message. The text is displayed normally.

  þ   MENUBREAK--This item and following items appear on a new line of the
      menu.

  þ   HELP--When used in combination with \a before the text, this item is
      right-justified.

Options can be combined using the C bitwise OR symbol (|), but GRAYED and
INACTIVE cannot be used together. MENUBREAK is uncommon in a top-level menu,
because Windows automatically separates a top-level menu into multiple lines
if the window is too narrow to fit the entire menu.

Following a POPUP statement in the main menu, the left and right brackets
(or the BEGIN and END keywords) block off a list of items in the popup. The
following statements are allowed in a popup definition:

MENUITEM "text", wID, options

and:

MENUITEM SEPARATOR

and:

POPUP  "text", options

MENUITEM SEPARATOR draws a horizontal line in the popup menu. This line is
often used to separate groups of related options.

For items in popup menus, you can use the columnar tab character \t in the
text string. Text following the \t is placed in a new column spaced far
enough to the right to accommodate the longest text string in the first
column of the popup. We'll see how this works when discussing keyboard
accelerators toward the end of this chapter. A \a right-justifies the text
that follows it. The options for MENUITEM in a popup are as follows:

  þ   CHECKED--A check mark appears to the left of the text.

  þ   GRAYED--The menu item is inactive and does not generate a WM_COMMAND
      message. The text is grayed.

  þ   INACTIVE--The menu item is inactive and does not generate a WM_COMMAND
      message. The text is displayed normally.

  þ   MENUBREAK--This item and the following items appear in a new column of
      the menu.

  þ   MENUBARBREAK--This item and the following items appear in a new column
      of the menu. A vertical line separates the columns.

GRAYED and INACTIVE cannot be used together. MENUBREAK and MENUBARBREAK
cannot be used together. You should use either MENUBREAK or MENUBARBREAK
when the number of items in a popup is too long to be displayed in a single
column.

The wID values in the MENUITEM statements are the numbers that Windows sends
to the window procedure in menu messages. The wID values should be unique
within a menu. Instead of using numbers, you'll probably want to use
identifiers defined in a header file. By convention, these identifiers begin
with the letters IDM ("ID for a menu").


Referencing the Menu in Your Program

Most Windows applications have only one menu in the resource script. The
program makes reference to this menu in the definition of the window class:

wndclass.lpszMenuName = "MyMenu" ;

Programmers often use the name of the program as the name of the menu so
that the same text string can also be used for the window class, the name of
the program's icon, and the name of the menu. However, you can also use a
number (or a macro identifier) for the menu rather than a name. The resource
script would look like this:

45 MENU
     {
[menu definition]
     }

In this case, the assignment statement for the lpszMenuName field of the
window class structure can be either:

wndclass.lpszMenuName = MAKEINTRESOURCE (45) ;

or:

wndclass.lpszMenuName = "#45" ;

Although specifying the menu in the window class is the most common way to
reference a menu resource, you have alternatives. A Windows application can
load a menu resource into memory with the LoadMenu function, which is
similar to the LoadIcon and LoadCursor functions described in Chapter 8. If
you use a name for the menu in the resource script, LoadMenu returns a
handle to the menu:

hMenu = LoadMenu (hInstance, "MyMenu") ;

If you use a number, the LoadMenu call takes either this form:

hMenu = LoadMenu (hInstance, MAKEINTRESOURCE (45)) ;

or this form:

hMenu = LoadMenu (hInstance, "#45") ;

You can then specify this menu handle as the ninth parameter to
CreateWindow:

hwnd = CreateWindow ("MyClass", "Window Caption",
               WS_OVERLAPPEDWINDOW,
               CW_USEDEFAULT,CW_USEDEFAULT,
               CW_USEDEFAULT,CW_USEDEFAULT,
               NULL,
               hMenu,
               hInstance,
               NULL) ;

In this case, the menu specified in the CreateWindow call overrides any menu
specified in the window class. You can think of the menu in the window class
as being a default menu for the windows based on the window class if the
ninth parameter to CreateWindow is NULL. Therefore, you can use different
menus for several windows based on the same window class.

You can also have a NULL menu in the window class and a NULL menu in the
CreateWindow call and assign a menu to a window after the window has been
created:

SetMenu (hwnd, hMenu) ;

This form lets you dynamically change a window's menu. We'll see an example
of this in the NOPOPUPS program shown later in this chapter.


Menus and Messages

Windows usually sends a window procedure several different messages when the
user selects a menu item. In most cases your program can ignore many of
these messages and simply pass them to DefWindowProc. Let's take a look at
them anyway.

The first message your program receives when the user selects a menu item
with the keyboard or mouse is a WM_SYSCOMMAND message. The values of wParam
and lParam are shown below:

           wParam  lParam
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Mouse:     F09x    0
Keyboard:  F10x    0

The WINDOWS.H identifier SC_MOUSEMENU is equal to F090H; SC_KEYMENU is
F100H, but the last digit in wParam (indicated by an x in the table above)
can be anything. Use:

(wParam & 0xFFF0)

if you need to check this value. Most programs pass these messages to
DefWindowProc.

The second message your program receives is a WM_INITMENU message with the
following parameters:

wParam               LOWORD (lParam)  HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Handle to main menu  0                0

The value of wParam is the handle to your main menu even if the user is
selecting an item from the system menu. Windows programs generally ignore
the WM_INITMENU message. Although the message exists to give you the
opportunity to change the menu before an item is chosen, I suspect any
changes to the top-level menu at this time would be very disconcerting to
the user.

The next message your program receives is WM_MENUSELECT. A program can
receive many WM_MENUSELECT messages as the user moves the cursor or mouse
among the menu items. The parameters that accompany WM_SELECT are as
follows:

wParam                       LOWORD (lParam)  HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Selected item: Menu ID or    Selection flags  Handle to menu containing
popup menu handle                             selected item


WM_MENUSELECT is a menu-tracking message. The value of wParam tells you what
item of the menu is currently selected (highlighted). The "selection flags"
in the low word of lParam can be a combination of the following: MF_GRAYED,
MF_DISABLED, MF_CHECKED, MF_BITMAP, MF_POPUP, MF_HELP, MF_SYSMENU, and
MF_MOUSESELECT. You may want to use WM_MENUSELECT if you need to change
something in the client area of your window based on the movement of the
highlight among the menu items. Most programs pass this message to
DefWindowProc.

When Windows is ready to display a popup menu, it sends the window procedure
a WM_INITMENUPOPUP message with the following parameters:

wParam             LOWORD (lParam)  HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Popup menu handle  Popup index      1 for system menu, 0 otherwise

This message is important if you need to enable or disable items in a popup
menu before it is displayed. For instance, suppose your program can copy
text from the clipboard using the Paste command on a popup menu. When you
receive a WM_INITMENUPOPUP message for that popup, you should determine if
the clipboard has text in it. If it doesn't, you should gray the Paste menu
item. We'll see an example of this in the revised POPPAD program shown
toward the end of this chapter.

The most important menu message is WM_COMMAND. This message indicates that
the user has chosen an enabled menu item from your window's menu. You'll
recall from Chapter 6 that WM_COMMAND messages also result from child window
controls. If you happen to use the same ID codes for menus and child window
controls, you can differentiate between them by the low word of lParam,
which will be 0 for a menu item:

          wParam      LOWORD (lParam)      HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Menu:     Menu ID     0                    0
Control:  Control ID  Child window handle  Notification code

The WM_SYSCOMMAND message is similar to the WM_COMMAND message except that
WM_SYSCOMMAND signals that the user has chosen an enabled menu item from the
system menu:

              wParam   LOWORD (lParam)  HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
System menu:  Menu ID  0                0

The menu ID indicates which item on the system menu has been chosen. For the
predefined system menu items, the bottom four bits should be masked out. The
resultant value will be one of the following: SC_SIZE, SC_MOVE, SC_MINIMIZE,
SC_MAXIMIZE, SC_NEXTWINDOW, SC_PREVWINDOW, SC_CLOSE, SC_VSCROLL, SC_HSCROLL,
SC- _ARRANGE, SC_RESTORE, and SC_TASKLIST. In addition, wParam can be
SC_MOUSEMENU or SC_KEYMENU, as indicated earlier.

If you add menu items to the system menu, wParam will be the menu ID that
you define. To avoid conflicts with the predefined menu IDs, use values
below F000H. It is important that you pass normal WM_SYSCOMMAND messages to
DefWindowProc. If you do not, you'll effectively disable the normal system
menu commands.

The final message we'll discuss is WM_MENUCHAR, which isn't really a menu
message at all. Windows sends this message to your window procedure in one
of two circumstances: if the user presses Alt and a character key that does
not correspond to a menu item, or, when a popup is displayed, if the user
presses a character key that does not correspond to an item in the popup.
The parameters that accompany the WM_MENUCHAR message are as follows:

wParam      LOWORD (lParam)  HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
ASCII code  Selection code   Handle to menu

The selection code is:

  þ   0--No popup is displayed.

  þ   MF_POPUP--Popup is displayed.

  þ   MF_SYSMENU--System menu popup is displayed.

Windows programs usually pass this message to DefWindowProc, which normally
returns a 0 to Windows, which causes Windows to beep. We'll see a use for
the WM_MENUCHAR message in the GRAFMENU program shown later in this chapter.


A Sample Program

Let's look at a simple example. The MENUDEMO program, shown in Figure 9-3,
has five items in the main menu--File, Edit, Background, Timer, and Help.
Each of these items has a popup. MENUDEMO does the simplest and most common
type of menu processing, which involves trapping WM_COMMAND messages and
checking the value of wParam.

 MENUDEMO.MAK

#------------------------
# MENUDEMO.MAK make file
#------------------------

menudemo.exe : menudemo.obj menudemo.def menudemo.res
     link menudemo, /align:16, NUL, /nod slibcew libw, menudemo
     rc menudemo.res

menudemo.obj : menudemo.c menudemo.h
     cl -c -Gsw -Ow -W2 -Zp menudemo.c

menudemo.res : menudemo.rc menudemo.h
     rc -r menudemo.rc

 MENUDEMO.C

/*-----------------------------------------
   MENUDEMO.C -- Menu Demonstration
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 
#include "menudemo.h"

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

char szAppName [] = "MenuDemo" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Menu Demonstration",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;     ShowWindow
(hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static int  wColorID [5] = { WHITE_BRUSH,  LTGRAY_BRUSH, GRAY_BRUSH,
                                  DKGRAY_BRUSH, BLACK_BRUSH } ;
     static WORD wSelection = IDM_WHITE ;
     HMENU       hMenu ;

     switch (message)
          {
          case WM_COMMAND :
               hMenu = GetMenu (hwnd) ;

               switch (wParam)
                    {
                    case IDM_NEW :
                    case IDM_OPEN :
                    case IDM_SAVE :
                    case IDM_SAVEAS :
                         MessageBeep (0) ;
                         return 0 ;

                    case IDM_EXIT :
                         SendMessage (hwnd, WM_CLOSE, 0, 0L) ;
                         return 0 ;

                    case IDM_UNDO :
                    case IDM_CUT :
                    case IDM_COPY :
                    case IDM_PASTE :
                    case IDM_CLEAR :
                         MessageBeep (0) ;
                         return 0 ;

                    case IDM_WHITE :          // Note: Logic below
                    case IDM_LTGRAY :         //   assumes that IDM_WHITE
                    case IDM_GRAY :           //   through IDM_BLACK are
                    case IDM_DKGRAY :         //   consecutive numbers in
                    case IDM_BLACK :          //   the order shown here.
CheckMenuItem (hMenu, wSelection, MF_UNCHECKED) ;
                         wSelection = wParam ;
                         CheckMenuItem (hMenu, wSelection, MF_CHECKED) ;

                         SetClassWord (hwnd, GCW_HBRBACKGROUND,
                              GetStockObject (wColorID [wParam -
IDM_WHITE])) ;

                         InvalidateRect (hwnd, NULL, TRUE) ;
                         return 0 ;

                    case IDM_START :
                         if (SetTimer (hwnd, 1, 1000, NULL))
                              {
                              EnableMenuItem (hMenu, IDM_START, MF_GRAYED) ;
                              EnableMenuItem (hMenu, IDM_STOP,  MF_ENABLED)
;
                              }
                         return 0 ;

                    case IDM_STOP :
                         KillTimer (hwnd, 1) ;
                         EnableMenuItem (hMenu, IDM_START, MF_ENABLED) ;
                         EnableMenuItem (hMenu, IDM_STOP,  MF_GRAYED) ;
                         return 0 ;

                    case IDM_HELP :
                         MessageBox (hwnd, "Help not yet implemented.",
                                     szAppName, MB_ICONINFORMATION | MB_OK)
;
                         return 0 ;

                    case IDM_ABOUT :
                         MessageBox (hwnd, "Menu Demonstration Program.",
                                     szAppName, MB_ICONINFORMATION | MB_OK)
;
                         return 0 ;
                    }
               break ;

          case WM_TIMER :
               MessageBeep (0) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 MENUDEMO.RC

/*-----------------------------
   MENUDEMO.RC resource script
  -----------------------------*/

#include "menudemo.h"

MenuDemo MENU
     {
     POPUP "&File"
          {
          MENUITEM "&New",                   IDM_NEW
          MENUITEM "&Open...",               IDM_OPEN
          MENUITEM "&Save",                  IDM_SAVE
          MENUITEM "Save &As...",            IDM_SAVEAS
          MENUITEM SEPARATOR
          MENUITEM "E&xit",                  IDM_EXIT
          }
     POPUP "&Edit"
          {
          MENUITEM "&Undo",                  IDM_UNDO
          MENUITEM SEPARATOR
          MENUITEM "Cu&t",                   IDM_CUT
          MENUITEM "&Copy",                  IDM_COPY
          MENUITEM "&Paste",                 IDM_PASTE
          MENUITEM "C&lear",                 IDM_CLEAR
          }
     POPUP "&Background"
          {
          MENUITEM "&White",                 IDM_WHITE, CHECKED
          MENUITEM "&Lt Gray",               IDM_LTGRAY
          MENUITEM "&Gray",                  IDM_GRAY
          MENUITEM "&Dk Gray",               IDM_DKGRAY
          MENUITEM "&Black",                 IDM_BLACK
          }
     POPUP "&Timer"
          {
          MENUITEM "&Start"                  IDM_START
          MENUITEM "S&top"                   IDM_STOP,  GRAYED
          }
     POPUP "&Help"
          {
          MENUITEM "&Help",                  IDM_HELP
          MENUITEM "&About MenuDemo...",     IDM_ABOUT
          }
     }

;FC

 MENUDEMO.H

/*------------------------
   MENUDEMO.H header file
  ------------------------*/

#define IDM_NEW      1
#define IDM_OPEN     2
#define IDM_SAVE     3
#define IDM_SAVEAS   4
#define IDM_EXIT     5

#define IDM_UNDO    10
#define IDM_CUT     11
#define IDM_COPY    12
#define IDM_PASTE   13
#define IDM_CLEAR   14

#define IDM_WHITE   20
#define IDM_LTGRAY  21
#define IDM_GRAY    22
#define IDM_DKGRAY  23
#define IDM_BLACK   24

#define IDM_START   30
#define IDM_STOP    31

#define IDM_HELP    40
#define IDM_ABOUT   41

 MENUDEMO.DEF

;-------------------------------------
; MENUDEMO.DEF module definition file
;-------------------------------------

NAME           MENUDEMO

DESCRIPTION    'Menu Demonstration Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

Identifiers for all menu IDs are defined in MENUDEMO.H. This file must be
specified (using a #include statement) in both the resource script file and
the C source code file. The identifiers begin with IDM. (The ID numbers
defined for the menu items need not be consecutive. However, if you process
these IDs in your program using switch and case statements, keep in mind
that the C compiler can best optimize this code using jump tables if you use
consecutive menu ID numbers.)

The MENUDEMO program simply beeps when it receives a WM_COMMAND message for
most items in the File and Edit popups. The Background popup lists five
stock brushes that MENUDEMO can use to color the background. In the
MENUDEMO.RC resource script the White menu item (with a menu ID of
IDM_WHITE) is flagged as CHECKED, which places a check mark next to the
item. In MENUDEMO.C, the value of wSelection is initially set to IDM_WHITE.

The five brushes on the Background popup are mutually exclusive. When
MENUDEMO.C receives a WM_COMMAND message where wParam is one of these five
items on the Background popup, it must remove the check mark from the
previously chosen background color and add a check mark to the new
background color. To do this, it first gets a handle to its menu:

hMenu = GetMenu (hwnd) ;

The CheckMenuItem function is used to uncheck the currently checked item:

CheckMenuItem (hMenu, wSelection, MF_UNCHECKED) ;

The wSelection value is set to the value of wParam, and the new background
color is checked:

wSelection = wParam ;
CheckMenuItem (hMenu, wSelection, MF_CHECKED) ;

The background color in the window class is then replaced with the new
background color, and the window client area is invalidated. Windows erases
the window using the new background color.

The Timer popup lists two options--Start and Stop. Initially, the Stop
option is grayed (as indicated in the menu definition for the resource
script). When you choose the Start option, MENUDEMO tries to start a timer
and, if successful, grays the Start option and makes the Stop option active:

EnableMenuItem (hMenu, IDM_START, MF_GRAYED) ;
EnableMenuItem (hMenu, IDM_STOP,  MF_ENABLED) ;

On receipt of a WM_COMMAND message with wParam equal to IDM_STOP, MENUDEMO
kills the timer, activates the Start option, and grays the Stop option:

EnableMenuItem (hMenu, IDM_START, MF_ENABLED) ;
EnableMenuItem (hMenu, IDM_STOP,  MF_GRAYED) ;

Notice that it's impossible for MENUDEMO to receive a WM_COMMAND message
with wParam equal to IDM_START when the timer is going. Similarly, it's
impossible to receive a WM_COMMAND with wParam equal to IDM_STOP when the
timer is not going.

When MENUDEMO receives a WM_COMMAND message with the wParam parameter equal
to IDM_ABOUT or IDM_HELP, it displays a message box. (In Chapter 10 we'll
change this to a dialog box.)

When MENUDEMO receives a WM_COMMAND message with wParam equal to IDM_EXIT,
it sends itself a WM_CLOSE message. This is the same message that
DefWindowProc sends the window procedure when it receives a WM_SYSCOMMAND
message with wParam equal to SC_CLOSE. We'll examine this more in the
POPPAD2 program shown toward the end of this chapter.


Menu Etiquette

The format of the File and Edit popups in MENUDEMO follows the
recommendations of the CUA Advanced Interface Design Guide. Many Windows
programs have File and Edit popups. One of the objectives of Windows is to
provide a user with a recognizable interface that does not require
relearning basic concepts for each program. It certainly helps if the File
and Edit menus look the same in every Windows program and use the same
letters for selection with the Alt key.

Beyond the File and Edit popups, the menus of most Windows programs will be
different. When designing a menu you should look at existing Windows
programs and aim for some consistency. Of course, if you think these other
programs are wrong and you know the right way to do it, nobody's going to
stop you. Also keep in mind that revising a menu usually requires revising
only the resource script and not your program code. You can move menu items
around at a later time without many problems.

At the beginning of this chapter, I showed you a menu with nine top-level
items but with only one popup that is invoked from the top-level menu. This
menu is certainly atypical. Most often, each top-level item has a popup,
even if the popup has only one option. Top-level items without popups can be
too easily chosen by mistake.


Defining a Menu the Hard Way

Defining a menu in a program's resource script is usually the easiest way to
add a menu in your window, but it's not the only way. You can dispense with
the resource script and create a menu entirely within your program using two
functions called CreateMenu and AppendMenu. After you finish defining the
menu, you can pass the menu handle to CreateWindow or use SetMenu to set the
window's menu.

Here's how it's done. CreateMenu simply returns a handle to a new menu:

hMenu = CreateMenu () ;

The menu is initially empty. AppendMenu inserts items into the menu. You
must obtain a different menu handle for the top-level menu item and for each
popup. The popups are constructed separately; the popup menu handles are
then inserted into the top-level menu. The code shown in Figure 9-4 creates
a menu in this fashion; in fact, it is the same menu as in the MENUDEMO
program.

hMenu = CreateMenu () ;

hMenuPopup = CreateMenu () ;

AppendMenu (hMenuPopup, MF_STRING,    IDM_NEW,    "&New") ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_OPEN,   "&Open...") ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_SAVE,   "&Save") ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_SAVEAS, "Save &As...") ;
AppendMenu (hMenuPopup, MF_SEPARATOR, 0,          NULL) ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_EXIT,   "E&xit") ;

AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&File") ;

hMenuPopup = CreateMenu () ;

AppendMenu (hMenuPopup, MF_STRING,    IDM_UNDO,  "&Undo") ;
AppendMenu (hMenuPopup, MF_SEPARATOR, 0,         NULL) ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_CUT,   "Cu&t") ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_COPY,  "&Copy") ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_PASTE, "&Paste") ;
AppendMenu (hMenuPopup, MF_STRING,    IDM_CLEAR, "C&lear") ;

AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Edit") ;

hMenuPopup = CreateMenu () ;

AppendMenu (hMenuPopup, MF_STRING | MF_CHECKED, IDM_WHITE,  "&White") ;
AppendMenu (hMenuPopup, MF_STRING,              IDM_LTGRAY, "&Lt Gray") ;
AppendMenu (hMenuPopup, MF_STRING,              IDM_GRAY,   "&Gray") ;
AppendMenu (hMenuPopup, MF_STRING,              IDM_DKGRAY, "&Dk Gray") ;
AppendMenu (hMenuPopup, MF_STRING,              IDM_BLACK,  "&Black") ;

AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Background") ;

hMenuPopup = CreateMenu () ;

AppendMenu (hMenuPopup, MF_STRING,             IDM_START, "&Start") ;
AppendMenu (hMenuPopup, MF_STRING | MF_GRAYED, IDM_STOP,  "S&top") ;

AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Timer") ;

hMenuPopup = CreateMenu () ;

AppendMenu (hMenuPopup, MF_STRING, IDM_HELP,  "&Help") ;
AppendMenu (hMenuPopup, MF_STRING, IDM_ABOUT, "&About MenuDemo...") ;

AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Help") ;

I think you'll agree that the resource script menu template is easier and
clearer. I'm not recommending that you define a menu in this way, only
showing that it can be done. Certainly you can cut down on the code size
substantially by using some arrays of structures containing all the menu
item character strings, IDs, and flags. But if you do that, you might as
well take advantage of the third method Windows provides for defining a
menu.


A Third Approach to Defining Menus

The LoadMenuIndirect function accepts a pointer to a structure of type
MENUITEMTEMPLATE and returns a handle to a menu. This function is used
within Windows to construct a menu after loading the normal menu template
from a resource script. If you're brave, you can try using it yourself.

Be forewarned, however: The MENUITEMTEMPLATE structure has a field defined
as LPSTR that is set to a far pointer to a character string, a handle to a
popup, or a handle to a bitmap. But you can't simply define a
MENUITEMTEMPLATE structure in your program and initialize the field to a
character string. During compilation, the pointer to the character string is
converted to a far pointer. This violates one of the most important rules
discussed in Chapter 7: Don't store far pointers to your data segment.
Instead, immediately before calling LoadMenuIndirect, you use a series of
assignment statements to set this field to the character string pointers.
Between these assignment statements and the LoadMenuIndirect call, you can't
make any Windows calls that can result in your data segment being moved
(such as GetMessage).


Floating Popup Menus

Beginning with Windows 3, you can make use of menus without having a
top-level menu bar. You can instead cause a popup menu to appear on top of
any part of the screen. One approach is to invoke this popup menu in
response to a click of the right mouse button. However, menu items must
still be selected with the left mouse button. The POPMENU program in Figure
9-5 (beginning on the following page) shows how this is done.

 POPMENU.MAK

#-----------------------
# POPMENU.MAK make file
#-----------------------

popmenu.exe : popmenu.obj popmenu.def popmenu.res
     link popmenu, /align:16, NUL, /nod slibcew libw, popmenu
     rc popmenu.res

popmenu.obj : popmenu.c popmenu.h
     cl -c -Gsw -Ow -W2 -Zp popmenu.c

popmenu.res : popmenu.rc popmenu.h
     rc -r popmenu.rc

 POPMENU.C

/*----------------------------------------
   POPMENU.C -- Popup Menu Demonstration
                (c) Charles Petzold, 1990
  ----------------------------------------*/

#include 
#include "popmenu.h"

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

char   szAppName [] = "PopMenu" ;
HANDLE hInst ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;



          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hInst = hInstance ;

     hwnd = CreateWindow (szAppName, "Popup Menu Demonstration",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static HMENU hMenu ;
     static int   wColorID [5] = { WHITE_BRUSH,  LTGRAY_BRUSH, GRAY_BRUSH,
                                   DKGRAY_BRUSH, BLACK_BRUSH } ;
     static WORD  wSelection = IDM_WHITE ;
     POINT        point ;

     switch (message)
          {
          case WM_CREATE :
               hMenu = LoadMenu (hInst, szAppName) ;
               hMenu = GetSubMenu (hMenu, 0) ;
               return 0 ;

          case WM_RBUTTONDOWN :
               point = MAKEPOINT (lParam) ;
               ClientToScreen (hwnd, &point) ;

               TrackPopupMenu (hMenu, 0, point.x, point.y, 0, hwnd, NULL) ;
               return 0 ;

          case WM_COMMAND :
               switch (wParam)
                    {                    case IDM_NEW :
                    case IDM_OPEN :
                    case IDM_SAVE :
                    case IDM_SAVEAS :
                    case IDM_UNDO :
                    case IDM_CUT :
                    case IDM_COPY :
                    case IDM_PASTE :
                    case IDM_CLEAR :
                         MessageBeep (0) ;
                         return 0 ;

                    case IDM_WHITE :          // Note: Logic below
                    case IDM_LTGRAY :         //   assumes that IDM_WHITE
                    case IDM_GRAY :           //   through IDM_BLACK are
                    case IDM_DKGRAY :         //   consecutive numbers in
                    case IDM_BLACK :          //   the order shown here.

                         CheckMenuItem (hMenu, wSelection, MF_UNCHECKED) ;
                         wSelection = wParam ;
                         CheckMenuItem (hMenu, wSelection, MF_CHECKED) ;

                         SetClassWord (hwnd, GCW_HBRBACKGROUND,
                              GetStockObject (wColorID [wParam -
IDM_WHITE])) ;

                         InvalidateRect (hwnd, NULL, TRUE) ;
                         return 0 ;

                    case IDM_ABOUT :
                         MessageBox (hwnd, "Popup Menu Demonstration
Program.",
                                     szAppName, MB_ICONINFORMATION | MB_OK)
;
                         return 0 ;

                    case IDM_EXIT :
                         SendMessage (hwnd, WM_CLOSE, 0, 0L) ;
                         return 0 ;

                    case IDM_HELP :
                         MessageBox (hwnd, "Help not yet implemented.",
                                     szAppName, MB_ICONINFORMATION | MB_OK)
;
                         return 0 ;
                    }
               break ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 POPMENU.RC

/*----------------------------
   POPMENU.RC resource script
  ----------------------------*/

#include "popmenu.h"

PopMenu MENU
     {
     POPUP ""
          {
          POPUP "&File"
               {
               MENUITEM "&New",               IDM_NEW
               MENUITEM "&Open...",           IDM_OPEN
               MENUITEM "&Save",              IDM_SAVE
               MENUITEM "Save &As...",        IDM_SAVEAS
               MENUITEM SEPARATOR
               MENUITEM "E&xit",              IDM_EXIT
               }
          POPUP "&Edit"
               {
               MENUITEM "&Undo",              IDM_UNDO
               MENUITEM SEPARATOR
               MENUITEM "Cu&t",               IDM_CUT
               MENUITEM "&Copy",              IDM_COPY
               MENUITEM "&Paste",             IDM_PASTE
               MENUITEM "C&lear",             IDM_CLEAR
               }
          POPUP "&Background"
               {
               MENUITEM "&White",             IDM_WHITE, CHECKED
               MENUITEM "&Lt Gray",           IDM_LTGRAY
               MENUITEM "&Gray",              IDM_GRAY
               MENUITEM "&Dk Gray",           IDM_DKGRAY
               MENUITEM "&Black",             IDM_BLACK
               }
          POPUP "&Help"
               {
               MENUITEM "&Help",              IDM_HELP
               MENUITEM "&About PopMenu...",  IDM_ABOUT
               }
          }
     }

 POPMENU.H

/*-----------------------
   POPMENU.H header file
  -----------------------*/

#define IDM_NEW      1
#define IDM_OPEN     2
#define IDM_SAVE     3
#define IDM_SAVEAS   4
#define IDM_EXIT     5

#define IDM_UNDO    10
#define IDM_CUT     11
#define IDM_COPY    12
#define IDM_PASTE   13
#define IDM_CLEAR   14

#define IDM_WHITE   20
#define IDM_LTGRAY  21
#define IDM_GRAY    22
#define IDM_DKGRAY  23
#define IDM_BLACK   24

#define IDM_HELP    30
#define IDM_ABOUT   31

 POPMENU.DEF

;------------------------------------
; POPMENU.DEF module definition file
;------------------------------------

NAME           POPMENU

DESCRIPTION    'Popup Menu Demonstration Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The POPMENU.RC resource script defines a menu very similar to the one in
MENUDEMO.RC. The difference is that the top-level menu contains only one
item--a popup that invokes the File, Edit, Background, and Help options.

During the WM_CREATE message in WndProc, POPMENU obtains a handle to this
popup menu:

hMenu = LoadMenu (hInst, szAppName) ;
hMenu = GetSubMenu (hMenu, 0) ;

During the WM_RBUTTONDOWN message, POPMENU obtains the position of the mouse
point, coverts the position to screen coordinates, and passes the
coordinates to TrackPopupMenu:

point = MAKEPOINT (lParam) ;
ClientToScreen (hwnd, &point) ;

TrackPopupMenu (hMenu, 0, point.x, point.y, 0, hwnd, NULL) ;

Windows then displays the popup menu with the items File, Edit, Background,
and Help. Selecting any of these options causes the nested popup menus to
appear to the right. The menu functions the same as a normal menu.


Using the System Menu

Parent windows created with a style that includes WS_SYSMENU have a system
menu box at the left of the caption bar. If you like, you can modify this
menu. For instance, you can add your own menu commands to the system menu.
While this is not recommended, modifying the system menu is often a
quick-and-dirty way to add a menu to a short program without defining it in
the resource script. The only restriction is this: The ID numbers you use to
add commands to the system menu must be lower than F000H. Otherwise, they
will conflict with the IDs that Windows uses for the normal system menu
commands. And remember: When you process WM_SYSCOMMAND messages in your
window procedure for these new menu items, you must pass the other
WM_SYSCOMMAND messages to DefWindowProc. If you don't, you'll effectively
disable all normal options on the system menu.

The program POORMENU ("poor person's menu"), shown in Figure 9-6 beginning
on the following page, adds a separator bar and three commands to the system
menu. The last of these commands removes the additions.

 POORMENU.MAK

#------------------------
# POORMENU.MAK make file
#------------------------

poormenu.exe : poormenu.obj poormenu.def
     link poormenu, /align:16, NUL, /nod slibcew libw, poormenu
     rc poormenu.exe

poormenu.obj : poormenu.c
     cl -c -Gsw -Ow -W2 -Zp poormenu.c

 POORMENU.C

/*-----------------------------------------
   POORMENU.C -- The Poor Person's Menu
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 

#define IDM_ABOUT   1
#define IDM_HELP    2
#define IDM_REMOVE  3

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

static char szAppName [] = "PoorMenu" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HMENU    hMenu ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;


          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "The Poor Person's Menu",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     hMenu = GetSystemMenu (hwnd, FALSE) ;

     AppendMenu (hMenu, MF_SEPARATOR, 0,          NULL) ;
     AppendMenu (hMenu, MF_STRING,    IDM_ABOUT,  "About...") ;
     AppendMenu (hMenu, MF_STRING,    IDM_HELP,   "Help...") ;
     AppendMenu (hMenu, MF_STRING,    IDM_REMOVE, "Remove Additions") ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     switch (message)
          {
          case WM_SYSCOMMAND :
               switch (wParam)
                    {
                    case IDM_ABOUT :
                         MessageBox (hwnd, "The Poor Person's Menu
Program.",
                                     szAppName, MB_OK | MB_ICONEXCLAMATION)
;
                         return 0 ;

                    case IDM_HELP :
                         MessageBox (hwnd, "Help not yet implemented.",
                                     szAppName, MB_OK | MB_ICONEXCLAMATION)
;
                         return 0 ;
                    case IDM_REMOVE :
                         GetSystemMenu (hwnd, TRUE) ;
                         return 0 ;
                    }
               break ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 POORMENU.DEF

;-------------------------------------
; POORMENU.DEF module definition file
;-------------------------------------

NAME           POORMENU

DESCRIPTION    'The Poor Person's Menu (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The three menu IDs are defined near the top of POORMENU.C:

#define IDM_ABOUT   1
#define IDM_HELP    2
#define IDM_REMOVE  3

After the program's window has been created, POORMENU obtains a handle to
the system menu:

hMenu = GetSystemMenu (hwnd, FALSE) ;

When you first call GetSystemMenu, you should set the second parameter to
FALSE in preparation for modifying the menu.

The menu is altered with four AppendMenu calls:

AppendMenu (hMenu, MF_SEPARATOR, 0,          NULL) ;
AppendMenu (hMenu, MF_STRING,    IDM_ABOUT,  "About...") ;
AppendMenu (hMenu, MF_STRING,    IDM_HELP,   "Help...") ;
AppendMenu (hMenu, MF_STRING,    IDM_REMOVE, "Remove Additions") ;

The first AppendMenu call adds the separator bar. Choosing the Remove
Additions menu item causes POORMENU to remove these additions, which it
accomplishes simply by calling GetSystemMenu again with the second parameter
set to TRUE:

GetSystemMenu (hwnd, TRUE) ;

The standard system menu has the options Restore, Move, Size, Minimize,
Maximize, Close, and Switch To. These generate WM_SYSCOMMAND messages with
wParam equal to SC_RESTORE, SC_MOVE, SC_SIZE, SC_MINIMUM, SC_MAXIMUM,
SC_CLOSE, and SC_TASKLIST. Although Windows programs do not normally do so,
you can process these messages yourself rather than pass them on to
DefWindowProc. You can also disable or remove some of these standard options
from the system menu using methods described below. The Windows
documentation also includes some standard additions to the system menu.
These use the identifiers SC_NEXTWINDOW, SC_PREVWINDOW, SC_VSCROLL,
SC_HSCROLL, and SC_ARRANGE. You might find it appropriate to add these
commands to the system menu in some applications.


Changing the Menu

We've already seen how the AppendMenu function can be used to define a menu
entirely within a program and to add menu items to the system menu. Prior to
Windows 3, you would have been forced to use the ChangeMenu function for
this job. ChangeMenu was so versatile that it was one of the most complex
functions in all of Windows. In Windows 3, ChangeMenu is still available,
but its functionality has been divided among five new functions:

  þ   AppendMenu adds a new item to the end of a menu.

  þ   DeleteMenu deletes an existing item from a menu and destroys the item.

  þ   InsertMenu inserts a new item into a menu.

  þ   ModifyMenu changes an existing menu item.

  þ   RemoveMenu removes an existing item from a menu.

The difference between DeleteMenu and RemoveMenu is important if the item is
a popup menu. DeleteMenu destroys the popup menu--but RemoveMenu does not.


Other Menu Commands

Here are some more functions useful for working with menus:

When you change a top-level menu item, the change is not shown until Windows
redraws the menu bar. You can force this redrawing by calling:

DrawMenuBar (hwnd) ;

Note that the parameter to DrawMenuBar is a handle to the window rather than
a handle to the menu.

You can obtain the handle of a popup menu using:

hMenuPopup = GetSubMenu (hMenu, nPos) ;

where nPos is the index (starting at 0) of the popup within the top-level
menu indicated by hMenu. You can then use the popup menu handle with other
functions (such as AppendMenu).

You can obtain the current number of items in a top-level or popup menu
using:

nCount = GetMenuItemCount (hMenu) ;

You can obtain the menu ID for an item in a popup menu from:

wID = GetMenuItemID (hMenuPopup, nPosition) ;

where nPosition is the position (starting at 0) of the item within the
popup.

In MENUDEMO you saw how to check or uncheck an item in a popup menu using:

CheckMenuItem (hMenu, wID, wCheck) ;

In MENUDEMO, hMenu was the handle to the top-level menu, wID was the menu
ID, and the value of wCheck was either MF_CHECKED or MF_UNCHECKED. If hMenu
is a handle to a popup menu, then the wID parameter can be a positional
index rather than a menu ID. If an index is more convenient, you include
MF_BYPOSITION in the third parameter. For instance:

CheckMenuItem (hMenu, nPosition, MF_CHECKED | MF_BYPOSITION) ;

The EnableMenuItem function works similarly to CheckMenuItem except the
third parameter is MF_ENABLED, MF_DISABLED, or MF_GRAYED. If you use
EnableMenuItem on a top-level menu item that has a popup, you must also use
the MF_BYPOSITION identifier in the third parameter because the menu item
has no menu ID. We'll see an example of EnableMenuItem in the POPPAD program
shown later in this chapter. HiliteMenuItem is similar to CheckMenuItem and
EnableMenuItem but uses MF_HILITE and MF_UNHILITE. This highlighting is the
reverse video that Windows uses when you move among menu items. You do not
normally need to use HiliteMenuItem.

What else do you need to do with your menu? Have you forgotten what
character string you used in a menu? You can refresh your memory by calling:

nByteCount = GetMenuString (hMenu, wID, lpString, nMaxCount, wFlag) ;

The wFlag is either MF_BYCOMMAND (where wID is a menu ID) or MF_BYPOSITION
(wID is a positional index). The function copies up to nMaxCount bytes of
the text string into lpString and returns the number of bytes copied.

Or perhaps you'd like to know what the current flags of a menu item are:

wFlags = GetMenuState (hMenu, wID, wFlag) ;

Again, wFlag is either MF_BYCOMMAND or MF_BYPOSITION. The wFlags parameter
is a combination of all the current flags. You can determine them by testing
against the MF_DISABLED, MF_GRAYED, MF_CHECKED, MF_MENUBREAK,
MF_MENUBARBREAK, and MF_SEPARATOR identifiers.

Or maybe by this time you're a little fed up with menus. In that case you'll
be pleased to know that if you no longer need a menu in your program, you
can destroy it:

DestroyMenu (hMenu) ;

This invalidates the menu handle.


An Unorthodox Approach to Menus

Now let's step a little off the beaten path. Instead of having drop-down
menus in your program, how about creating multiple top-level menus without
any popups and switching between the top-level menus using the SetMenu call?
The NOPOPUPS program, shown in Figure 9-7, demonstrates how to do it. This
program includes similar File and Edit items that MENUDEMO uses but displays
them as alternate top-level menus.

 NOPOPUPS.MAK

#------------------------
# NOPOPUPS.MAK make file
#------------------------

nopopups.exe : nopopups.obj nopopups.def nopopups.res
     link nopopups, /align:16, NUL, /nod slibcew libw, nopopups
     rc nopopups.res

nopopups.obj : nopopups.c nopopups.h
     cl -c -Gsw -Ow -W2 -Zp nopopups.c

nopopups.res : nopopups.rc nopopups.h
     rc -r nopopups.rc

 NOPOPUPS.C

/*-------------------------------------------------
   NOPOPUPS.C -- Demonstrates No-Popup Nested Menu
                 (c) Charles Petzold, 1990
  -------------------------------------------------*/

#include 
#include "nopopups.h"

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "NoPopUps" ;
     HWND        hwnd ;
     MSG         msg ;
     WNDCLASS    wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "No-Popup Nested Menu Demonstration",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static HMENU hMenuMain, hMenuEdit, hMenuFile ;
     HANDLE       hInstance ;

     switch (message)
          {
          case WM_CREATE :
               hInstance = GetWindowWord (hwnd, GWW_HINSTANCE) ;

               hMenuMain = LoadMenu (hInstance, "MenuMain") ;
               hMenuFile = LoadMenu (hInstance, "MenuFile") ;
               hMenuEdit = LoadMenu (hInstance, "MenuEdit") ;

               SetMenu (hwnd, hMenuMain) ;
               return 0 ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDM_MAIN :
                         SetMenu (hwnd, hMenuMain) ;
                         return 0 ;

                    case IDM_FILE :
                         SetMenu (hwnd, hMenuFile) ;
                         return 0 ;

                    case IDM_EDIT :
                         SetMenu (hwnd, hMenuEdit) ;
                         return 0 ;

                    case IDM_NEW :
                    case IDM_OPEN :
                    case IDM_SAVE :
                    case IDM_SAVEAS :
                    case IDM_UNDO :
                    case IDM_CUT :
                    case IDM_COPY :
                    case IDM_PASTE :
                    case IDM_CLEAR :
                         MessageBeep (0) ;
                         return 0 ;
                    }
               break ;          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 NOPOPUPS.RC

/*-----------------------------
   NOPOPUPS.RC resource script
  -----------------------------*/

#include "nopopups.h"

MenuMain MENU
     {
     MENUITEM "MAIN:",         0,        INACTIVE
     MENUITEM "&File...",      IDM_FILE
     MENUITEM "&Edit...",      IDM_EDIT
     }

MenuFile MENU
     {
     MENUITEM "FILE:",         0,        INACTIVE
     MENUITEM "&New",          IDM_NEW
     MENUITEM "&Open...",      IDM_OPEN
     MENUITEM "&Save",         IDM_SAVE
     MENUITEM "Save &As...",   IDM_SAVEAS
     MENUITEM "(&Main)",       IDM_MAIN
     }

MenuEdit MENU
     {
     MENUITEM "EDIT:",         0,        INACTIVE
     MENUITEM "&Undo",         IDM_UNDO
     MENUITEM "Cu&t",          IDM_CUT
     MENUITEM "&Copy",         IDM_COPY
     MENUITEM "&Paste",        IDM_PASTE
     MENUITEM "C&lear",        IDM_CLEAR
     MENUITEM "(&Main)",       IDM_MAIN
     }

 NOPOPUPS.H

/*------------------------
   NOPOPUPS.H header file
  ------------------------*/

#define IDM_NEW      1
#define IDM_OPEN     2
#define IDM_SAVE     3
#define IDM_SAVEAS   4

#define IDM_UNDO     5
#define IDM_CUT      6
#define IDM_COPY     7
#define IDM_PASTE    8
#define IDM_CLEAR    9

#define IDM_MAIN    10
#define IDM_EDIT    11
#define IDM_FILE    12

 NOPOPUPS.DEF

;-------------------------------------
; NOPOPUPS.DEF module definition file
;-------------------------------------

NAME           NOPOPUPS

DESCRIPTION    'Demonstration of No-Popup Menu (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The resource script has three menus rather than one. When the window
procedure processes the WM_CREATE message, Windows loads each of the menu
resources into memory:

hMenuMain = LoadMenu (hInstance, "MenuMain") ;
hMenuFile = LoadMenu (hInstance, "MenuFile") ;
hMenuEdit = LoadMenu (hInstance, "MenuEdit") ;

Initially, the program displays the main menu:

SetMenu (hwnd, hMenuMain) ;

The main menu lists the three options using the character strings "MAIN:",
"File...^^, and "Edit...^^ However, "MAIN:" is disabled, so it doesn't cause
WM_COMMAND messages to be sent to the window procedure. The File and Edit
menus begin "FILE:" and "EDIT:" to identify these as submenus. The last item
in each menu is the character string "(Main)"; this option indicates a
return to the main menu. Switching among these three menus is simple:

case WM_COMMAND :

     switch (wParam)
          {
          case IDM_MAIN :
               SetMenu (hwnd, hMenuMain) ;
               return 0 ;

          case IDM_FILE :
               SetMenu (hwnd, hMenuFile) ;
               return 0 ;

          case IDM_EDIT :
               SetMenu (hwnd, hMenuEdit) ;
               return 0 ;
[other program lines]
          }
     break ;



USING BITMAPS IN MENUS

Character strings are not the only way to display a menu item. You can also
use a bitmap. If you immediately recoiled at the thought of pictures of file
folders, paste jars, and trash cans in a menu, don't think of pictures.
Think instead of how useful menu bitmaps might be for a drawing program.
Think of using different fonts and font sizes, line widths, hatch patterns,
and colors in your menus.

The program we're going to examine is called GRAFMENU ("graphics menu"). The
top-level menu is shown in Figure 9-8. The enlarged block letters are
obtained from 40-by-16-pixel monochrome bitmap files created in SDKPAINT and
saved as .BMP files; they could be pictures instead. Choosing FONT from the
menu invokes a popup containing three options--Courier, Helvetica, and Times
Roman--each displayed in its respective font (Figure 9-9). These bitmaps
were created in the program using a technique involving a "memory device
context."

  (Figure 9-8. may be found in the printed book.)

  (Figure 9-9. may be found in the printed book.)

Finally, when you pull down the system menu, you see that you have access to
some "help" information, with the word "Help" perhaps mirroring the
desperation of a new user (Figure 9-10). This 64-by-64-pixel monochrome
bitmap was created in SDKPAINT.

  (Figure 9-10. may be found in the printed book.)

The GRAFMENU program, including the four bitmaps created in SDKPAINT, is
shown in Figure 9-11.

 GRAFMENU.MAK

#------------------------
# GRAFMENU.MAK make file
#------------------------

grafmenu.exe : grafmenu.obj grafmenu.def grafmenu.res
     link grafmenu, /align:16, NUL, /nod slibcew libw, grafmenu
     rc grafmenu.res

grafmenu.obj : grafmenu.c grafmenu.h
     cl -c -Gsw -Ow -W2 -Zp grafmenu.c

grafmenu.res : grafmenu.rc grafmenu.h \
               editlabl.bmp filelabl.bmp fontlabl.bmp bighelp.bmp
     rc -r grafmenu.rc

 GRAFMENU.C

/*----------------------------------------------
   GRAFMENU.C -- Demonstrates Bitmap Menu Items
                 (c) Charles Petzold, 1990
  ----------------------------------------------*/

#include 
#include 
#include "grafmenu.h"

long FAR PASCAL WndProc  (HWND, WORD, WORD, LONG) ;
HBITMAP StretchBitmap (HBITMAP) ;
HBITMAP GetBitmapFont (int) ;

char szAppName [] = "GrafMenu" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HBITMAP  hBitmapHelp, hBitmapFile, hBitmapEdit,
              hBitmapFont, hBitmapPopFont [3] ;
     HMENU    hMenu, hMenuPopup ;
     HWND     hwnd ;
     int      i ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hMenu = CreateMenu () ;

     hMenuPopup = LoadMenu (hInstance, "MenuFile") ;
     hBitmapFile = StretchBitmap (LoadBitmap (hInstance, "BitmapFile")) ;
     AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup,
                 (LPSTR) (LONG) hBitmapFile) ;
     hMenuPopup = LoadMenu (hInstance, "MenuEdit") ;
     hBitmapEdit = StretchBitmap (LoadBitmap (hInstance, "BitmapEdit")) ;
     AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup,
                 (LPSTR) (LONG) hBitmapEdit) ;

     hMenuPopup = CreateMenu () ;

     for (i = 0 ; i < 3 ; i++)
          {
          hBitmapPopFont [i] = GetBitmapFont (i) ;
          AppendMenu (hMenuPopup, MF_BITMAP, IDM_COUR + i,
                      (LPSTR) (LONG) hBitmapPopFont [i]) ;
          }

     hBitmapFont = StretchBitmap (LoadBitmap (hInstance, "BitmapFont")) ;
     AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup,
                 (LPSTR) (LONG) hBitmapFont) ;

     hwnd = CreateWindow (szAppName, "Bitmap Menu Demonstration",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, hMenu, hInstance, NULL) ;

     hMenu = GetSystemMenu (hwnd, FALSE) ;
     hBitmapHelp = StretchBitmap (LoadBitmap (hInstance, "BitmapHelp")) ;
     AppendMenu (hMenu, MF_SEPARATOR, NULL,     NULL) ;
     AppendMenu (hMenu, MF_BITMAP,    IDM_HELP, (LPSTR) (LONG) hBitmapHelp)
;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }

     DeleteObject (hBitmapHelp) ;
     DeleteObject (hBitmapEdit) ;
     DeleteObject (hBitmapFile) ;
     DeleteObject (hBitmapFont) ;

     for (i = 0 ; i < 3 ; i++)
          DeleteObject (hBitmapPopFont [i]) ;

     return msg.wParam ;
     }

HBITMAP StretchBitmap (HBITMAP hBitmap1)
     {
     BITMAP     bm1, bm2 ;
     HBITMAP    hBitmap2 ;
     HDC        hdc, hdcMem1, hdcMem2 ;
     TEXTMETRIC tm ;

     hdc = CreateIC ("DISPLAY", NULL, NULL, NULL) ;
     GetTextMetrics (hdc, &tm) ;
     hdcMem1 = CreateCompatibleDC (hdc) ;
     hdcMem2 = CreateCompatibleDC (hdc) ;
     DeleteDC (hdc) ;

     GetObject (hBitmap1, sizeof (BITMAP), (LPSTR) &bm1) ;

     bm2 = bm1 ;
     bm2.bmWidth      = (tm.tmAveCharWidth * bm2.bmWidth)  / 4 ;
     bm2.bmHeight     = (tm.tmHeight       * bm2.bmHeight) / 8 ;
     bm2.bmWidthBytes = ((bm2.bmWidth + 15) / 16) * 2 ;

     hBitmap2 = CreateBitmapIndirect (&bm2) ;

     SelectObject (hdcMem1, hBitmap1) ;
     SelectObject (hdcMem2, hBitmap2) ;

     StretchBlt (hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight,
                 hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ;

     DeleteDC (hdcMem1) ;
     DeleteDC (hdcMem2) ;
     DeleteObject (hBitmap1) ;

     return hBitmap2 ;
     }

HBITMAP GetBitmapFont (int i)
     {
     static  struct
          {
          BYTE lfPitchAndFamily ;
          BYTE lfFaceName [LF_FACESIZE] ;
          char *szMenuText ;
          }
          lfSet [3] =
          {
          FIXED_PITCH    | FF_MODERN, "Courier",   "Courier",
          VARIABLE_PITCH | FF_SWISS,  "Helvetica", "Helvetica",
          VARIABLE_PITCH | FF_ROMAN,  "Tms Rmn",   "Times Roman"
          } ;
     DWORD   dwSize ;
     HBITMAP hBitmap ;
     HDC     hdc, hdcMem ;
     HFONT   hFont ;
     LOGFONT lf ;
     hFont = GetStockObject (SYSTEM_FONT) ;
     GetObject (hFont, sizeof (LOGFONT), (LPSTR) &lf) ;

     lf.lfHeight *= 2 ;
     lf.lfWidth  *= 2 ;
     lf.lfPitchAndFamily = lfSet[i].lfPitchAndFamily ;
     strcpy (lf.lfFaceName, lfSet[i].lfFaceName) ;

     hdc = CreateIC ("DISPLAY", NULL, NULL, NULL) ;
     hdcMem = CreateCompatibleDC (hdc) ;
     SelectObject (hdcMem, CreateFontIndirect (&lf)) ;
     dwSize = GetTextExtent (hdcMem, lfSet[i].szMenuText,
                             strlen (lfSet[i].szMenuText)) ;

     hBitmap = CreateBitmap (LOWORD (dwSize)-1, HIWORD (dwSize), 1, 1, NULL)
;
     SelectObject (hdcMem, hBitmap) ;


     TextOut (hdcMem, 0, 0, lfSet[i].szMenuText,
                            strlen (lfSet[i].szMenuText)) ;

     DeleteObject (SelectObject (hdcMem, hFont)) ;
     DeleteDC (hdcMem) ;
     DeleteDC (hdc) ;

     return hBitmap ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     HMENU  hMenu ;
     static short nCurrentFont = IDM_COUR ;

     switch (message)
          {
          case WM_CREATE :
               CheckMenuItem (GetMenu (hwnd), nCurrentFont, MF_CHECKED) ;
               return 0 ;

          case WM_SYSCOMMAND :
               switch (wParam)
                    {
                    case IDM_HELP :
                         MessageBox (hwnd, "Help not yet implemented.",
                                   szAppName, MB_OK | MB_ICONEXCLAMATION) ;
                         return 0 ;
                    }
               break ;
          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDM_NEW :
                    case IDM_OPEN :
                    case IDM_SAVE :
                    case IDM_SAVEAS :
                    case IDM_UNDO :
                    case IDM_CUT :
                    case IDM_COPY :
                    case IDM_PASTE :
                    case IDM_CLEAR :
                         MessageBeep (0) ;
                         return 0 ;

                    case IDM_COUR :
                    case IDM_HELV :
                    case IDM_TMSRMN :
                         hMenu = GetMenu (hwnd) ;
                         CheckMenuItem (hMenu, nCurrentFont, MF_UNCHECKED) ;
                         nCurrentFont = wParam ;
                         CheckMenuItem (hMenu, nCurrentFont, MF_CHECKED) ;
                         return 0 ;
                    }
               break ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 GRAFMENU.RC

/*-----------------------------
   GRAFMENU.RC resource script
  -----------------------------*/

#include "grafmenu.h"

BitmapEdit BITMAP editlabl.bmp
BitmapFile BITMAP filelabl.bmp
BitmapFont BITMAP fontlabl.bmp
BitmapHelp BITMAP bighelp.bmpMenuFile MENU
     {
     MENUITEM "&New",        IDM_NEW
     MENUITEM "&Open...",    IDM_OPEN
     MENUITEM "&Save",       IDM_SAVE
     MENUITEM "Save &As...", IDM_SAVEAS
     }

MenuEdit MENU
     {
     MENUITEM "&Undo",       IDM_UNDO
     MENUITEM SEPARATOR
     MENUITEM "Cu&t",        IDM_CUT
     MENUITEM "&Copy",       IDM_COPY
     MENUITEM "&Paste",      IDM_PASTE
     MENUITEM "C&lear",      IDM_CLEAR
     }

 GRAFMENU.H

/*------------------------
   GRAFMENU.H header file
  ------------------------*/

#define IDM_NEW      1
#define IDM_OPEN     2
#define IDM_SAVE     3
#define IDM_SAVEAS   4

#define IDM_UNDO     5
#define IDM_CUT      6
#define IDM_COPY     7
#define IDM_PASTE    8
#define IDM_CLEAR    9

#define IDM_COUR    10
#define IDM_HELV    11
#define IDM_TMSRMN  12

#define IDM_HELP    13

 EDITLABL.BMP  -- Please refer to the book.

 FILELABL.BMP  -- Please refer to the book.

 FONTLABL.BMP  -- Please refer to the book.

 BIGHELP.BMP  -- Please refer to the book.

 GRAFMENU.DEF

;-------------------------------------
; GRAFMENU.DEF module definition file
;-------------------------------------

NAME           GRAFMENU

DESCRIPTION    'Demo of Bitmapped Menu Items (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

To examine the subject of bitmaps and menus in the detail it deserves, we'll
need to cross the border into GDI territory--a full exploration of which
awaits us in the next section of this book. The discussion here will serve
as a preview of topics that we'll return to again in Chapter 11.

Two Methods of Creating Bitmaps for Menus

To insert a bitmap into a menu, you use AppendMenu or InsertMenu. Where does
this bitmap come from? It can come from one of two places. First, you can
create a bitmap using SDKPAINT and include the bitmap file in your resource
script. Within the program, you can use LoadBitmap to load the bitmap
resource into memory and use AppendMenu or InsertMenu to attach it to the
menu. There's a problem with this approach, however. The bitmap will not be
suitable for all types of video resolutions and aspect ratios; you have to
stretch the loaded bitmap to account for this. Alternatively, you can create
the bitmap right in the program and attach it to the menu.

Both of these methods sound a lot more difficult than they actually are. We
don't have to mess around with the actual bits themselves. Windows provides
functions that let us manipulate bitmaps cleanly using something called the
"memory device context."


The Memory Device Context

When you use GDI calls (such as TextOut) to write on the client area of your
window, you're actually writing to a block of memory (the video display
memory) that is organized much like a giant bitmap. The width and height of
this bitmap are equal to the resolution of the video adapter. The manner in
which multiple bits define color is also defined by the video adapter.
Windows should also be able to pretend that a block of regular memory is
video display memory. It should be able to write on this memory the same way
it writes on the screen. We should then be able to use this block of memory
as a bitmap.

That's exactly what a memory device context is. It helps us fill up and
manipulate bitmaps in a Windows program. Here are the steps involved:

  1.  Create a memory device context using the CreateCompatibleDC call.
      Initially, the display surface of this memory device context contains
      one monochrome pixel. You can think of this device context as being 1
      pixel high and 1 pixel wide, with two colors (black and white).

  2.  Create an uninitialized bitmap using CreateBitmap,
      CreateBitmapIndirect, or CreateCompatibleBitmap. When you create the
      bitmap, you specify the height and width and the color organization.
      However, the pixels of the bitmap need not actually represent anything
      yet. Save the handle to the bitmap.

      3.

Select the bitmap into the memory device context using SelectObject. Now the
memory device context has a display surface that is the size of the bitmap
with the same number of colors as defined by the bitmap.

  Use GDI functions to draw on the memory device context the same way you
use GDI functions to draw on a normal device context. Anything you draw
within the display surface of the memory device context is actually drawn on
the bitmap selected into the device context.

  Delete the memory device context. You are left with a handle to a bitmap
that contains a pixel representation of what you drew on the memory device
context.


Creating a Bitmap with Text

The GetBitmapFont function in GRAFMENU takes a parameter of 0, 1, or 2 and
returns a handle to a bitmap. This bitmap contains the string "Courier,"
"Helvetica," or "Times Roman" in the appropriate font and about twice the
size of the normal system font. Let's see how GetBitmapFont does it. (The
code that follows is not the same as that in the GRAFMENU.C file. For
purposes of clarity, I've replaced references to the lfSet structure with
the values appropriate for Times Roman.)

The first step is to get a handle to the system font and use GetObject to
copy characteristics of that font into the structure lf that has type
LOGFONT ("logical font"):

hFont = GetStockObject (SYSTEM_FONT) ;
GetObject (hFont, sizeof (LOGFONT), (LPSTR) &lf) ;

Certain fields of this logical font structure must be modified to make it
describe a larger Times Roman font:

lf.lfHeight *= 2 ;
lf.lfWidth  *= 2 ;
lf.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN ;
strcpy (lf.lfFaceName, "Tms Rmn") ;

The next step is to get a device context for the screen and create a memory
device context compatible with the screen:

hdc = CreateIC ("DISPLAY", NULL, NULL, NULL) ;
hdcMem = CreateCompatibleDC (hdc) ;

The handle to the memory device context is hdcMem. Next, we create a font
based on the modified lf structure and select that font into the memory
device context:

SelectObject (hdcMem, CreateFontIndirect (&lf)) ;

Now when we write some text to the memory device context, Windows will use
the Times Roman font selected into the device context.

But this memory device context still has a one-pixel monochrome device
surface. We have to create a bitmap large enough for the text we want to
display on it. You can obtain the dimensions of the text through
GetTextExtent and create a bitmap based on these dimensions with
CreateBitmap:

dwSize = GetTextExtent (hdcMem, "Times Roman", 11) ;
hBitmap = CreateBitmap (LOWORD (dwSize), HIWORD (dwSize), 1, 1, NULL) ;
SelectObject (hdcMem, hBitmap) ;

This device context now has a monochrome display surface exactly the size of
the text. Now all we have to do is write the text to it. You've seen this
function before:

TextOut (hdcMem, 0, 0, "Times Roman", 11) ;

We're finished except for cleaning up. To do so, we select the system font
(with handle hFont) back into the device context using SelectObject, and we
delete the previous font handle that SelectObject returns, which is the
handle to the Times Roman font:

DeleteObject (SelectObject (hdcMem, hFont)) ;

Now we can also delete the two device contexts:

DeleteDC (hdcMem) ;
DeleteDC (hdc) ;

We're left with a bitmap that has the text "Times Roman" in a Times Roman
font.


Scaling Bitmaps

The memory device context also comes to the rescue when we need to scale
fonts to a different display resolution or aspect ratio. I created the four
bitmaps used in GRAFMENU to be the correct size for a display that has a
system font height of 8 pixels and width of 4 pixels. For other system font
dimensions, the bitmap has to be stretched. This is done in GRAFMENU's
StretchBitmap function.

The first step is to get the device context for the screen, obtain the text
metrics for the system font, and create two memory device contexts:

hdc = CreateIC ("DISPLAY", NULL, NULL, NULL) ;
GetTextMetrics (hdc, &tm) ;
hdcMem1 = CreateCompatibleDC (hdc) ;
hdcMem2 = CreateCompatibleDC (hdc) ;
DeleteDC (hdc) ;

The bitmap handle passed to the function is hBitmap1. The program can obtain
the dimensions of this bitmap using GetObject:

GetObject (hBitmap1, sizeof (BITMAP), (LPSTR) &bm1) ;

This copies the dimensions into a structure bm1 of type BITMAP. The
structure bm2 is set equal to bm1, and then certain fields are modified
based on the system font dimensions:

bm2 = bm1 ;
bm2.bmWidth      = (tm.tmAveCharWidth * bm2.bmWidth)  / 4 ;
bm2.bmHeight     = (tm.tmHeight       * bm2.bmHeight) / 8 ;
bm2.bmWidthBytes = ((bm2.bmWidth + 15) / 16) * 2 ;

Then a new bitmap with handle hBitmap2 can be created based on the altered
dimensions:

hBitmap2 = CreateBitmapIndirect (&bm2) ;

You can then select these two bitmaps into the two memory display contexts:

SelectObject (hdcMem1, hBitmap1) ;
SelectObject (hdcMem2, hBitmap2) ;

We want to copy the first bitmap to the second bitmap and stretch it in the
process. This involves the StretchBlt call:

StretchBlt (hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight,
            hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ;

Now the second bitmap has the properly scaled bitmap. We'll use that one in
the menu. Cleanup is simple:

DeleteDC (hdcMem1) ;
DeleteDC (hdcMem2) ;
DeleteObject (hBitmap1) ;


Putting the Menu Together

GRAFMENU's WinMain function uses the StretchBitmap and GetBitmapFont
functions when constructing the menu. GRAFMENU has two menus already defined
in the resource script. These will become popups for the File and Edit
options.

GRAFMENU begins by obtaining a handle to an empty menu:

hMenu = CreateMenu () ;

The popup menu for File (containing the four options New, Open, Save, and
Save As) is loaded from the resource script:

hMenuPopup = LoadMenu (hInstance, "MenuFile") ;

The bitmap containing the word "FILE" is also loaded from the resource
script and stretched using StretchBitmap:

hBitmapFile = StretchBitmap (LoadBitmap (hInstance, "BitmapFile")) ;

The bitmap handle and popup menu handle become parameters in the ChangeMenu
call:

AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (LPSTR) (LONG)
hBitmapFile) ;

The same procedure is followed for the Edit menu:

hMenuPopup = LoadMenu (hInstance, "MenuEdit") ;
hBitmapEdit = StretchBitmap (LoadBitmap (hInstance, "BitmapEdit")) ;
AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (LPSTR) (LONG)
hBitmapEdit) ;

The popup menu for the three fonts is constructed from calls to the
GetBitmapFont function:

hMenuPopup = CreateMenu () ;
for (i = 0 ; i < 3 ; i++)
     {
     hBitmapPopFont [i] = GetBitmapFont (i) ;
     AppendMenu (hMenuPopup, MF_BITMAP,IDM_COUR + i,
                 (LPSTR) (LONG) hMenuPopupFont [i]) ;

The popup is then added to the menu:

hBitmapFont = StretchBitmap (LoadBitmap (hInstance, "BitmapFont")) ;
AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hBitmapFont,
            (LONG) (LPSTR) hBitmapFont) ;

The window menu is complete. Now you can include hMenu in the CreateWindow
call:

hwnd = CreateWindow (szAppName, "Bitmap Menu Demonstration",
               WS_OVERLAPPED,
               CW_USEDEFAULT, CW_USEDEFAULT,
               CW_USEDEFAULT, CW_USEDEFAULT,
               NULL, hMenu, hInstance, NULL) ;

After hwnd is available, GRAFMENU can alter the system menu. GRAFMENU first
obtains a handle to it:

hMenu = GetSystemMenu (hwnd, FALSE) ;

This loads the "Help" bitmap and stretches it to an appropriate size:

hBitmapHelp = StretchBitmap (LoadBitmap (hInstance, "BitmapHelp")) ;

This adds a separator bar and the stretched bitmap to the system menu:

AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
AppendMenu (hMenu, MF_BITMAP, IDM_HELP, (LPSTR) (LONG) hBitmapHelp) ;

Remember that bitmaps are GDI objects and must be explicitly deleted before
your program terminates. You accomplish this after GRAFMENU exits from its
message loop:

DeleteObject (hBitmapHelp) ;
DeleteObject (hBitmapEdit) ;
DeleteObject (hBitmapFile) ;
DeleteObject (hBitmapFont) ;

for (i = 0 ; i < 3 ; i++)
     DeleteObject (hBitmapPopFont [i]) ;

I'll conclude this section with a couple of miscellaneous notes:

  þ   In a top-level menu, Windows adjusts the menu bar height to
      accommodate the tallest bitmap. Other bitmaps (or character strings)
      are aligned at the top of the menu bar. The size of the menu bar
      obtained from:

      GetSystemMetrics (SM_CYMENU)

      is no longer valid after you put bitmaps in a top-level window.

  þ   As you can see from playing with GRAFMENU, you can use check marks
      with bitmapped menu items in popups, but the check mark is of normal
      size. If that bothers you, you can create a customized check mark and
      use SetMenuItemBitmaps.

  þ   Another approach to using non-text (or text in a font other than the
      system font) on a menu is the "owner-draw" item. The Windows Guide to
      Programming discusses this approach.


Adding a Keyboard Interface

Now we have another problem. When the menu contains text, Windows
automatically adds a keyboard interface. You can select a menu item using
the Alt key in combination with a letter of the character string. But once
you put a bitmap in a menu, you've eliminated that keyboard interface. Even
if the bitmap says something, Windows doesn't know about it.

This is where the WM_MENUCHAR message comes in handy. Windows sends a
WM_MENUCHAR message to your window procedure when you press Alt with a
character key that does not correspond to a menu item. We need to intercept
WM_MENUCHAR messages and check the value of wParam (the ASCII character of
the pressed key). If this corresponds to a menu item, we have to return a
long integer back to Windows where the high word is set to 2 and the low
word is set to the index of the menu item we want associated with that key.
Windows does the rest.



KEYBOARD ACCELERATORS

Described as simply as possible, keyboard accelerators are key combinations
that generate WM_COMMAND (or in some cases WM_SYSCOMMAND) messages. Most
often, programs use keyboard accelerators to duplicate the action of common
menu options. (However, keyboard accelerators can also perform nonmenu
functions.) For instance, many Windows programs have an Edit menu that
includes a Cut option; these programs conventionally assign the Del key as a
keyboard accelerator for this option. The user can choose the Cut option
from the menu by pressing an Alt-key combination or can use the keyboard
accelerator by simply pressing the Del key. When the window procedure gets a
WM_COMMAND message, it does not have to determine whether the menu or the
keyboard accelerator was used.

Why You Should Use Keyboard Accelerators

You may ask: Why should I use keyboard accelerators? Why can't I simply trap
WM- _KEYDOWN or WM_CHAR messages and duplicate the menu functions myself?
What's the advantage? For a single-window application, you can certainly
trap keyboard messages, but you get certain advantages from using keyboard
accelerators: You don't need to duplicate the menu and keyboard accelerator
logic. If the keyboard accelerator duplicates a menu function, Windows
flashes the top-level item on the menu when a keyboard accelerator is used,
thus providing some visual feedback to the user.

For applications with multiple windows and multiple window procedures,
keyboard accelerators become very important. As we've seen, Windows sends
keyboard messages to the window procedure for the window that currently has
the input focus. For keyboard accelerators, however, Windows sends the
WM_COMMAND message to the window procedure whose handle is specified in the
Windows function TranslateAccelerator. Generally, this will be your main
window, the same window that has the menu, which means that the logic for
acting upon keyboard accelerators does not have to be duplicated in every
window procedure.

This advantage becomes particularly important if you use modeless dialog
boxes (discussed in Chapter 10) or child windows on your main window's
client area. If a particular keyboard accelerator is defined to move among
windows, then only one window procedure has to include this logic. The child
windows do not receive WM_COMMAND messages from the keyboard accelerators.


Some Rules on Assigning Accelerators

In theory, you can define a keyboard accelerator for any virtual key or any
character key in combination with the Shift key, the Ctrl key, or both.
However, the CUA Advanced Interface Design Guide offers several
recommendations that are intended to achieve some consistency among
applications and to avoid interfering with Windows' use of the keyboard. For
programs that have an Edit menu, the CUA Advanced Interface Design Guide
highly recommends use of the following accelerators:

Key(s)                         Function
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Alt+Backspace                  Undo
Del                            Clear
Ctrl+Ins                       Copy
Shift+Ins                      Paste
Shift+Del                      Cut

You should avoid using Tab, Enter, Esc, and the Spacebar in keyboard
accelerators, because these are often used for system functions.

Although some older Windows programs use alphabetic keys in combination with
the Ctrl key for keyboard accelerators, more recent Windows programs use
function keys, sometimes in combination with the Shift key, the Ctrl key, or
both. These function-key assignments are common in some applications:

Key(s)                      Function
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
F1                          Help
F3                          Save
F6                          Next window
Shift+F6                    Previous window
Ctrl+F6                     Next section
Shift+Ctrl+F6               Previous section


The Accelerator Table

Keyboard accelerator tables are defined in your .RC resource script. The
general form is shown here:

MyAccelerators ACCELERATORS
     {
[accelerator definitions]
     }

This accelerator table name is MyAccelerators. The ACCELERATORS table does
not include load and memory options. You can have multiple ACCELERATORS
tables in your resource script.

Each keyboard accelerator you define requires a different line in the table.
There are four types of accelerator definitions:

"char",  wID          [,NOINVERT] [,SHIFT] [,CONTROL]

"^char", wID          [,NOINVERT] [,SHIFT] [,CONTROL]

nCode,   wID, ASCII   [,NOINVERT] [,SHIFT] [,CONTROL]

nCode,   wID, VIRTKEY [,NOINVERT] [,SHIFT] [,CONTROL]

In these examples, "char" means a single character enclosed in double
quotation marks, and "^char" is the character ^ and a single character in
double quotation marks. The wID number performs a function similar to the
menu ID in a menu definition. It is the value that Windows sends to your
window procedure in the WM_COMMAND message to identify the accelerator.
These are usually identifiers defined in a header file. When the keyboard
accelerator duplicates a menu command, use the same ID for both the menu and
the accelerator. When the keyboard accelerator does not duplicate a menu
command, use a unique ID.

Keyboard accelerators almost always select options in popup menus. Windows
automatically flashes a top-level menu item when you press an accelerator
key that duplicates an option in a popup. (For example, the Edit text
flashes if you press the Del key.) If you don't want the menu to flash,
include the option NOINVERT.

In the first type of accelerator definition, the keyboard accelerator is a
case-sensitive match of the character in double quotes:

"char",  wID          [,NOINVERT] [,SHIFT] [,CONTROL]

If you want to define a keyboard accelerator for that key in combination
with the Shift or Ctrl key or both, simply add SHIFT or CONTROL or both.

In the second type of definition, the keyboard accelerator is the character
in combination with the Ctrl key:

"^char", wID          [,NOINVERT] [,SHIFT] [,CONTROL]

This type is the same as the first type when the CONTROL keyword is used
with the character alone.

The third and fourth types use a number (nCode) rather than a character in
quotes:

nCode,   wID, ASCII   [,NOINVERT] [,SHIFT] [,CONTROL]
nCode,   wID, VIRTKEY [,NOINVERT] [,SHIFT] [,CONTROL]

This number is interpreted as either case-sensitive ASCII code or a virtual
key code, depending on the ASCII or VIRTKEY keyword.

The most common keyboard accelerators are the second and fourth types. You
use the second type for character keys in combination with Ctrl. For
example, this defines an accelerator for Ctrl-A:

"^A", wID

Use the fourth type for virtual key codes such as function keys. This
defines an accelerator for the Ctrl-F9 combination:

VK_F9, wID, VIRTKEY, CONTROL

The identifier VK_F9 is defined in WINDOWS.H as the virtual key code for the
F9 key, so you have to include the statement:

#include 

near the top of the resource script. The resource compiler defines an
identifier named RC_INVOKED that causes much of WINDOWS.H to be ignored.

The first and third types of definition shown above are rarely used. If you
want to use them, watch out for case-sensitivity. Windows does a
case-sensitive match on the "char"  or nCode based on the character you
press. When you add the SHIFT keyword, Windows checks to see if the Shift
key is depressed. This situation sometimes causes results you may not
anticipate. For instance, if "char" is "A", the keyboard accelerator is
invoked when you press the A key with the Shift key down or Caps Lock on,
but not both. If you use "A" with SHIFT, the A key must be pressed with
Shift down, but the accelerator can't be invoked at all when Caps Lock is
on. Similarly, "a" by itself is a keyboard accelerator for the unshifted A
key or for the A key with both Shift down and Caps Lock on. But "a" with
SHIFT invokes the accelerator only when Shift is down and Caps Lock is on.

When you define keyboard accelerators for a menu item, you should include
the key combination in the menu item text. The tab (\t) character separates
the text from the accelerator so that the accelerators align in a second
column. To notate accelerator keys in a menu, the CUA Advanced Interface
Design Guide recommends the text Ctrl or Shift followed by a plus sign and
the key--for instance:

  þ   F6

  þ   Shift+F6

  þ   Ctrl+F6


Loading the Accelerator Table

Within your program, you use the LoadAccelerators function to load the
accelerator table into memory and obtain a handle to it. The
LoadAccelerators statement is very similar to the LoadIcon, LoadCursor,
LoadBitmap, and LoadMenu statements.

First, define a handle to an accelerator table as type HANDLE:

HANDLE hAccel ;

Then load the accelerator table:

hAccel = LoadAccelerators (hInstance, "MyAccelerators") ;

As with icons, cursors, bitmaps, and menus, you can use a number for the
accelerator table name and then use that number in the LoadAccelerators
statement with the MAKEINTRESOURCE macro or in quotations preceded by a #
character.


Translating the Keystrokes

We will now tamper with three lines of code that are common to all the
Windows programs that we've created so far in this book. The code is the
standard message loop:

while (GetMessage (&msg, NULL, 0, 0))
     {
     TranslateMessage (&msg) ;
     DispatchMessage (&msg) ;
     }

Here's how we change it to use the keyboard accelerator table:

while (GetMessage (&msg, NULL, 0, 0))
     {
     if (!TranslateAccelerator (hwnd, hAccel, &msg))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     }

The TranslateAccelerator function determines if the message stored in the
msg message structure is a keyboard message. If it is, the function searches
for a match in the accelerator table whose handle is hAccel. If it finds a
match, it calls the window procedure for the window whose handle is hwnd. If
the keyboard accelerator ID corresponds to a menu item in the system menu,
then the message is WM_SYSCOMMAND. Otherwise, the message is WM_COMMAND.

When TranslateAccelerator returns, the return value is nonzero if the
message has been translated (and already sent to the window procedure) and 0
if not. If TranslateAccelerator returns a nonzero value, you should not call
TranslateMessage and DispatchMessage but rather loop back to the GetMessage
call.

The hwnd parameter in TranslateMessage looks a little out of place because
it's not required in the other three functions in the message loop.
Moreover, the message structure itself (the structure variable msg) has a
member named hwnd, which is also a handle to a window.

The fields of the msg structure are filled in by the GetMessage call. When
the second parameter of GetMessage is NULL, the function retrieves messages
for all windows belonging to the application. When GetMessage returns, the
hwnd member of the msg structure is the window handle of the window that
will get the message. However, when TranslateAccelerator translates a
keyboard message into a WM_COMMAND or WM_SYSCOMMAND message, it replaces the
msg.hwnd window handle with the window handle hwnd specified as the first
parameter to the function. That is how Windows sends all keyboard
accelerator messages to the same window procedure even if another window in
the application currently has the input focus. TranslateAccelerator does not
translate keyboard messages when a modal dialog box or message box has the
input focus, because messages for these windows do not come through the
program's message loop.

In some cases in which another window in your program (such as a modeless
dialog box) has the input focus, you may not want keyboard accelerators to
be translated. You'll see how to handle this in Chapter 10.


Receiving the Accelerator Messages

When a keyboard accelerator corresponds to a menu item in the system menu,
TranslateAccelerator sends the window procedure a WM_SYSCOMMAND message. If
you need to, you can differentiate between a direct system menu selection
and a keyboard accelerator for that system menu item by the high word of
lParam:

              wParam          LOWORD (lParam)  HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Accelerator:  Accelerator ID  0                1
Menu:         Menu ID         0                0

If the accelerator ID corresponds to a menu item (or does not correspond to
any item on the menu or system menu), TranslateAccelerator sends the window
procedure a WM_COMMAND message. The following table shows the types of
WM_COMMAND messages you can receive for keyboard accelerators, menu
commands, and child window controls:

              wParam          LOWORD (lParam)      HIWORD (lParam)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Accelerator:  Accelerator ID  0                    1
Menu:         Menu ID         0                    0
Control:      Control ID      Child window handle  Notification code

If the keyboard accelerator corresponds to a menu item, the window procedure
also receives WM_INITMENU, WM_INITMENUPOPUP, and WM_MENUSELECT messages,
just as if the menu option had been chosen. Programs usually enable and
disable items in a popup menu when processing WM_INITMENUPOPUP, so you still
have that facility when

using keyboard accelerators. If the keyboard accelerator corresponds to a
disabled or grayed menu item, however, TranslateAccelerator does not send
the window procedure a WM_COMMAND or WM_SYSCOMMAND message.

If the active window is minimized, TranslateAccelerator sends the window
procedure WM_SYSCOMMAND messages--but not WM_COMMAND messages--for keyboard
accelerators that correspond to enabled system menu items.
TranslateAccelerator also sends that window procedure WM_COMMAND messages
for accelerators that do not correspond to any menu items.


POPPAD with a Menu and Accelerators

In Chapter 6 we created a program called POPPAD1 that uses a child window
edit control to mimic some of the workings of Windows' Note Pad program. In
this chapter we'll add a File and Edit menu and call it POPPAD2. The Edit
items will all be functional; we'll finish the File functions in Chapter 10
and the Print function in Chapter 15. POPPAD2 is shown in Figure 9-12
beginning on the following page.

 POPPAD2.MAK

#-----------------------
# POPPAD2.MAK make file
#-----------------------

poppad2.exe : poppad2.obj poppad2.def poppad2.res
     link poppad2, /align:16, NUL, /nod slibcew libw, poppad2.def
     rc poppad2.res

poppad2.obj : poppad2.c poppad2.h
     cl -c -Gsw -Ow -W2 -Zp poppad2.c

poppad2.res : poppad2.rc poppad2.h poppad2.ico
     rc -r poppad2.rc

 POPPAD2.C

/*-----------------------------------------------------
   POPPAD2.C -- Popup Editor Version 2 (includes menu)
                (c) Charles Petzold, 1990
  -----------------------------------------------------*/

#include 
#include "poppad2.h"
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

char szAppName [] = "PopPad2" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HANDLE   hAccel ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;

          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (hInstance, szAppName) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, szAppName,
                          WS_OVERLAPPEDWINDOW,
                          GetSystemMetrics (SM_CXSCREEN) / 4,
                          GetSystemMetrics (SM_CYSCREEN) / 4,
                          GetSystemMetrics (SM_CXSCREEN) / 2,
                          GetSystemMetrics (SM_CYSCREEN) / 2,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     hAccel = LoadAccelerators (hInstance, szAppName) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          if (!TranslateAccelerator (hwnd, hAccel, &msg))
               {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
               }
          }

     return msg.wParam ;
     }

AskConfirmation (HWND hwnd)
     {
     return MessageBox (hwnd, "Really want to close PopPad2?",
                        szAppName, MB_YESNO | MB_ICONQUESTION) ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static HWND hwndEdit ;
     LONG        lSelect ;
     WORD        wEnable ;
     switch (message)
          {
          case WM_CREATE :
               hwndEdit = CreateWindow ("edit", NULL,
                         WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
                              WS_BORDER | ES_LEFT | ES_MULTILINE |
                              ES_AUTOHSCROLL | ES_AUTOVSCROLL,
                         0, 0, 0, 0,
                         hwnd, 1, ((LPCREATESTRUCT) lParam)->hInstance,
NULL) ;
               return 0 ;

          case WM_SETFOCUS :
               SetFocus (hwndEdit) ;
               return 0 ;

          case WM_SIZE :
               MoveWindow (hwndEdit, 0, 0, LOWORD (lParam),
                                           HIWORD (lParam), TRUE) ;
               return 0 ;

          case WM_INITMENUPOPUP :
               if (lParam == 1)
                    {
                    EnableMenuItem (wParam, IDM_UNDO,
                         SendMessage (hwndEdit, EM_CANUNDO, 0, 0L) ?
                              MF_ENABLED : MF_GRAYED) ;

                    EnableMenuItem (wParam, IDM_PASTE,
                         IsClipboardFormatAvailable (CF_TEXT) ?
                              MF_ENABLED : MF_GRAYED) ;

                    lSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0L) ;

                    if (HIWORD (lSelect) == LOWORD (lSelect))
                         wEnable = MF_GRAYED ;
                    else
                         wEnable = MF_ENABLED ;

                    EnableMenuItem (wParam, IDM_CUT,   wEnable) ;
                    EnableMenuItem (wParam, IDM_COPY,  wEnable) ;
                    EnableMenuItem (wParam, IDM_CLEAR, wEnable) ;

                    return 0 ;
                    }
               break ;
          case WM_COMMAND :
               if (LOWORD (lParam))
                    {
                    if (wParam == 1 && HIWORD (lParam) == EN_ERRSPACE)
                         MessageBox (hwnd, "Edit control out of space.",
                                     szAppName, MB_OK | MB_ICONSTOP) ;

                    return 0 ;
                    }

               else switch (wParam)
                         {
                         case IDM_NEW :
                         case IDM_OPEN :
                         case IDM_SAVE :
                         case IDM_SAVEAS :
                         case IDM_PRINT :
                              MessageBeep (0) ;
                              return 0 ;

                         case IDM_EXIT :
                              SendMessage (hwnd, WM_CLOSE, 0, 0L) ;
                              return 0 ;

                         case IDM_ABOUT :
                              MessageBox (hwnd,
                                   "POPPAD2 (c) Charles Petzold, 1990",
                                   szAppName, MB_OK | MB_ICONINFORMATION) ;
                              return 0 ;

                         case IDM_UNDO :
                              SendMessage (hwndEdit, WM_UNDO, 0, 0L) ;
                              return 0 ;

                         case IDM_CUT :
                              SendMessage (hwndEdit, WM_CUT, 0, 0L) ;
                              return 0 ;

                         case IDM_COPY :
                              SendMessage (hwndEdit, WM_COPY, 0, 0L) ;
                              return 0 ;

                         case IDM_PASTE :
                              SendMessage (hwndEdit, WM_PASTE, 0, 0L) ;
                              return 0 ;

                         case IDM_CLEAR :
                              SendMessage (hwndEdit, WM_CLEAR, 0, 0L) ;
                              return 0 ;
                         case IDM_SELALL :
                              SendMessage (hwndEdit, EM_SETSEL, 0,
                                           MAKELONG (0, 32767)) ;

                              return 0 ;
                         }
               break ;

          case WM_CLOSE :
               if (IDYES == AskConfirmation (hwnd))
                    DestroyWindow (hwnd) ;
               return 0 ;

          case WM_QUERYENDSESSION :
               if (IDYES == AskConfirmation (hwnd))
                    return 1L ;
               else
                    return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 POPPAD2.RC

/*----------------------------
   POPPAD2.RC resource script
  ----------------------------*/

#include 
#include "poppad2.h"

PopPad2 ICON poppad2.ico

PopPad2 MENU
     {
     POPUP "&File"
          {
          MENUITEM "&New",              IDM_NEW
          MENUITEM "&Open...",          IDM_OPEN
          MENUITEM "&Save",             IDM_SAVE
          MENUITEM "Save &As...",       IDM_SAVEAS
          MENUITEM SEPARATOR
          MENUITEM "&Print",            IDM_PRINT
          MENUITEM SEPARATOR
          MENUITEM "E&xit",             IDM_EXIT
          MENUITEM "&About PopPad2...", IDM_ABOUT
          }
     POPUP "&Edit"
          {
          MENUITEM "&Undo\tAlt+BkSp",   IDM_UNDO
          MENUITEM SEPARATOR
          MENUITEM "Cu&t\tShift+Del",   IDM_CUT
          MENUITEM "&Copy\tCtrl+Ins",   IDM_COPY
          MENUITEM "&Paste\tShift+Ins", IDM_PASTE
          MENUITEM "C&lear\tDel",       IDM_CLEAR
          MENUITEM SEPARATOR
          MENUITEM "&Select All",       IDM_SELALL
          }
     }

PopPad2 ACCELERATORS
     {
     VK_DELETE, IDM_CUT,   VIRTKEY, SHIFT
     VK_INSERT, IDM_COPY,  VIRTKEY, CONTROL
     VK_INSERT, IDM_PASTE, VIRTKEY, SHIFT
     VK_DELETE, IDM_CLEAR, VIRTKEY
     }

 POPPAD2.H

/*-----------------------
   POPPAD2.H header file
  -----------------------*/

#define IDM_NEW      1
#define IDM_OPEN     2
#define IDM_SAVE     3
#define IDM_SAVEAS   4
#define IDM_PRINT    5

#define IDM_EXIT     6
#define IDM_ABOUT    7

#define IDM_UNDO     8
#define IDM_CUT      9
#define IDM_COPY    10
#define IDM_PASTE   11
#define IDM_CLEAR   12
#define IDM_SELALL  13

 POPPAD2.ICO  -- Please refer to the book.

 POPPAD2.DEF

;------------------------------------
; POPPAD2.DEF module definition file
;------------------------------------

NAME           POPPAD2

DESCRIPTION    'Popup Editor Version 2 (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The POPPAD2.RC resource script file contains the menu and accelerator table.
You'll notice that the accelerators are all indicated within the text
strings of the Edit popup menu following the tab (\t) character.


Enabling Menu Items

The major job in the window procedure now involves enabling and graying the
options in the Edit menu, which is done when processing the
WM_INITMENUPOPUP. First, the program checks to see if the Edit popup is
about to be displayed. Because the position index of Edit in the menu
(starting with File at 0) is 1, lParam equals 1 if the Edit popup is about
to be displayed.

To determine if the Undo option can be enabled, POPPAD2 sends an EM_CANUNDO
message to the edit control. The SendMessage call returns nonzero if the
edit control can perform an Undo action, in which case the option is
enabled; otherwise, it is grayed:

EnableMenuItem (wParam, IDM_UNDO,
     SendMessage (hwndEdit, EM_CANUNDO, 0, 0L) ?
                  MF_ENABLED : MF_GRAYED) ;

The Paste option should be enabled only if the clipboard currently contains
text. We can determine this through the IsClipboardFormatAvailable call with
the CF_TEXT identifier:

EnableMenuItem (wParam, IDM_PASTE,
     IsClipboardFormatAvailable (CF_TEXT) ?
                   MF_ENABLED : MF_GRAYED) ;

The Cut, Copy, and Clear options should be enabled only if text in the edit
control has been selected. Sending the edit control an EM_GETSEL message
returns a long integer containing this information:

lSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0L) ;

The low word of lSelect is the position of the first selected character; the
high word of lSelect is the position of the character following the
selection. If these two words are equal, no text has been selected:

if (HIWORD (lSelect) == LOWORD (lSelect))
     wEnable = MF_GRAYED ;
else
     wEnable = MF_ENABLED ;

The value of wEnable is then used for the Cut, Copy, and Clear options:

EnableMenuItem (wParam, IDM_CUT,   wEnable) ;
EnableMenuItem (wParam, IDM_COPY,  wEnable) ;
EnableMenuItem (wParam, IDM_CLEAR, wEnable) ;


Processing the Menu Options

Of course, if we were not using a child window edit control for POPPAD2, we
would now be faced with the problems involved with actually implementing the
Undo, Cut, Copy, Paste, Clear, and Select All options from the Edit menu.
But the edit control makes this process easy, because we merely send the
edit control a message for each of these options:

case IDM_UNDO :
     SendMessage (hwndEdit, WM_UNDO, 0, 0L) ;
     return 0 ;

case IDM_CUT :
     SendMessage (hwndEdit, WM_CUT, 0, 0L) ;
     return 0 ;

case IDM_COPY :
     SendMessage (hwndEdit, WM_COPY, 0, 0L) ;
     return 0 ;

case IDM_PASTE :
     SendMessage (hwndEdit, WM_PASTE, 0, 0L) ;
     return 0 ;

case IDM_CLEAR :
     SendMessage (hwndEdit, WM_CLEAR, 0, 0L) ;
     return 0 ;

case IDM_SELALL :
     SendMessage (hwndEdit, EM_SETSEL, 0,
                            MAKELONG (0, 32767)) ;
     return 0 ;

Notice that we could have simplified this even further by making the values
of IDM_UNDO, IDM_CUT, and so forth equal to the values of the corresponding
window messages WM_UNDO, WM_CUT, and so forth.

The About option for the File popup invokes a simple message box:

case IDM_ABOUT :
     MessageBox (hwnd,
          "POPPAD2 (c) Charles Petzold, 1990",
          szAppName, MB_OK | MB_ICONINFORMATION) ;
     break ;

In Chapter 10 we'll make this a dialog box.

The Exit option sends the window procedure a WM_CLOSE message:

case IDM_EXIT :
     SendMessage (hwnd, WM_CLOSE, 0, 0L) ;
     return 0 ;

That is precisely what DefWindowProc does when it receives a WM_SYSCOMMAND
message with wParam equal to SC_CLOSE.

In previous programs we have not processed the WM_CLOSE messages in our
window procedure but have simply passed them to DefWindowProc. DefWindowProc
does something very simple with WM_CLOSE: It calls the DestroyWindow
function. Rather than send WM_CLOSE messages to DefWindowProc, however,
POPPAD2 processes them. This fact is not so important now, but it will
become very important in Chapter 10 when POPPAD can actually edit files:

case WM_CLOSE :
     if (IDYES == AskConfirmation (hwnd))
          DestroyWindow (hwnd) ;
     return 0 ;

AskConfirmation is a function in POPPAD2 that displays a message box asking
for confirmation to close the program:

AskConfirmation (HWND hwnd)
     {
     return MessageBox (hwnd, "Really want to close POPPAD2?",
                       szAppName, MB_YESNO | MB_ICONQUESTION) ;
     }

The message box (as well as the AskConfirmation function) returns IDYES if
the Yes button is selected. Only then does POPPAD2 call DestroyWindow.
Otherwise, the program is not terminated.

If you want confirmation before terminating a program, you must also process
WM_QUERYENDSESSION messages. Windows begins sending every window procedure a
WM_QUERYENDSESSION message when the user chooses Close from the MS-DOS
Executive system menu. If any window procedure returns 0 from this message,
the Windows session is not terminated. Here's how we handle
WM_QUERYENDSESSION:

case WM_QUERYENDSESSION :
     if (IDYES == AskConfirmation (hwnd))
          return 1L ;
     else
          return 0 ;

The WM_CLOSE and WM_QUERYENDSESSION messages are the only two messages you
have to process if you want to ask for user confirmation before ending a
program. That's why we made the Exit menu option in POPPAD2 send the window
procedure a WM_CLOSE message--by doing so, we avoided having to ask for
confirmation at yet a third point.

If you process WM_QUERYENDSESSION messages, you may also be interested in
the WM_ENDSESSION message. Windows sends this message to every window
procedure that has previously received a WM_QUERYENDSESSION message. The
wParam parameter is 0 if the session fails to terminate because another
program has returned 0 from WM_QUERYENDSESSION. The WM_ENDSESSION message
essentially answers the question: I told Windows it was OK to terminate me,
but did I really get terminated?

Although I've included the normal New, Open, Save, and Save As options in
POPPAD2's File menu, they are currently nonfunctional. To process these
commands, we need to use dialog boxes. You're now ready to learn about them.