Win32 Series - Menus

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.

 

 MenusControls
LOWORD (wParam):Menu IDControl ID
HIWORD (wParam):0Notification code
lParam:0Child 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.

 

MENUDEMO.C

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

#include <windows.h>
#include "resource.h"

#define ID_TIMER 1

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName[] = TEXT ("MenuDemo") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;
     
     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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = szAppName ;
     wndclass.lpszClassName = szAppName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Menu Demonstration"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static int idColor [5] = { WHITE_BRUSH,  LTGRAY_BRUSH, GRAY_BRUSH,
                                DKGRAY_BRUSH, BLACK_BRUSH } ;
     static int iSelection = IDM_BKGND_WHITE ;
     HMENU      hMenu ;
     
     switch (message)
     {
     case WM_COMMAND:
          hMenu = GetMenu (hwnd) ;
          
          switch (LOWORD (wParam))
          {
          case IDM_FILE_NEW:
          case IDM_FILE_OPEN:
          case IDM_FILE_SAVE:
          case IDM_FILE_SAVE_AS:
               MessageBeep (0) ;
               return 0 ;

          case IDM_APP_EXIT:
               SendMessage (hwnd, WM_CLOSE, 0, 0) ;
               return 0 ;
               
          case IDM_EDIT_UNDO:
          case IDM_EDIT_CUT:
          case IDM_EDIT_COPY:
          case IDM_EDIT_PASTE:
          case IDM_EDIT_CLEAR:
               MessageBeep (0) ;
               return 0 ;
               
          case IDM_BKGND_WHITE:         // Note: Logic below
          case IDM_BKGND_LTGRAY:        //   assumes that IDM_WHITE
          case IDM_BKGND_GRAY:          //   through IDM_BLACK are
          case IDM_BKGND_DKGRAY:        //   consecutive numbers in
          case IDM_BKGND_BLACK:         //   the order shown here.
               
               CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ;
               iSelection = LOWORD (wParam) ;
               CheckMenuItem (hMenu, iSelection, MF_CHECKED) ;
               
               SetClassLong (hwnd, GCL_HBRBACKGROUND, (LONG) 
                    GetStockObject 
                             (idColor [LOWORD (wParam) - IDM_BKGND_WHITE])) ;
               
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;
               
          case IDM_TIMER_START:
               if (SetTimer (hwnd, ID_TIMER, 1000, NULL))
               {
                    EnableMenuItem (hMenu, IDM_TIMER_START, MF_GRAYED) ;
                    EnableMenuItem (hMenu, IDM_TIMER_STOP,  MF_ENABLED) ;
               }
               return 0 ;
               
          case IDM_TIMER_STOP:
               KillTimer (hwnd, ID_TIMER) ;
               EnableMenuItem (hMenu, IDM_TIMER_START, MF_ENABLED) ;
               EnableMenuItem (hMenu, IDM_TIMER_STOP,  MF_GRAYED) ;
               return 0 ;

          case IDM_APP_HELP:
               MessageBox (hwnd, TEXT ("Help not yet implemented!"),
                           szAppName, MB_ICONEXCLAMATION | MB_OK) ;
               return 0 ;
               
          case IDM_APP_ABOUT:
               MessageBox (hwnd, TEXT ("Menu Demonstration Program\n")
                                 TEXT ("(c) Charles Petzold, 1998"),
                           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 (excerpts)

//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"

/
// Menu

MENUDEMO MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&New",                        IDM_FILE_NEW
        MENUITEM "&Open",                       IDM_FILE_OPEN
        MENUITEM "&Save",                       IDM_FILE_SAVE
        MENUITEM "Save &As...",                 IDM_FILE_SAVE_AS
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       IDM_APP_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo",                       IDM_EDIT_UNDO
        MENUITEM SEPARATOR
        MENUITEM "C&ut",                        IDM_EDIT_CUT
        MENUITEM "&Copy",                       IDM_EDIT_COPY
        MENUITEM "&Paste",                      IDM_EDIT_PASTE
        MENUITEM "De&lete",                     IDM_EDIT_CLEAR
    END
    POPUP "&Background"
    BEGIN
        MENUITEM "&White",                      IDM_BKGND_WHITE, CHECKED
        MENUITEM "&Light Gray",                 IDM_BKGND_LTGRAY
        MENUITEM "&Gray",                       IDM_BKGND_GRAY
        MENUITEM "&Dark Gray",                  IDM_BKGND_DKGRAY
        MENUITEM "&Black",                      IDM_BKGND_BLACK
    END
    POPUP "&Timer"
    BEGIN
        MENUITEM "&Start",                      IDM_TIMER_START
        MENUITEM "S&top",                       IDM_TIMER_STOP, GRAYED
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&Help...",                    IDM_APP_HELP
        MENUITEM "&About MenuDemo...",          IDM_APP_ABOUT
    END
END

 

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by MenuDemo.rc

#define IDM_FILE_NEW                    40001
#define IDM_FILE_OPEN                   40002
#define IDM_FILE_SAVE                   40003
#define IDM_FILE_SAVE_AS                40004
#define IDM_APP_EXIT                    40005
#define IDM_EDIT_UNDO                   40006
#define IDM_EDIT_CUT                    40007
#define IDM_EDIT_COPY                   40008
#define IDM_EDIT_PASTE                  40009
#define IDM_EDIT_CLEAR                  40010
#define IDM_BKGND_WHITE                 40011
#define IDM_BKGND_LTGRAY                40012
#define IDM_BKGND_GRAY                  40013
#define IDM_BKGND_DKGRAY                40014
#define IDM_BKGND_BLACK                 40015
#define IDM_TIMER_START                 40016
#define IDM_TIMER_STOP                  40017
#define IDM_APP_HELP                    40018
#define IDM_APP_ABOUT                   40019

 

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.

 

POPMENU.C

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

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

HINSTANCE hInst ;
TCHAR     szAppName[] = TEXT ("PopMenu") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;
     
     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, szAppName) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hInst = hInstance ;
     
     hwnd = CreateWindow (szAppName, TEXT ("Popup Menu Demonstration"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

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

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HMENU hMenu ;
     static int   idColor [5] = { WHITE_BRUSH,  LTGRAY_BRUSH, GRAY_BRUSH,
                                  DKGRAY_BRUSH, BLACK_BRUSH } ;
     static int   iSelection = IDM_BKGND_WHITE ;
     POINT        point ;
     
     switch (message)
     {
     case WM_CREATE:
          hMenu = LoadMenu (hInst, szAppName) ;
          hMenu = GetSubMenu (hMenu, 0) ;
          return 0 ;

     case WM_RBUTTONUP:
          point.x = LOWORD (lParam) ;
          point.y = HIWORD (lParam) ;
          ClientToScreen (hwnd, &point) ;
          
          TrackPopupMenu (hMenu, TPM_RIGHTBUTTON, point.x, point.y, 
                          0, hwnd, NULL) ;
          return 0 ;
          
     case WM_COMMAND:
          switch (LOWORD (wParam))
          {
          case IDM_FILE_NEW:
          case IDM_FILE_OPEN:
          case IDM_FILE_SAVE:
          case IDM_FILE_SAVE_AS:
          case IDM_EDIT_UNDO:
          case IDM_EDIT_CUT:
          case IDM_EDIT_COPY:
          case IDM_EDIT_PASTE:
          case IDM_EDIT_CLEAR:
               MessageBeep (0) ;
               return 0 ;
               
          case IDM_BKGND_WHITE:         // Note: Logic below
          case IDM_BKGND_LTGRAY:        //   assumes that IDM_WHITE
          case IDM_BKGND_GRAY:          //   through IDM_BLACK are
          case IDM_BKGND_DKGRAY:        //   consecutive numbers in
          case IDM_BKGND_BLACK:         //   the order shown here.
               
               CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ;
               iSelection = LOWORD (wParam) ;
               CheckMenuItem (hMenu, iSelection, MF_CHECKED) ;
               
               SetClassLong (hwnd, GCL_HBRBACKGROUND, (LONG) 
                    GetStockObject 
                         (idColor [LOWORD (wParam) - IDM_BKGND_WHITE])) ;
               
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;
               
          case IDM_APP_ABOUT:
               MessageBox (hwnd, TEXT ("Popup Menu Demonstration Program\n")
                                 TEXT ("(c) Charles Petzold, 1998"),
                           szAppName, MB_ICONINFORMATION | MB_OK) ;
               return 0 ;
               
          case IDM_APP_EXIT:
               SendMessage (hwnd, WM_CLOSE, 0, 0) ;
               return 0 ;
               
          case IDM_APP_HELP:
               MessageBox (hwnd, TEXT ("Help not yet implemented!"),
                           szAppName, MB_ICONEXCLAMATION | MB_OK) ;
               return 0 ;
          }
          break ;
          
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

POPMENU.RC (excerpts)

//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"

/
// Menu

POPMENU MENU DISCARDABLE 
BEGIN
    POPUP "MyMenu"
    BEGIN
        POPUP "&File"
        BEGIN
            MENUITEM "&New",                        IDM_FILE_NEW
            MENUITEM "&Open",                       IDM_FILE_OPEN
            MENUITEM "&Save",                       IDM_FILE_SAVE
            MENUITEM "Save &As",                    IDM_FILE_SAVE_AS
            MENUITEM SEPARATOR
            MENUITEM "E&xit",                       IDM_APP_EXIT
        END
        POPUP "&Edit"
        BEGIN
            MENUITEM "&Undo",                       IDM_EDIT_UNDO
            MENUITEM SEPARATOR
            MENUITEM "Cu&t",                        IDM_EDIT_CUT
            MENUITEM "&Copy",                       IDM_EDIT_COPY
            MENUITEM "&Paste",                      IDM_EDIT_PASTE
            MENUITEM "De&lete",                     IDM_EDIT_CLEAR
        END
        POPUP "&Background"
        BEGIN
            MENUITEM "&White",                      IDM_BKGND_WHITE, CHECKED
            MENUITEM "&Light Gray",                 IDM_BKGND_LTGRAY
            MENUITEM "&Gray",                       IDM_BKGND_GRAY
            MENUITEM "&Dark Gray",                  IDM_BKGND_DKGRAY
            MENUITEM "&Black",                      IDM_BKGND_BLACK
        END
        POPUP "&Help"
        BEGIN
            MENUITEM "&Help...",                    IDM_APP_HELP
            MENUITEM "&About PopMenu...",           IDM_APP_ABOUT
        END
    END
END

 

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by PopMenu.rc

#define IDM_FILE_NEW                    40001
#define IDM_FILE_OPEN                   40002
#define IDM_FILE_SAVE                   40003
#define IDM_FILE_SAVE_AS                40004
#define IDM_APP_EXIT                    40005
#define IDM_EDIT_UNDO                   40006
#define IDM_EDIT_CUT                    40007
#define IDM_EDIT_COPY                   40008
#define IDM_EDIT_PASTE                  40009
#define IDM_EDIT_CLEAR                  40010
#define IDM_BKGND_WHITE                 40011
#define IDM_BKGND_LTGRAY                40012
#define IDM_BKGND_GRAY                  40013
#define IDM_BKGND_DKGRAY                40014
#define IDM_BKGND_BLACK                 40015
#define IDM_APP_HELP                    40016
#define IDM_APP_ABOUT                   40017

 

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.

 

POORMENU.C

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

#include <windows.h>

#define IDM_SYS_ABOUT   1
#define IDM_SYS_HELP    2
#define IDM_SYS_REMOVE  3

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

static TCHAR szAppName[] = TEXT ("PoorMenu") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     HMENU    hMenu ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;
     
     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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("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_SYS_ABOUT,  TEXT ("About...")) ;
     AppendMenu (hMenu, MF_STRING, IDM_SYS_HELP,   TEXT ("Help...")) ;
     AppendMenu (hMenu, MF_STRING, IDM_SYS_REMOVE, TEXT ("Remove Additions")) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
     {
     case WM_SYSCOMMAND:
          switch (LOWORD (wParam))
          {
          case IDM_SYS_ABOUT:
               MessageBox (hwnd, TEXT ("A Poor-Person's Menu Program\n")
                                 TEXT ("(c) Charles Petzold, 1998"),
                           szAppName, MB_OK | MB_ICONINFORMATION) ;
               return 0 ;
               
          case IDM_SYS_HELP:
               MessageBox (hwnd, TEXT ("Help not yet implemented!"),
                           szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               return 0 ;
               
          case IDM_SYS_REMOVE:
               GetSystemMenu (hwnd, TRUE) ;
               return 0 ;
          }
          break ;
          
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

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.

 

NOPOPUPS.C

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

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)

{
     static TCHAR szAppName[] = TEXT ("NoPopUps") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;
     
     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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, 
                          TEXT ("No-Popup Nested Menu Demonstration"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HMENU hMenuMain, hMenuEdit, hMenuFile ;
     HINSTANCE    hInstance ;

     switch (message)
     {
     case WM_CREATE:
          hInstance = (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE) ;
          
          hMenuMain = LoadMenu (hInstance, TEXT ("MenuMain")) ;
          hMenuFile = LoadMenu (hInstance, TEXT ("MenuFile")) ;
          hMenuEdit = LoadMenu (hInstance, TEXT ("MenuEdit")) ;
          
          SetMenu (hwnd, hMenuMain) ;
          return 0 ;
          
     case WM_COMMAND:
          switch (LOWORD (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_FILE_NEW:
          case IDM_FILE_OPEN:
          case IDM_FILE_SAVE:
          case IDM_FILE_SAVE_AS:
          case IDM_EDIT_UNDO:
          case IDM_EDIT_CUT:
          case IDM_EDIT_COPY:
          case IDM_EDIT_PASTE:
          case IDM_EDIT_CLEAR:
               MessageBeep (0) ;
               return 0 ;
          }
          break ;
          
     case WM_DESTROY:
          SetMenu (hwnd, hMenuMain) ;
          DestroyMenu (hMenuFile) ;
          DestroyMenu (hMenuEdit) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

NOPOPUPS.RC (excerpts)

//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"
/
// Menu

MENUMAIN MENU DISCARDABLE 
BEGIN
    MENUITEM "MAIN:",                       0, INACTIVE
    MENUITEM "&File...",                    IDM_FILE
    MENUITEM "&Edit...",                    IDM_EDIT
END

MENUFILE MENU DISCARDABLE 
BEGIN
    MENUITEM "FILE:",                       0, INACTIVE
    MENUITEM "&New",                        IDM_FILE_NEW
    MENUITEM "&Open...",                    IDM_FILE_OPEN
    MENUITEM "&Save",                       IDM_FILE_SAVE
    MENUITEM "Save &As",                    IDM_FILE_SAVE_AS
    MENUITEM "(&Main)",                     IDM_MAIN
END

MENUEDIT MENU DISCARDABLE 
BEGIN
    MENUITEM "EDIT:",                       0, INACTIVE
    MENUITEM "&Undo",                       IDM_EDIT_UNDO
    MENUITEM "Cu&t",                        IDM_EDIT_CUT
    MENUITEM "&Copy",                       IDM_EDIT_COPY
    MENUITEM "&Paste",                      IDM_EDIT_PASTE
    MENUITEM "De&lete",                     IDM_EDIT_CLEAR
    MENUITEM "(&Main)",                     IDM_MAIN
END

 

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by NoPopups.rc
#define IDM_FILE                        40001
#define IDM_EDIT                        40002
#define IDM_FILE_NEW                    40003
#define IDM_FILE_OPEN                   40004
#define IDM_FILE_SAVE                   40005
#define IDM_FILE_SAVE_AS                40006
#define IDM_MAIN                        40007
#define IDM_EDIT_UNDO                   40008
#define IDM_EDIT_CUT                    40009
#define IDM_EDIT_COPY                   40010
#define IDM_EDIT_PASTE                  40011
#define IDM_EDIT_CLEAR                  40012

 

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.

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值