http://www-user.tu-chemnitz.de/~heha/petzold/
Menus
Do you remember the Monty Python skit about the cheese shop? Here's how it goes: A guy comes into a cheese shop and wants a particular type of cheese. The shop doesn't have it. So he asks for another type of cheese, and another, and another, and another (eventually totaling about 40 types, most of which are quite obscure), and still the answer is "No, no, no, no, no." Ultimately, there's a shooting involved.
This whole unfortunate incident could have been avoided through the use of menus. A menu is a list of available options. A menu tells a hungry patron what the kitchen can serve up and—for a Windows program—tells the user what operations an application is capable of performing.
A menu is probably the most important part of the consistent user interface that Windows programs offer, and adding a menu to your program is a relatively easy part of Windows programming. You define the menu in Developer Studio. Each selectable menu item is given a unique ID number. 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.
After discussing menus, I'll conclude this chapter with a section on keyboard accelerators, which are key combinations that are used primarily to duplicate menu functions.
Menu Concepts
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 usually invoke drop-down menus, which are also called "popup menus" or "submenus." You can also 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 the next chapter.) Most parent windows have, to the far left of the caption bar, a display of the program's small icon. This icon 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 by 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 the handle to 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.
Defining the Menu
To use Developer Studio to add a menu to your program's resource script, select Resource from the Insert menu and pick Menu. (But you probably figured that out already.) You can then interactively define your menu. Each item in the menu has an associated Menu Item Properties dialog box that indicates the item's text string. If the Pop-up box is checked, the item invokes a popup menu and no ID is associated with the item. If the Pop-up box is not checked, the item generates a WM_COMMAND message with a specified ID. These two types of menu items will appear in the resource script as POPUP and MENUITEM statements, respectively.
When you type the text for an item in a menu, you can type an ampersand (&) to indicate that the following character is to be underlined when Windows displays the menu. Such an underlined character is 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 instead use the first letter of the menu item's text for Alt-key searches.
If you select the Grayed option in the Menu Items Properties dialog box, the menu item is inactive, its text is grayed, and the item does not generate a WM_COMMAND message. If you select the Inactive option, the menu item is inactive and does not generate a WM_COMMAND message but its text is displayed normally. The Checked option places a check mark next to a menu item. The Separator option causes a horizontal separator bar to be drawn on popup menus.
For items in popup menus, you can use the columnar tab character \t in the character 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 we look at keyboard accelerators toward the end of this chapter. A \a in the character string right-justifies the text that follows it.
The ID values you specify are the numbers that Windows sends to the window procedure in menu messages. The ID values should be unique within a menu. By convention, I use identifiers beginning 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. You can give the menu a text name that is the same as the name of the program. Programmers often use the name of the program as the name of the menu so that the same character string can be used for the window class, the name of the program's icon, and the name of the menu. The program then makes reference to this menu in the definition of the window class:
wndclass.lpszMenuName = szAppName ;
Although specifying the menu in the window class is the most common way to reference a menu resource, that's not the only way to do it. A Windows application can load a menu resource into memory with the LoadMenu function, which is similar to the LoadIcon and LoadCursor functions described earlier. LoadMenu returns a handle to the menu. If you use a name for the menu in the resource script, the statement looks like this:
hMenu = LoadMenu (hInstance, TEXT ("MyMenu")) ;
If you use a number, the LoadMenu call takes this form:
hMenu = LoadMenu (hInstance, MAKEINTRESOURCE (ID_MENU)) ;
You can then specify this menu handle as the ninth parameter to CreateWindow:
hwnd = CreateWindow (TEXT ("MyClass"), TEXT ("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 name in the window class and a NULL menu handle in theCreateWindow 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. Any menu that is attached to a window is destroyed when the window is destroyed. Any menus not attached to a window should be explicitly destroyed by calls to DestroyMenu before the program terminates.
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 toDefWindowProc. One such message is WM_INITMENU with the following parameters:
wParam: | Handle to main menu |
lParam: | 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 disconcerting to the user.
Your program also receives WM_MENUSELECT messages. A program can receive many WM_MENUSELECT messages as the user moves the cursor or mouse among the menu items. This is helpful for implementing a status bar that contains a full text description of the menu option. The parameters that accompany WM_MENUSELECT are as follows:
LOWORD (wParam): | Selected item: Menu ID or popup menu index |
HIWORD (wParam): | Selection flags |
lParam: | Handle to menu containing 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 high word ofwParam 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: | Popup menu handle |
LOWORD (lParam): | Popup index |
HIWORD (lParam): | 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 whether 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 fromChapter 8 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 examining the value of lParam, which will be 0 for a menu item.
Menus | Controls | |
LOWORD (wParam): | Menu ID | Control ID |
HIWORD (wParam): | 0 | Notification code |
lParam: | 0 | Child window handle |
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: | Menu ID |
lParam: | 0 |
However, if the WM_SYSCOMMAND message is the result of a mouse click, LOWORD (lParam) and HIWORD (lParam) will contain the x and y screen coordinates of the mouse cursor's location.
For WM_SYSCOMMAND, 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 by ANDing with 0xFFF0. 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.
If you add menu items to the system menu, the low word of wParam will be the menu ID that you define. To avoid conflicts with the predefined menu IDs, use values below 0xF000. It is important that you pass normal WM_SYSCOMMAND messages toDefWindowProc. If you do not, you'll effectively disable the normal system menu commands. The final message we'll look at 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:
LOWORD (wParam): | Character code (ASCII or Unicode) |
HIWORD (wParam): | Selection code |
lParam: | 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 inChapter 14.
A Sample Program
Let's look at a simple example. The MENUDEMO program, shown in Figure 10-6, 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 low word of wParam.
Figure 10-6. The MENUDEMO program.
The MENUDEMO.RC resource script should give you hints on defining the menu. The menu has a text name of "MenuDemo." Most items have underlined letters, which means you must type an ampersand (&) before the letter. The MENUITEM SEPARATOR statement results from checking the Separator box in the Menu Item Properties dialog box. Notice that one item in the menu has the Checked option and another has the Grayed option. Also, the five items in the Background popup menu should be entered in the order shown to ensure that the identifiers are in numeric order; the program relies on this.
All the menu item identifiers are defined in RESOURCE.H. 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_BKGND_WHITE) is flagged as CHECKED, which places a check mark next to the item. In MENUDEMO.C, the value ofiSelection is initially set to IDM_BKGND_WHITE.
The five brushes on the Background popup menu 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, iSelection, MF_UNCHECKED) ;
The iSelection value is set to the value of wParam, and the new background color is checked:
iSelection = wParam ; CheckMenuItem (hMenu, iSelection, 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_TIMER_START, MF_GRAYED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_ENABLED) ;
On receipt of a WM_COMMAND message with wParam equal to IDM_TIMER_STOP, MENUDEMO kills the timer, activates the Start option, and grays the Stop option:
EnableMenuItem (hMenu, IDM_TIMER_START, MF_ENABLED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_GRAYED) ;
Notice that it's impossible for MENUDEMO to receive a WM_COMMAND message withwParam equal to IDM_TIMER_START while the timer is going. Similarly, it's impossible to receive a WM_COMMAND with wParam equal to IDM_TIMER_STOP while the timer is not going. When MENUDEMO receives a WM_COMMAND message with the wParam parameter equal to IDM_APP_ABOUT or IDM_APP_HELP, it displays a message box. (In the next chapter, we'll change this to a dialog box.)
When MENUDEMO receives a WM_COMMAND message with wParam equal to IDM_APP_EXIT, it sends itself a WM_CLOSE message. This is the same message thatDefWindowProc sends the window procedure when it receives a WM_SYSCOMMAND message withwParam equal to SC_CLOSE. We'll examine this more in the POPPAD2 program shown near the end of this chapter.
Menu Etiquette
The format of the File and Edit popups in MENUDEMO is quite similar to those in other Windows programs. 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 in combination with the Alt key.
Beyond the File and Edit popups, the menus of most Windows programs will probably 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.
Although your program menu can have MENUITEM statements on the top level, these are not typical because they can be too easily chosen by mistake. If you do this, use an exclamation point after the text string to indicate that the menu item does not invoke a popup.
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 by using two functions called CreateMenu and AppendMenu. After you finish defining the menu, you can pass the menu handle toCreateWindow 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 10-7 creates a menu in this fashion; in fact, it is the same menu that I used in the MENUDEMO program. For illustrative simplicity, the code uses ASCII character strings.
Figure 10-7. C code that creates the same menu as used in the MENUDEMO program but without requiring a resource script file.
hMenu = CreateMenu () ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_NEW, "&New") ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_OPEN, "&Open...") ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_SAVE, "&Save") ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_SAVE_AS, "Save &As...") ; AppendMenu (hMenuPopup, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenuPopup, MF_STRING, IDM_APP_EXIT, "E&xit") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&File") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_UNDO, "&Undo") ; AppendMenu (hMenuPopup, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_CUT, "Cu&t") ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_COPY, "&Copy") ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_PASTE, "&Paste") ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_CLEAR, "De&lete") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Edit") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING¦ MF_CHECKED, IDM_BKGND_WHITE, "&White") ; AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_LTGRAY, "&Light Gray"); AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_GRAY, "&Gray") ; AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_DKGRAY, "&Dark Gray"); AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_BLACK, "&Black") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Background") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_TIMER_START, "&Start") ; AppendMenu (hMenuPopup, MF_STRING ¦ MF_GRAYED, IDM_TIMER_STOP, "S&top") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Timer") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_HELP_HELP, "&Help") ; AppendMenu (hMenuPopup, MF_STRING, IDM_APP_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 could 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. TheLoadMenuIndirect 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.
Floating Popup Menus
You can also 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. The POPMENU program in Figure 10-8 shows how this is done.
Figure 10-8. The POPMENU program.
The POPMENU.RC resource script defines a menu similar to the one in MENUDEMO.RC. The difference is that the top-level menu contains only one item—a popup named "MyMenu" that invokes the File, Edit, Background, and Help options. These four options will be arranged on the popup menu in a vertical list rather than on the main menu in a horizontal list.
During the WM_CREATE message in WndProc, POPMENU obtains a handle to the first popup menu—that is, the popup with the text "MyMenu":
hMenu = LoadMenu (hInst, szAppName) ; hMenu = GetSubMenu (hMenu, 0) ;
During the WM_RBUTTONUP message, POPMENU obtains the position of the mouse pointer, converts the position to screen coordinates, and passes the coordinates toTrackPopupMenu:
point.x = LOWORD (lParam) ; point.y = HIWORD (lParam) ; ClientToScreen (hwnd, &point) ; TrackPopupMenu (hMenu, TPM_RIGHTBUTTON, 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.
If you want to use the same menu for the program's main menu and with the TrackPopupMenu, you'll have a bit of a problem because the function requires a popup menu handle. A workaround is provided in the Microsoft Knowledge Base article ID Q99806.
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 by adding your own menu commands. In the early days of Windows, programs commonly put the "About" menu item on the system menu. While modifying the system menu is not nearly as common these days, it remains 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 0xF000. Otherwise, they will conflict with the IDs that Windows uses for the normal system menu commands. And keep in mind that 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 10-9, adds a separator bar and three commands to the system menu. The last of these commands removes the additions.
Figure 10-9. The POORMENU program.
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_SYS_ABOUT, TEXT ("About...")) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_HELP, TEXT ("Help...")) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_REMOVE, TEXT ("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 callingGetSystemMenu again with the second parameter set to TRUE:
GetSystemMenu (hwnd, TRUE) ;
The standard system menu has the options Restore, Move, Size, Minimize, Maximize, and Close. These generate WM_SYSCOMMAND messages with wParam equal to SC_RESTORE, SC_MOVE, SC_SIZE, SC_MINIMUM, SC_MAXIMUM, and SC_CLOSE. 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.0, 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 (at least at that time). Times have changed. Many other current functions are now more complex thanChangeMenu ever was, and ChangeMenu has been replaced with five newer 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
In this section, you'll find 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) ;
Notice that the argument to DrawMenuBar is a handle to the window rather than a handle to the menu.
You can obtain the handle to a popup menu using
hMenuPopup = GetSubMenu (hMenu, iPosition) ;
where iPosition is the index (starting at 0) of the popup within the top-level menu indicated byhMenu. You can then use the popup menu handle with other functions (such asAppendMenu).
You can obtain the current number of items in a top-level or popup menu by using
iCount = GetMenuItemCount (hMenu) ;
You can obtain the menu ID for an item in a popup menu from
id = GetMenuItemID (hMenuPopup, iPosition) ;
where iPosition 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, id, iCheck) ;
In MENUDEMO, hMenu was the handle to the top-level menu, id was the menu ID, and the value of iCheck was either MF_CHECKED or MF_UNCHECKED. If hMenu is a handle to a popup menu, the id parameter can be a positional index rather than a menu ID. If an index is more convenient, you include MF_BYPOSITION in the third argument:
CheckMenuItem (hMenu, iPosition, MF_CHECKED ¦ MF_BYPOSITION) ;
The EnableMenuItem function works similarly to CheckMenuItem, except that the third argument 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 ofEnableMenuItem in the POPPAD2 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
iCharCount = GetMenuString (hMenu, id, pString, iMaxCount, iFlag) ;
The iFlag is either MF_BYCOMMAND (where id is a menu ID) or MF_BYPOSITION (where id is a positional index). The function copies up to iMaxCount characters intopString and returns the number of characters copied.
Or perhaps you'd like to know what the current flags of a menu item are:
iFlags = GetMenuState (hMenu, id, iFlag) ;
Again, iFlag is either MF_BYCOMMAND or MF_BYPOSITION. The iFlags parameter is a combination of all the current flags. You can determine the current flags 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 function 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? Such a menu might remind old-timers of that character-mode classic, Lotus 1-2-3. The NOPOPUPS program, shown in Figure 10-10, demonstrates how to do it. This program includes File and Edit items similar to those that MENUDEMO uses but displays them as alternate top-level menus.
Figure 10-10. The NOPOPUPS program.
In Microsoft Developer Studio, you create three menus rather than one. You'll be selecting Resource from the Insert menu three times. Each menu has a different text name. When the window procedure processes the WM_CREATE message, Windows loads each menu resource into memory:
hMenuMain = LoadMenu (hInstance, TEXT ("MenuMain")) ; hMenuFile = LoadMenu (hInstance, TEXT ("MenuFile")) ; hMenuEdit = LoadMenu (hInstance, TEXT ("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 ;
During the WM_DESTROY message, NOPOPUPS sets the program's menu to the Main menu and destroys the File and Edit menus with calls to DestroyMenu. The Main menu is destroyed automatically when the window is destroyed.