A computer application without a menu bar is like a restaurant that sends you to the kitchen to find your own meal. Fortunately, the days of hidden keyboard commands and tricky control codes are gone. Your application's users expect your program to display its commands proudly in plain view. Moreover, your users expect to find familiar commands in familiar places.
This consistency between applications is one thing that makes Windows applications easier to use. When users load a new application, they may not know exactly how to use it, but they at least know where to find the Open command (the File menu) or where to find the Cut, Copy, and Paste commands (the Edit menu). You can be sure that if users don't find these commands and others like them in the places they expect the commands to be, they may stop using your application.
However, handling menus with MFC is a lot different from handling them with a conventional Windows programming language, such as C, because MFC incorporates message maps to link menu commands with the functions that handle those commands. In addition, MFC's CCmdUI class makes it easier than falling on ice to keep your menu commands enabled or disabled, check marked, or bulleted. In this chapter, you get an in-depth look at how the MFC menus work.
Although creating and manipulating MFC menus takes quite a bit of program code, the process is fairly simple. You need only to create the menu with the Visual C++ built-in menu editor, add message response functions to your window class, and add the appropriate entries to your message map. Then, after you load the menu in your program, the menu takes care of itself, and the appropriate message response functions are called whenever the user selects a menu item. The steps for adding menu support to your MFC application are as follows:
In the rest of this chapter, you'll see, not only how to perform the preceding steps, but also how to assign command IDs to your menus and how to ensure that those menu IDs are accessible to the rest of your program. In the next section, you learn to use the Visual C++ menu editor to create your menu resource.
As you now know, the first step toward adding a menu to your MFC application is creating the menu resource, which acts as a sort of template for Windows. When Windows sees the menu resource in your program, it uses the commands in the resource to construct the menu bar for you. To create a menu resource, just perform the following steps:
When you create menu captions, you can specify a hotkey by placing an ampersand (&) immediately before the letter in the caption that the user can press to select that command. Windows automatically underlines the hotkey when it displays the menu caption.
So that Windows can tell the application which menu command the user has selected, you must define IDs for all commands in your menus. The ID is a numerical value represented by a constant. However, you don't have to worry about an ID's actual value because when you add a new command-ID constant to your menu resource, the menu editor automatically gives it the next consecutive ID value.
You can choose any constant names you want for IDs. However, most MFC programmers follow a simple convention: They start the ID with the prefix ID, followed by the menu's title and command separated by an underscore. So, a command ID for an Options menu's Color command would be ID_OPTIONS_COLOR.
In Figure 6.4, you can see an application's Exit command being defined. In this case, the command ID is previously defined by Visual C++, which defines an entire set of common command IDs for your application's File, Edit, View, Window, and Help menus. Table 6.1 lists the most commonly used of these command IDs along with their hexadecimal values.
Table 6.1 Predefined Command IDs and Their Values
Command ID | Value |
ID_APP_ABOUT | 0xE140 |
ID_APP_EXIT | 0xE141 |
ID_EDIT_CLEAR | 0xE120 |
ID_EDIT_CLEAR_ALL | 0xE121 |
ID_EDIT_COPY | 0xE122 |
ID_EDIT_CUT | 0xE123 |
ID_EDIT_FIND | 0xE124 |
ID_EDIT_PASTE | 0xE125 |
ID_EDIT_PASTE_LINK | 0xE126 |
ID_EDIT_PASTE_SPECIAL | 0xE127 |
ID_EDIT_REDO | 0xE12C |
ID_EDIT_REPEAT | 0xE128 |
ID_EDIT_REPLACE | 0xE129 |
ID_EDIT_SELECT_ALL | 0xE12A |
ID_EDIT_UNDO | 0xE12B |
ID_FILE_CLOSE | 0xE102 |
ID_FILE_NEW | 0xE100 |
ID_FILE_OPEN | 0xE101 |
ID_FILE_PAGE_SETUP | 0xE105 |
ID_FILE_PRINT | 0xE107 |
ID_FILE_PRINT_PREVIEW | 0xE109 |
ID_FILE_PRINT_SETUP | 0xE106 |
ID_FILE_SAVE | 0xE103 |
ID_FILE_SAVE_AS | 0xE104 |
ID_WINDOW_ARRANGE | 0xE131 |
ID_WINDOW_CASCADE | 0xE132 |
ID_WINDOW_NEW | 0xE130 |
ID_WINDOW_TILE_HORZ | 0xE133 |
ID_WINDOW_TILE_VERT | 0xE134 |
When defining menu items that will use previously defined IDs, do not type the ID into the Menu Item property sheet. If you just type the menu commandfor example, E&xitDeveloper Studio will attempt to match the command with a predefined ID. If you try to add the ID manually, you will get a compiler warning because the ID will be defined both in your RESOURCE.H file and in the predefined IDs.
After creating your menu resource (or any other type of resource for that matter), Developer Studio creates at least two files that you need to add to your application. The first file is called RESOURCE.H. This file contains all of the resource IDs (in this case, menu IDs) that you've defined. You must include RESOURCE.H in any file that refers to the constants you've defined. Otherwise, the compiler will have no idea what the constants represent and will complain with a string of error messages. Listing 6.1 shows the RESOURCE.H file that was created by Developer Studio for the Menu application you'll examine later in this chapter.
Listing 6.1 RESOURCE.HThe RESOURCE.H File Holds All the Constants You Defined in Your Resource File
//{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by menu.rc // #define IDR_MENU1 101 #define IDM_CHECKS_OPTION1 40002 #define IDM_CHECKS_OPTION2 40003 #define IDM_CHECKS_OPTION3 40004 #define IDM_ENABLE_ENABLE 40005 #define IDM_ENABLE_OPTION1 40006 #define IDM_ENABLE_OPTION2 40007 #define IDM_BULLETS_OPTION1 40008 #define IDM_BULLETS_OPTION2 40009 #define IDM_BULLETS_OPTION3 40010 #define IDM_SWITCH_ONOFF 40011 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 104 #define _APS_NEXT_COMMAND_VALUE 40012 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
The first block of constants in Listing 6.1 are the constants that were defined when creating the application's menus. The second block of constants are used internally by Developer Studio to help manage the other symbols. For example, you can see that the last constant defined in the first block has a value of 40011, which means the next available value is 40012. The constant APS_NEXT_COMMAND_VALUE is not so coincidentally given the value 40012.
The second file that Developer Studio creates will have an .RC extension. This is the resource script that defines all your applications resources, including not just menus, but also dialog boxes, string tables, icons, cursors, and more. The resource script is a lot like a source code file, except it's written in a language that the resource compiler understands. The resource compiler takes the .RC file and compiles it into a .RES file, which is the binary representation of your application's resources. Listing 6.2 shows the MENU.RC file, which is the resource script for the menus you'll experiment with when you examine the Menu application later in this chapter, in the section titled "Exploring the Menu Application."
Listing 6.2 MENU.RCThe Resource Script of the Menu Application
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Menu // IDR_MENU1 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_APP_EXIT END POPUP "&Enable" BEGIN MENUITEM "&Enable", IDM_ENABLE_ENABLE MENUITEM SEPARATOR MENUITEM "Option&1", IDM_ENABLE_OPTION1 MENUITEM "Option&2", IDM_ENABLE_OPTION2 END POPUP "&Checks" BEGIN MENUITEM "Option&1", IDM_CHECKS_OPTION1 MENUITEM "Option&2", IDM_CHECKS_OPTION2 MENUITEM "Option&3", IDM_CHECKS_OPTION3 END POPUP "&Bullets" BEGIN MENUITEM "Option&1", IDM_BULLETS_OPTION1 MENUITEM "Option&2", IDM_BULLETS_OPTION2 MENUITEM "Option&3", IDM_BULLETS_OPTION3 END POPUP "&Switch" BEGIN MENUITEM "&On", IDM_SWITCH_ONOFF END END #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
The specific area of interest in Listing 6.2 is the section of resource script following the Menu comment block. There you can see the actual commands that the resource compiler uses to create the binary version of the application's menus. Many programmers still write their resource scripts by hand, and most like to tinker with the script produced by programs, such as Developer Studio. If you're interested in learning more about resource scripts (always a good idea), consult your favorite Windows programming book.
When you've had a chance to explore the Menu application that's presented later in this chapter (in the section titled "Exploring the Menu Application"), you might want to compare the resource script to the application's actual menus. You'll immediately see how the script's structure is converted into the menu hierarchy that you found in the application's menu bar.
Whether you want to experiment with the .RC file or just use it as is (the best choice unless you understand resource scripts thoroughly), you need to make it part
of your project before Developer Studio can compile it and add it to your application's executable file. To add the resource script to your project, choose I
nsert, Files Into Project from Developer Studio's menu bar. When you do, the Insert Files Into Project dialog box appears. Find the .RC file, and
double-click it. Developer Studio adds the file to your project. The next time you compile your project, Developer Studio will automatically invoke the resource
compiler to compile the script into a binary .RES file.
On this book's CD-ROM (or, if you installed the CD-ROM, on your hard disk) in the CHAP06 folder, you'll find the Menu sample program. To run the program, double-click the MENU.EXE file. When you do, you see the window shown in Figure 6.5. Besides the silly message in the window, this application sports a full set of menus that do all kinds of fancy tricks.
The menu application boasts a full menu bar.
First, turn your attention to the Enable menu. Click the menu's title, and you'll see that the menu has a command named Enable as well as two
disabled menu commands called Option1 and Option2 (see Figure 6.6). Currently, Option1 and Option2 are disabled, meaning
that they are grayed out and you can't select them.
At first, two commands on the Enable menu are disabled.
To enable these two commands, you must first select the Enable command. When you do, a check mark appears next to the Enable command,
and the Option1 and Option2 commands are enabled, as shown in Figure 6.7. If you click one of the newly enabled commands, a message box
appears confirming that the command was received by the application.
The Enable command enables the remaining commands in the menu.
Next, click the Checks menu to display its commands, which are named Option1, Option2, and Option3. (Man, I'm really
creative with those command names, eh?) Figure 6.8 shows what the menu looks like. When you click a command in the menu, a check mark appears next to the
command. You can click any or all of the commands in the Check menu (see Figure 6.9).
The Checks menu commands start off with no check marks.
You can check mark any of the commands on the Checks menu by clicking the command.
The Bullets menu hides a few cool tricks, too. When you look at the menu, the first command (yes, it's called Option1) is marked with a bullet,
which indicates that that option is currently selected. As you'll soon discover, only one of the three options can be selected at a time. Whichever option you click
inherits the bullet (see Figure 6.10).
The options in the Bullets menu work like radio buttons.
Finally, the Switch menu demonstrates how a program can change the command captions in a menu. When you first open the Switch menu, its
single command is captioned On. When you click the command, it changes to Off (see Figure 6.11). Click again, and it's back to On.
The Switch menu's single command toggles between On and Off.
Now that you've had a chance to use the Menu application, you're probably dying to see how it all works. Listings 6.3 and 6.4 are the source code for the Menu application's CMainFrame class. (There's nothing new or interesting about Menu's application class, so it's not shown here.) Listing 6.3 is the header file where the class is declared, whereas Listing 6.4 is the class's implementation file where its many functions are defined.
Listing 6.3 MAINFRM.HThe Header File of the Frame Window Class
/////////////////////////////////////////////////////////// // MAINFRM.H: Header file for the CMainFrame class, which // represents the application's main window. /////////////////////////////////////////////////////////// enum {Option1, Option2, Option3}; class CMainFrame : public CFrameWnd { // Protected data members. protected: BOOL m_enabled; BOOL m_checkOption1; BOOL m_checkOption2; BOOL m_checkOption3; UINT m_bulletOption; BOOL m_switch; // Constructor and destructor. public: CMainFrame(); ~CMainFrame(); // Overrides. protected: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // Message map functions. protected: // System message handlers. afx_msg void OnPaint(); // Menu command message handlers. afx_msg void OnEnableEnable(); afx_msg void OnEnableOption1(); afx_msg void OnEnableOption2(); afx_msg void OnChecksOption1(); afx_msg void OnChecksOption2(); afx_msg void OnChecksOption3(); afx_msg void OnBullets(UINT nID); afx_msg void OnSwitchOnOff(); // Update command UI handlers. afx_msg void OnUpdateEnableEnableUI(CCmdUI* pCmdUI); afx_msg void OnUpdateEnableOption1UI(CCmdUI* pCmdUI); afx_msg void OnUpdateEnableOption2UI(CCmdUI* pCmdUI); afx_msg void OnUpdateChecksOption1UI(CCmdUI* pCmdUI); afx_msg void OnUpdateChecksOption2UI(CCmdUI* pCmdUI); afx_msg void OnUpdateChecksOption3UI(CCmdUI* pCmdUI); afx_msg void OnUpdateBulletsUI(CCmdUI* pCmdUI); afx_msg void OnUpdateSwitchOnOffUI(CCmdUI* pCmdUI); // Protected member functions. protected: void ShowMessage(CPaintDC* paintDC); DECLARE_MESSAGE_MAP() };
Listing 6.4 MAINFRM.CPPThe Implementation File of the Frame Window Class
/////////////////////////////////////////////////////////// // MAINFRM.CPP: Implementation file for the CMainFrame // class, which represents the application's // main window. /////////////////////////////////////////////////////////// #include <afxwin.h> #include "mainfrm.h" #include "resource.h" BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) // Message map entries for system messages. ON_WM_PAINT() // Message map entries for menu commands. ON_COMMAND(IDM_ENABLE_ENABLE, OnEnableEnable) ON_COMMAND(IDM_ENABLE_OPTION1, OnEnableOption1) ON_COMMAND(IDM_ENABLE_OPTION2, OnEnableOption2) ON_COMMAND(IDM_CHECKS_OPTION1, OnChecksOption1) ON_COMMAND(IDM_CHECKS_OPTION2, OnChecksOption2) ON_COMMAND(IDM_CHECKS_OPTION3, OnChecksOption3) ON_COMMAND(IDM_SWITCH_ONOFF, OnSwitchOnOff) ON_COMMAND_RANGE(IDM_BULLETS_OPTION1, IDM_BULLETS_OPTION3, OnBullets) // Message map entries for update command UI handlers. ON_UPDATE_COMMAND_UI(IDM_ENABLE_ENABLE, OnUpdateEnableEnableUI) ON_UPDATE_COMMAND_UI(IDM_ENABLE_OPTION1, OnUpdateEnableOption1UI) ON_UPDATE_COMMAND_UI(IDM_ENABLE_OPTION2, OnUpdateEnableOption2UI) ON_UPDATE_COMMAND_UI(IDM_CHECKS_OPTION1, OnUpdateChecksOption1UI) ON_UPDATE_COMMAND_UI(IDM_CHECKS_OPTION2, OnUpdateChecksOption2UI) ON_UPDATE_COMMAND_UI(IDM_CHECKS_OPTION3, OnUpdateChecksOption3UI) ON_UPDATE_COMMAND_UI(IDM_SWITCH_ONOFF, OnUpdateSwitchOnOffUI) ON_UPDATE_COMMAND_UI_RANGE(IDM_BULLETS_OPTION1, IDM_BULLETS_OPTION3, OnUpdateBulletsUI) END_MESSAGE_MAP() /////////////////////////////////////////////////////////// // CMainFrame: Construction and destruction. /////////////////////////////////////////////////////////// CMainFrame::CMainFrame() { // Create the main frame window. Create(NULL, "Menu App", WS_OVERLAPPEDWINDOW, rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU1)); // Initialize class data members. m_enabled = FALSE; m_checkOption1 = FALSE; m_checkOption2 = FALSE; m_checkOption3 = FALSE; m_bulletOption = Option1; m_switch = FALSE; } CMainFrame::~CMainFrame() { } /////////////////////////////////////////////////////////// // Overrides. /////////////////////////////////////////////////////////// BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { // Set size of the main window. cs.cx = 380; cs.cy = 140; // Call the base class's version. BOOL returnCode = CFrameWnd::PreCreateWindow(cs); return returnCode; } /////////////////////////////////////////////////////////// // Message map functions. /////////////////////////////////////////////////////////// void CMainFrame::OnPaint() { CPaintDC* paintDC = new CPaintDC(this); ShowMessage(paintDC); delete paintDC; } void CMainFrame::OnEnableEnable() { m_enabled = !m_enabled; } void CMainFrame::OnEnableOption1() { MessageBox("Enable/Option1"); } void CMainFrame::OnEnableOption2() { MessageBox("Enable/Option2"); } void CMainFrame::OnChecksOption1() { m_checkOption1 = !m_checkOption1; } void CMainFrame::OnChecksOption2() { m_checkOption2 = !m_checkOption2; } void CMainFrame::OnChecksOption3() { m_checkOption3 = !m_checkOption3; } void CMainFrame::OnBullets(UINT nID) { if (nID == IDM_BULLETS_OPTION1) m_bulletOption = Option1; else if (nID == IDM_BULLETS_OPTION2) m_bulletOption = Option2; else m_bulletOption = Option3; } void CMainFrame::OnSwitchOnOff() { m_switch = !m_switch; } /////////////////////////////////////////////////////////// // Update Command UI Handlers /////////////////////////////////////////////////////////// void CMainFrame::OnUpdateEnableEnableUI(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_enabled); } void CMainFrame::OnUpdateEnableOption1UI(CCmdUI* pCmdUI) { pCmdUI->Enable(m_enabled); } void CMainFrame::OnUpdateEnableOption2UI(CCmdUI* pCmdUI) { pCmdUI->Enable(m_enabled); } void CMainFrame::OnUpdateChecksOption1UI(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_checkOption1); } void CMainFrame::OnUpdateChecksOption2UI(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_checkOption2); } void CMainFrame::OnUpdateChecksOption3UI(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_checkOption3); } void CMainFrame::OnUpdateBulletsUI(CCmdUI* pCmdUI) { if (pCmdUI->m_nID == IDM_BULLETS_OPTION1) pCmdUI->SetRadio(m_bulletOption == Option1); else if (pCmdUI->m_nID == IDM_BULLETS_OPTION2) pCmdUI->SetRadio(m_bulletOption == Option2); else pCmdUI->SetRadio(m_bulletOption == Option3); } void CMainFrame::OnUpdateSwitchOnOffUI(CCmdUI* pCmdUI) { if (m_switch) pCmdUI->SetText("&Off"); else pCmdUI->SetText("&On"); } /////////////////////////////////////////////////////////// // Protected member functions. /////////////////////////////////////////////////////////// void CMainFrame::ShowMessage(CPaintDC* paintDC) { // Initialize a LOGFONT structure for the fonts. LOGFONT logFont; logFont.lfHeight = 48; logFont.lfWidth = 0; logFont.lfEscapement = 0; logFont.lfOrientation = 0; logFont.lfWeight = FW_NORMAL; logFont.lfItalic = 0; logFont.lfUnderline = 0; logFont.lfStrikeOut = 0; logFont.lfCharSet = ANSI_CHARSET; logFont.lfOutPrecision = OUT_DEFAULT_PRECIS; logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; logFont.lfQuality = PROOF_QUALITY; logFont.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN; strcpy(logFont.lfFaceName, "Times New Roman"); // Create a new font and select it into the DC. CFont font; font.CreateFontIndirect(&logFont); CFont* oldFont = paintDC->SelectObject(&font); // Print text with the new font. paintDC->TextOut(20, 20, "Try out the menus!"); // Restore the old font to the DC. paintDC->SelectObject(oldFont); }
If you have very simple menus in your applicationsmenus that require no special handling, such as check marking or disablingyou probably don't need menu state variables in your program. However, most full-fledged Windows applications have at least a few menu commands that require special handling. The most common example is a menu command that must be enabled or disabled depending upon the application's current status. For example, the Edit, Paste command should not be enabled unless there's something in the Clipboard to paste. Another example might be a menu of options that can be turned on or off. If an option is on, it should be check marked or bulleted.
How you use menu state variables in your application depends, of course, on how you need to manage the application's menu commands. Usually, you'll use Boolean values to determine the state of a menu command. A set of options, for example, will have an equivalent set of Boolean variables: one for each option. If the option is on, the equivalent variable is set to TRUE; if the option is off, the variable is set to FALSE. Having menu state variables, not only helps your program know how to respond to the user's commands, but also enables your application to easily maintain the menu commands' appearances, as you'll see later in this chapter, in the section titled "Writing Update-Command-UI Functions."
If you look at the declaration of the CMainFrame class, you'll see the data member declarations shown in Listing 6.5.
Listing 6.5 LST06_05.CPPThe Menu State Variables of the CMainFrame Class
BOOL m_enabled; BOOL m_checkOption1; BOOL m_checkOption2; BOOL m_checkOption3; UINT m_bulletOption; BOOL m_switch;
Here, m_enabled tracks the state of the Enable, Enable command, whereas m_checkOption1, m_checkOption2, and m_checkOption3 track
the commands on the Checks menu. When any of these variables are TRUE, the associated command is on. The m_bulletOption variable holds the
Bullets menu's mode, which is the option in the menu that's currently active. This works a little differently because, unlike the check marks in the C
hecks menu, only one option at a time can be active in Bullets menu. The different modes are defined in the enumeration near the top of the MAINFRM.H
file, like this:
enum {Option1, Option2, Option3};
Finally, the m_switch variable holds the state of the Switch menu's single command, which is either the text "On" or "Off," depending on the command's
state. You'll see how these variables work in conjunction with the menus and the program code later in this chapter, in the section titled "Writing
Update-Command-UI Functions."
As you know, MFC uses message maps to associate message response functions with messages Windows sends to the application. Some of the messages are system messages, such as WM_PAINT, which are sent automatically by Windows. Other messages that an application must respond to are the messages triggered when the user selects menu commands. You'll soon see how to add these messages to your message map. However, you also need to declare the message response functions with which these messages will be associated. Listing 6.6 shows how the Menu application's CMainFrame class declares its menu message response member functions.
Listing 6.6 LST06_06.CPPDeclaring Menu Message Handlers
afx_msg void OnEnableEnable(); afx_msg void OnEnableOption1(); afx_msg void OnEnableOption2(); afx_msg void OnChecksOption1(); afx_msg void OnChecksOption2(); afx_msg void OnChecksOption3(); afx_msg void OnBullets(UINT nID); afx_msg void OnSwitchOnOff();
With the exception of the OnBullets() function, each of the menu message map functions are named using the same convention. The function name starts with the
prefix On followed by the menu's title and the command's title in upper and lowercase. So, for example, the function associated with the Enable menu's
Enable command is called OnEnableEnable(), whereas the function associated with the Checks menu's Option1 command is called
OnChecksOption1(). The OnBullets() function gets its name from the Bullets menu. However, as you'll soon see, OnBullets() handles all of the
commands on the Bullets menu, so the individual command names are not added to the message map function's name.
In most MFC applications, you'll also need to create update-command-UI functions, which are responsible for the appearance of menu commands. For example, update-command-UI functions can add check marks or bullets to menu items. They can also enable or disable menu items. Just as with the regular menu message handlers, the update-command-UI handlers must be declared in your class. Listing 6.7 shows how they're declared in the Menu application's CMainFrame class.
Listing 6.7 LST06_07.CPPDeclaring Update-Command-UI Member Functions
afx_msg void OnUpdateEnableEnableUI(CCmdUI* pCmdUI); afx_msg void OnUpdateEnableOption1UI(CCmdUI* pCmdUI); afx_msg void OnUpdateEnableOption2UI(CCmdUI* pCmdUI); afx_msg void OnUpdateChecksOption1UI(CCmdUI* pCmdUI); afx_msg void OnUpdateChecksOption2UI(CCmdUI* pCmdUI); afx_msg void OnUpdateChecksOption3UI(CCmdUI* pCmdUI); afx_msg void OnUpdateBulletsUI(CCmdUI* pCmdUI); afx_msg void OnUpdateSwitchOnOffUI(CCmdUI* pCmdUI);
The naming convention for update-command-UI functions is similar to that used for regular menu message handlers. The main difference is that the prefix is
OnUpdate, and there's also the UI suffix. So, the name of an update-command-UI function for the Checks menu's Option2 command is
OnUpdateChecksOption2UI(). All of the update-command-UI functions have a single parameter, which is a pointer to a CCmdUI object. You'll soon learn how
the update-command-UI functions control the appearance of menu items.
Now that you have your menu message map functions declared, it's time to define the message map itself. To define the various types of message map functions used with menus, you use four new message map macros: ON_COMMAND, ON_COMMAND_RANGE, ON_UPDATE_COMMAND_UI, and ON_UPDATE_COMMAND_UI_RANGE. Listing 6.8 shows the message map entries for the Menu application's message handlers.
Listing 6.8 LST06_08.CPPMessage Handler Message Map Entries
ON_COMMAND(IDM_ENABLE_ENABLE, OnEnableEnable) ON_COMMAND(IDM_ENABLE_OPTION1, OnEnableOption1) ON_COMMAND(IDM_ENABLE_OPTION2, OnEnableOption2) ON_COMMAND(IDM_CHECKS_OPTION1, OnChecksOption1) ON_COMMAND(IDM_CHECKS_OPTION2, OnChecksOption2) ON_COMMAND(IDM_CHECKS_OPTION3, OnChecksOption3) ON_COMMAND(IDM_SWITCH_ONOFF, OnSwitchOnOff)
Use the ON_COMMAND macro to map a single menu command to a message response function. The macro's first argument is the message ID, and the second
argument is the name of the function that will handle that message. For example, in the Menu application, the Enable, Option1 command has a
command ID of IDM_ENABLE_OPTION1. This message ID, which is sent to the application whenever the user selects the Enable, Option1
command, is mapped to the message response function OnEnableOption1().
If you want to map a range of menu commands to a single message response function, use the ON_COMMAND_RANGE macro, like this:
ON_COMMAND_RANGE(IDM_BULLETS_OPTION1, IDM_BULLETS_OPTION3, OnBullets)
The ON_COMMAND_RANGE macro's three arguments are the range's starting ID, the range's ending ID, and the name of the message response function. In order for
this type of message mapping to work, the command IDs in the range must be consecutive. In the previous example, the Menu application's
IDM_BULLETS_OPTION1, IDM_BULLETS_OPTION2, and IDM_BULLETS_OPTION3 command IDs (which are associated with the Option1, Option2
, and Option3 commands on the Bullets menu) are all being mapped to the OnBullets() message response function. When you examine
OnBullets() later in this chapter, in the section titled "Writing Menu Message Handlers," you'll see how the range mapping works.
When I said that the command IDs used in the ON_COMMAND_RANGE macro must be consecutive, I'm referring to the values of the IDs, not the apparent order of the constants that represent the IDs. For example, if the constants CONST1, CONST2, and CONST3 represent the values 100, 101, and 102, respectively, they are consecutive. However, if the CONST1 equals 100, CONST2 equals 102, and CONST3 equals 101, they are not consecutiveeven though the constant names seem to be consecutive.
See Defining a Message Map, (ch. 4)
Once you have your message-response functions added to your message map, it's time to think about any update-command-UI functions you might need. If you have any menu items that are subject to enabling/disabling, check marking, or other similar changes, you'll need to add ON_UPDATE_COMMAND_UI macros to your message map. In the Menu application, the update-command-UI entries look like Listing 6.9.
Listing 6.9 LST06_09.CPPDefining Update-Command-UI Message Map Entries
ON_UPDATE_COMMAND_UI(IDM_ENABLE_ENABLE, OnUpdateEnableEnableUI) ON_UPDATE_COMMAND_UI(IDM_ENABLE_OPTION1, OnUpdateEnableOption1UI) ON_UPDATE_COMMAND_UI(IDM_ENABLE_OPTION2, OnUpdateEnableOption2UI) ON_UPDATE_COMMAND_UI(IDM_CHECKS_OPTION1, OnUpdateChecksOption1UI) ON_UPDATE_COMMAND_UI(IDM_CHECKS_OPTION2, OnUpdateChecksOption2UI) ON_UPDATE_COMMAND_UI(IDM_CHECKS_OPTION3, OnUpdateChecksOption3UI) ON_UPDATE_COMMAND_UI(IDM_SWITCH_ONOFF, OnUpdateSwitchOnOffUI)
As you can see, update-command-UI message map entries are similar to the regular ON_COMMAND entries, requiring two arguments: the ID for which you're defining the entry and the name of the function to which the ID should be mapped. However, although the definition of the table entries is similar, they way you write the actual response update-command-UI functions is very different, as you'll soon see.
There is also a version of the ON_UPDATE_COMMAND_UI macro that can map a range of IDs to a single function. As you may have guessed, that macro is ON_UPDATE_COMMAND_UI_RANGE, which is used like this:
ON_UPDATE_COMMAND_UI_RANGE(IDM_BULLETS_OPTION1, IDM_BULLETS_OPTION3, OnUpdateBulletsUI)
The macros arguments are the ID for the start of the range, the ID for the end of the range, and the name of the function to which the IDs should be mapped.
When it comes to responding to menu commands, the process is fairly straightforward once you've created the message map. You just write a function that
performs whatever tasks the menu command is supposed to handle. For example, in the Menu application, the Enable, Enable command turns on
the other commands in the Enable menu, as shown in Listing 6.10.
Listing 6.10 LST06_10.CPPResponding to a Menu Command
void CMainFrame::OnEnableEnable() { m_enabled = !m_enabled; }
The OnEnable() message response function simply toggles the value of the m_enabled flag, which determines whether the Option1 and Option2
commands in the Enable menu will be enabled or disabled. The m_enabled flag is used in the OnUpdateEnableOption1UI(),
OnUpdateEnableOption2UI(), and OnUpdateEnableEnableUI() functions to determine the visual state of the associated menu items.
Once the Enable, Option1 and Enable, Option2 commands are enabled, the user can select them, which causes MFC to call the
associated message response function. In the case of Option1, that function is OnEnableOption1(), which is shown in Listing 6.11.
Listing 6.11 LST06_11.CPPResponding to the Enable Menu Option1 Command
void CMainFrame::OnEnableOption1() { MessageBox("Enable/Option1"); }
As you can see, this function simply displays a message box, indicating to the user that the menu command was received. In a real application, you'd almost certainly want to do something a little more sophisticated. However, regardless of what the message response function does or how complex it gets, the concept is the same. The user chooses a menu command; MFC calls the associated message response function; and that function performs whatever tasks are required by the command.
What about your need to respond to a range of commands with a single message response function? You may remember that the ON_COMMAND_RANGE macro sets
up your message map to handle this eventuality. In the Menu application, all the commands on the Bullets menu are handled by a single function, shown in
Listing 6.12.
Listing 6.12 LST06_12.CPPResponding to a Range of Command IDs
void CMainFrame::OnBullets(UINT nID) { if (nID == IDM_BULLETS_OPTION1) m_bulletOption = Option1; else if (nID == IDM_BULLETS_OPTION2) m_bulletOption = Option2; else m_bulletOption = Option3;
OnBullets() receives one parameter, nID, which is the ID number of the command on whose behalf the function was called. By examining nID, you can easily
perform whatever is required of the specific command. In the OnBullets() function, the program sets the currently selected option by saving it in
m_bulletOption. The m_bulletOption variable, not only informs the program of which option is currently active, but also, as you'll soon see, enables the
appropriate update-command-UI function to properly update the visual state of the Bullets menu.
The last topic you need to cover in this chapter is the update-command-UI functions, which control how your menu commands look to the user when a pop-up
menu is displayed. As with the more conventional message response functions, how you write your update-command-UI functions depends upon each menu's
purpose. In the case of the Menu application's Checks menu, the function only needs to check or uncheck the menu command, depending upon the value
of the flag associated with the command. For example, look at the OnUpdateChecksOption1UI() function shown in Listing 6.13.
Listing 6.13 LST06_13.CPPUpdating the Appearance of Menu Commands
void CMainFrame::OnUpdateChecksOption1UI(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_checkOption1); }
Here you can see that the function receives a single parameter, which is a pointer to a CCmdUI object. You use the CCmdUI object's member functions to manipulate the menu command. These member functions are Enable() (which enables or disables a menu item), SetCheck() (which adds or removes a check mark), SetRadio() (which adds or removes bullets), and SetText() (which changes a menu command's caption). All of these functions, except SetText(), require a single parameter. A non-zero value turns the attribute on, and a 0 turns the attribute off. For example, calling SetCheck(TRUE) turns a check mark on, whereas calling SetCheck(FALSE) turns it off. The argument for the SetText() function is the new text string for the menu item.
In Listing 6.13, the m_checkOption1 flag is either TRUE or FALSE, so it can be used to directly control whether the menu command displays a check mark. If you
recall, the value of m_checkOption1 is toggled whenever the user selects the Checks, Option1 command. The entire process goes something like
this:
A similar process to the one just outlined occurs every time the user opens a menu, with the update-command-UI functions updating each menu command's appearance before they are displayed.
Of course, as you may be thinking, the Checks menu commands might be better handled by a single update-command-UI function. And, you're right. The
second, and more elegant solution, is demonstrated in the Menu application by the OnUpdateBulletsUI() function, which handles the range of message IDs from
IDM_BULLETS_OPTION1 through IDM_BULLETS_OPTION3, as shown in Listing 6.14.
Listing 6.14 LST06_14.CPPUpdating a Range of Menu Commands
void CMainFrame::OnUpdateBulletsUI(CCmdUI* pCmdUI) { if (pCmdUI->m_nID == IDM_BULLETS_OPTION1) pCmdUI->SetRadio(m_bulletOption == Option1); else if (pCmdUI->m_nID == IDM_BULLETS_OPTION2) pCmdUI->SetRadio(m_bulletOption == Option2); else pCmdUI->SetRadio(m_bulletOption == Option3); }
The function in Listing 6.14 gets called three times when the user opens the Bullets menu: once for each command in the menu. The CCmdUI object passed
as the function's single parameter contains a data member, m_nID, that holds the ID of the message for which the function is currently being called. By using this
IDas well as the value of m_bulletOption, which holds the currently selected optionthe function can easily add or remove the bullets from the menu
commands.
MFC gives you all of the tools you need to create professional-looking menus for your applications. By taking advantage of message maps, you can associate a specific function with a menu message as well as keep your menu commands visually updated. Menus are an important element of most Windows applicationsan element that takes some work to implement properly. However, menu handling in an MFC program is much simpler than writing your menu-handling code from scratch.