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.