CS-360
Fall, 2000
Class 7
R. Eckert

MENUS

Up to now our Windows programs have had very simple menus. Windows provides
support for very complex menus, popup menus that appear under the main menu bar,
and menu bars with graphics images. We can also alter a program's menu
dynamically as the program executes. (This is useful for programs that
operate in more than one state, or if you want to support beginner/advanced
versions of menus.)

CREATING MENUS--

  Write a source .RC resource script file containing the menu definition.

  Or use Developer Studio's menu editor to create the menu visually.

Simplest menus--consist of key words, and the contents of menu don't change.
(Recall previous examples.)

MYMENU     MENU
BEGIN
        MENUITEM "&Beep", IDM_BEEP
        MENUITEM "&Quit", IDM_QUIT
END

After this is compiled and bound, Windows takes care of the details of
positioning each item, highlighting items when selected, and sending
WM_COMMAND messages when items are selected.

Syntax:
       MenuName  MENU
       BEGIN
          /* menu definition goes here */
       END

MENU is a key word the resource compiler recognizes;

MenuName--a string used to find the menu data in the program resources
saved with the resource data; Windows uses it to find the actual data. The
.exe file would look like:

                       __________________
                      |                  |
                      |                  v
      ----------------------------------------------------
     | Code         MYICON   MYMENU  Icon data  Menu Data |
      ----------------------------------------------------
                               |                    ^
                               |____________________|

Menu items--Go between BEGIN and END; can only be MENUITEM or POPUP:

MENUITEM  string,  MenuID   options
or
MENUITEM  SEPARATOR    --> causes a horizontal line to be drawn between
                           previous and following menu items.

string: The menu item's characters enclosed in " ". Any character preceded
        by a & will be underlined on the menu bar; ==> accelerator key <Alt>
        <key> can also be used to select that item. (Works only if program's
        message loop has a TranlateMessage()).

MenuID: A number (usually a defined constant) that becomes the low word of the 
        wParam that is passed with the WM_COMMAND message that is sent by Windows 
        whenever the user clicks that item or presses its keyboard accelerator.

option: How the item is to appear and whether or not it is active--
        CHECKED, GRAYED (inactive and gray), INACTIVE (inactive),
        MENUBARBREAK, MENUBREAK (both place item on new line).

Popup menus--Used when the number of menu items gets too big. Windows will
wrap to make multiple-line menus; but that uses client area space.

Popup menus are used to give user more selections without using extra space.
We can have popups from popups (nested) up to 8 levels. The syntax is:

POPUP  string  options

     Here string gives the title of the popup menu--what will appear on
     menu bar.

     No ID is needed since popup titles are not selectable by themselves and
     don't generate WM_COMMAND messages.

     options-- MENUBARBREAK (places the new item in a new column -->multicolumn,
     rectangular popup menu; a line is used to separate this item from
     previous one) MENUBREAK (same as above, but with no separating line).


The MENU1 program--

Selection 1 ==> Enables Selection 3 (no longer grayed out).
Selection 2 ==> Toggles the checked status of Selection 2.
Selection 3 ==> Nothing if disabled; Confirms that Selection 3 is enabled.
Right ==> Disables Selection 3.
Left ==> Beeps.
Quit ==> Exits Windows.
Help ==> Creates a box that says "not much help". "Select any menu item."

All these actions are in response to the WM_COMMAND messages from the menu
items selected.

In each case, where a choice is supposed to do something to one of the other
menu items, we must get a handle to the entire menu so we can access its
items:

     GetMenu(hWnd) returns a handle to the menu attached to the given window

Then EnableMenuItem() is used to enable or disable a specified menu item in 
the menu:

     EnableMenuItem(hmenu, idEnableItem, ActionFlag);

hmenu is the handle to the menu that contains the item to be
activated/deactivated;

idEnableItem specifies which item is to be activated or deactivated
(two ways of doing it: by menu item ID number or position);

The ActionFlag specifies an action and which way the second parameter is 
going to be specified. Examples:

    MF_BYCOMMAND | MF_ENABLED  ==> we're enabling it by specifying the
    command in the second parameter.

    MF_BYPOSITION | MF_DISABLED  ==> we're disabling it and specifying the
    menu item by its position.


e.g., EnableMenuItem(hMEnu, IDM_SEL3, MF_BYCOMMAND | MF_ENABLED);

Position numbers are relative to top-most, left-most item (position 0);
It's difficult to keep track of menu item positions, so this is not used
very often.

Possible actions are: MF_ENABLED, MF_DISABLED (seldom used, since it is confusing 
to the user), and MF_GRAYED.

In MENU1 IDM_SEL2 is supposed to toggle the checked status. So we keep track of 
its state using a static BOOL variable bCheckOnOFf. The status is changed using
the conditional assignment statement:

         bCheckOnOF = bCheckOnOff ? FALSE : TRUE;

i.e., if it's true, assign it a value of FALSE, if not a value of TRUE.

Then the CheckMenuItem() checks/unchecks the item. This function works
just like EnableMenuItem(). Action flag: MF_CHECKED or MF_UNCHECKED.

IDM_RIGHT does the same kind of stuff.

As an alternative to having our program track the status of a menu item
(e.g., with a static BOOL variable as above), we could have used
GetMenuState()--returns a UINT value which encodes the status of a menu item
as a combination of MF_CHECKED, MF_ENABLED, etc. For example, the following
fragment checks if IDM_MENUID is checked:

HMENU hMenu;
WORD wStatus;
hMenu = GetMenu(hWnd);
wStatus = GetMenuState(hMenu, IDM_MENUID, MF_BYCOMMAND);
if (wSataus & MF_CHECKED)
  /* code to deal with checked state */
else
  /* other code */

The appearance of the menu is determined by the .RC file. The following
figures show the appearance of two different .RC files that were created
with the Developer Studio menu editor and then compiled and linked with the
SAME MENU1.CPP file. This again illustrates how the "look and feel" of a Windows
program is determined by the program's resources, whereas it's behavior is
determined by the source code. There are many advantages to keeping these
two aspects of a program separate.


One Menu1 Format

   
Another Menu1 Format

CREATING MENUS ON THE FLY (as the program operates)--

We may want to be able to change menus and/or menu items dynamically because:

 Certain operations may not always be possible, and we want to delete
 these items from the menu when action isn't possible (instead of just
 graying them).

 Other operations may become possible, and you want to add them to the menu.

 You may want to use a bitmap image in place of text for one or more popup
 menu items--e.g., for tool selection (picking a brush image for
 painting). Graphical menu items cannot be defined in the resource script
 file, but can be created as the program runs.

MENU-ALTERING FUNCTIONS--

CreateMenu()        Creates a new menu, ready to add items.

CreatePopupMenu()   Creates a new popup menu, ready to receive items.

SetMenu()           Attaches menu to a window; often used with LoadMenu() to
                    switch between several menus used by the program.

AppendMenu()        Adds a new menu item or popup to the end of a menu.

InsertMenu()        Inserts a new menu item or popup into a menu/popup menu.

DeleteMenu()        Removes a menu item from a menu or popup menu.

DestroyMenu()       Deletes an entire menu, removing it from memory; only
                    needed if the menu was loaded but not attached to a window.

DrawMenuBar()       Draws the menu bar (in main menu area below window caption),
                    making any changes visible.

LoadMenu()          Loads a menu from the program's resource data, ready to
                    be attached to a Window with SetMenu().

Basic sequence--

  1. Use CreateMenu() to create a new, empty menu with which to start;
     returns a handle to the new menu.

  2. Use AppendMenu() and/or InsertMenu() to add menu items as needed.

  3. Use SetMenu() to attach the menu to a window, so it can be used.

If the main menu contains popup menus, the popups are created separately,
and then attached to the menu as follows:

  1. Use CreatePopupMenu() to create a new, empty popup menu. Returns a
     handle to the popup menu.

  2. Use AppendMenu() or InsertMenu() to add menu items to popup as needed.

  3. Use AppendMenu() or InsertMenu() to add the finished popup menu to the
     main menu.

If a menu (with popups) is attached to the window when the window is
destroyed, the menu will be removed from memory; if not, you must destroy it
using DestroyMenu(), or it will remain in memory for the entire Windows
session.

You can also insert or delete menu items using InsertMenu() or DeleteMenu()--
This is usually easier than creating an entire menu from scratch and more
flexible than defining multiple menus in the program's resource (.RC)
file and switching between them with LoadMenu() and SetMenu().

Steps to creating a menu with bitmap images--

  1. Create the image as a bitmap using Developer Studio (.BMP).

  2. Include the bitmaps in the program's resource data.

  3. Load the bitmap data into memory while the program is running using
     LoadBitmap() -- returns a handle to the bitmap.

  4. Use AppendMenu() or InsertMenu() to add the bitmap as a menu item.

  5. Attach the menu to the window with SetMenu().

  6. At termination (or after the bitmap is not needed), use DeleteObject() to
     remove the bitmap from memory.


The MENU2 example program -- No menu is defined in the .RC file; instead the
main menu is created upon receipt of the WM_CREATE message. The initial
popup menu has three bitmap images. Clicking on either changes the mouse
cursor to that shape ==> we need to include two cursors (the third is the 
predefined ARROW cursor) along with the three bitmaps in the resource file.
(Bitmaps and cursors have different formats, so we need both.)

The following figures show the application with its initial menu and the
result of selecting the "Tools" popup menu:

    
    

The following figure shows the application after the user has selected
the initial menu item: "Add Menu Items". Note that the new "Added Popup Menu"
has been added and is active, while the original menu item: "Add Menu Items"
is inactive. Also note that the item: "New Selection 2, beeps if action" on the
new popup is initially inactive.



The following figure shows the situation after the user selects the item: "New
Selection 1, toggles next item". Note how the item: "New Selection 2, beeps if
active" is now active.



Finally, if the user selects the item: "Delete this entire popup menu", the
situation is restored to that shown in the first figure in this sequence, above.
In other words, the new popup menu disappears.

Creating the MENU2 resources with Developer Studio's visual editors--

We need to create two cursors (ID "CUTCURSOR" with filename cutcur.cur and
"GLUECUR" with filename gluecur.cur). Use the Developer Studio cursor editor to
create and insert these resources into the project (as described in previous
examples).

The Developer Studio Bitmap Editor is used to create bitmap resources in the
same way as the Cursor Editor is used to create cursors. We need three of
them: ID "CUTBMP" with filename cutbmp.bmp, ID "PASTEBMP" with filename
pastebmp.bmp, and "ARROWBMP" with filename arrowbmp.bmp.

As mentioned above, there is no menu resource for this project, since the
menu is created dynamically within the program. However, we do need to
assign constant values to the IDM constant names used in the program. This
is done in the menu2.h file, which will need to be included along with the
resource.h file.

In addition, in the .cpp file, the constants ARROWCURSOR, GLUECURSOR, and
CUTCURSOR are used in a switch/case statement. These need to be given
constant values. This is also done in the menu2.h file.

The MENU2.CPP file--

The initial main menu has a popup menu containing the three bitmaps and
another popup containing three other text menu items. All of these need to 
be created.

Steps:
   Create the main menu and popup menu (empty); i.e., get handles.
   Load the bitmaps that are to go into the popup menu.
     (Need instance of the module whose executable contains the bitmaps.)
   Append the bitmaps to the menu--
      AppendMenu(hMenu, menu_item_flags, menu_item_id, menu_item_content).
      menu_item_flags: MF_BITMAP, MF_STRING
      menu_item_id's: from resouce data (IDM_'s)
      menu_item_content examples: (LPSTR)hCut
                                  "&Quit"
      Normally the last parameter is a pointer to a string, so we need the
      casts for bitmaps.
   Attach the entire menu structure to program's window with SetMenu().

Without the call to SetMenu(), the menu structure would be defined in
memory, but not related to the program's window.

The only reason we create the menu this way is because it has bitmaps. If
not, it would be much easier to just define the menu in the .RC file.

"Add Menu Item" (WM_COMMAND message, LOWORD(wParam) = IDM_ADD) adds a new 
popup menu to the menu bar. The variable bAddedItems keeps track of whether
the popup has been added. If not, it is created just as the main menu with calls
to AppendMenu() and InsertMenu(). The variable bSelTwoOn keeps track of the
state of "Select2" on this new menu. DrawMenuBar() is called after the popup 
is added to redraw the menu bar with the new entry. Otherwise, the menu bar 
would not change until Windows updates the nonclient area of the window.

Note that after we've added this popup once, the "Add Menu Items" is grayed
out, so we can't add it more than once.

If the user clicks on new popup's "Delete this entire popup menu", a
WM_COMMAND with LOWWORD(wParam) = IDM_REMOVE is sent. The program reacts to 
this by getting the handle to the menu and, if the popup exists, deletes the item
(the popup) in the first position. The "Add Menu Items" is then activated,
and the menu bar redrawn.

The "Select1" item on the new popup (IDM_SEL1) toggles "Select2" on and off
using EnableMenuItem() and CheckMenuItem().

The "Select2" item (if active IDM_SEL2) just beeps.

Clicking on either bitmap from the Tools popup (IDM_CUT, IDM_PASTE) just
changes the nCursor variable that keeps track of the current cursor. This
variable is examined when processing the WM_SETCURSOR message. Recall that
this message is sent to a window if the mouse causes the cursor to move in
that window. Each time that happens, the switch statement on hCursor uses
LoadCursor() to set the current mouse cursor.

Note that the original menu when the window class was registered is NULL,
since the menu is loaded dynamically in the program.

Also note that when the window is destroyed, DeleteObject() is called to get
rid of the bitmaps.