Webocreation

Monday, November 23, 2009

Create a MessageMap

Create a MessageMap

To get you started using message maps, this section walks you through the process of declaring and implementing a message map and adding the corresponding message handler. Most of the work required to create a message map is done automatically by AppWizard. Your responsibility in working with messages is limited to making message map connections between messages and their handler functions.

This section includes the following topics:

® Declaring a Message Map

® Implementing a Message Map

® Adding a Message Handler

For detailed information about message map construction, see "Technical Note 6: Message Maps," available in the Microsoft Foundation Class Reference section in the Visual C++ online documentation.

Declaring a Message Map

To declare a message map, add a DECLARE_MESSAGE_MAP macro to the end of a class declaration in the corresponding header file, xxx.h. If you use AppWizard to create the starter files for your application, a declare statement for each class is added automatically to the header file. The DECLARE_MESSAGE_MAP statement simply creates a message map table in which each of the message map entries will be stored. ClassWizard maintains the declarations for message handlers.

For example, the OnLButtonDblClk message handler and message map associated with the CMsgView class are declared with the following statements in the header file, xxx.h:

// Generated message map functions
protected:
//{{AFX_MSG(CMsgView)
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

The afx_msg prefix is a visual reminder that OnLButtonDblClk is a message handler. You can omit afx_msg from your code because it evaluates to nothing when the code is compiled. The term "afx_msg" implies that a function behaves as a virtual function, but does not require a vtable entry.

The DECLARE_MESSAGE_MAP statement is the final statement in the class declaration because it uses C++ access specifiers to specify the visibility of its member functions. You can declare member functions of your own following the DECLARE_MESSAGE_MAP statement, but if you do, you should start off with a public, protected, or private keyword to ensure that you get the accessibility you want for these member functions.

Implementing a Message Map

Once the message map is declared, you can use ClassWizard to implement the message map and add the appropriate entries to the class implementation file, xxx.cpp. The BEGIN_MESSAGE_MAP and END_MESSAGE_MAP macros bracket the message map. The following example code shows a message map for the derived view class, CMsgView, in the implementation file, Msg.cpp.

// MSG.CPP
BEGIN_MESSAGE_MAP(CMsgView, CView)
//{{AFX_MSG_MAP(CMsgView)
ON_WM_LBUTTONDBLCLK()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

The BEGIN_MESSAGE_MAP macro begins the message map and identifies both the class to which the message map belongs and the base class. Message maps are passed by inheritance just as other class members are, and the base class is required so the framework can identify the message map associated with the base class. If the message is not handled, the framework determines to which base class to pass a message by looking at the parameters to BEGIN_MESSAGE_MAP. The END_MESSAGE_MAP macro ends the message map.

ClassWizard maintains the message map entries between the //{{AFX_MSGMAP and //{{AFX_MSGMAP comments. Anything placed in this area should be placed there by ClassWizard. ON_WM_LBUTTONDBLCLK is a macro defined in the header file (.h), which adds an entry for WM_LBUTTONDBLCLK messages to the message map. The macro accepts no parameters because it is hard coded to link WM_LBUTTONDBLCLK messages to the OnLButtonDblClk message handler.

You can also process a message for which MFC does not provide a message map macro. To do this, you create an entry for the message using the ON_MESSAGE macro, which accepts two parameters: the message ID and the address of the corresponding message handler. The second statement of the following example code maps WM_SETTEXT messages to a message handler called OnSetText:

BEGIN_MESSAGE_MAP(DerivedClass, BaseClass)
ON_MESSAGE (WM_SETTEXT, OnSetText)
END_MESSAGE_MAP()

For information about using ClassWizard, see Using Wizards to Handle Messages later in this chapter.

Adding a Message Handler

After declaring and creating a message map for a class, you can add a corresponding message handler to the class implementation file, xxx.cpp. Later in this chapter, you will learn to use ClassWizard or the WizardBar to add message handlers as needed for your classes. When you use ClassWizard to create a new class, it provides a message map for the class. Alternatively, you can create a message map manually using the source code editor; this is only recommended for experienced MFC developers.

The following example code shows a message map in the CMsgView class for the ON_WM_LBUTTONDBLCLK Windows message followed by its associated handler, OnLButtonDblClk. This code is located in the implementation file, Msg.cpp.

// MSG.CPP
BEGIN_MESSAGE_MAP(CMsgView, CView)
//{{AFX_MSG_MAP(CMsgView)
ON_WM_LBUTTONDBLCLK()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CMsgView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
MessageBox("You have just double-clicked the left mouse button");
CView::OnLButtonDblClk(nFlags, point);
}

If you manually add message handlers to your message map, you should add the prototypes for those handlers outside the message map area bounded by the //{{AFX_MSG_MAP and //}}AFX_MSG_MAP comments. This area is maintained by ClassWizard and any code placed in this area should be placed there by ClassWizard.
a Use ClassWizard to create message-map entries. If you add message-map entries manually, add them outside the //{AFX_MSG section. Otherwise, you may not be able to use ClassWizard later to edit the message map.

To see a demonstration showing how the message map and message handler for the OnRButtonDown function are implemented in an application's source files, click this icon.

How MFC Processes Messages

In previous sections, you've seen how the message map is built and where its various pieces are located in the application's source files. In this section, we'll look at how MFC routes messages through the framework and calls the appropriate message handlers to respond to the messages.

Both Windows and command messages are usually sent to the main frame window of the application. The MFC window procedure gets the messages and routes them differently, depending on the type of message received.

The best way to understand message handling in MFC is to look at the path messages usually follow as they are routed through the framework. Since Windows and command messages are handled differently, first we'll look at Windows messages and how they are handled, and then we'll look at command messages.

This section includes the following topics:

How Windows Messages Are Handled

Windows messages are routed through the framework in a three-step process. Briefly, this process is as follows:

1. The message is sent to a window — more specifically, to a window's window procedure.

2. Using the window object's message map, the MFC window procedure searches for a message handler that pertains to the message.

3. If the window procedure finds the correct message handler, it calls the handler and passes relevant message parameters, such as where the event took place.

To view an animation that shows how a Windows message is routed, click this icon.

The process of routing Windows messages is described in more detail in the following paragraphs.

Step 1: Windows Sends a Message to a Window

To start processing a Windows message, the Windows operating system sends a message to a window object. The message is usually handled by the window to which it is sent. The window object might be a main frame window, a child window, a standard control, a dialog box, or a view. MFC supports Windows messages through the CWnd class and CWnd-based classes, such as CView and CFrameWnd. You can add a handler to any of the CWnd classes to support Windows messages.

Step 2: Window Procedure Searches for Message Handler

After the window receives the message, the window procedure CWnd::WindowProc is called to find a message handler. The WindowProc function searches for the pertinent message map entry by iterating through the array of message map structures associated with the window class. The message map entry defines the messages a class will handle and correlates these messages to their message handlers.

Step 3: Window Procedure Calls Message Handler

Finally, if the window procedure finds a message handler for the targeted window class, it calls the handler; otherwise, the base class will be checked for a handler. If no handler exists there, then its base class will be examined for a handler. This process continues up through the class hierarchy until either a handler is found or until the CWnd class is reached. If no handler in the derived or base class is found, the default window procedure, CWnd::DefWindowProc, is called, which provides default behavior for all Windows events (moving, sizing, and so on).

How Command Messages Are Handled

Unlike Windows messages, command messages can be handled by a wide variety of objects — the application, documents, document templates, windows, and views. A command message originates from a menu item, command button, or accelerator key.

Any class derived from the CCmdTarget class is eligible to become a destination for a command message. Key classes that are derived from CCmdTarget are CWnd, CView, CWinApp, CDocument, CWnd, and CFrameWnd. Note that CWnd classes can receive both command messages and Windows messages.

When a command affects a particular object, it makes sense to have that object handle the command. For example, the Open command on the File menu is logically associated with the application, so the handler for the Open command is a member function of the application class.

The following paragraphs first examine how the framework routes command messages, and then briefly review how command messages are sent and received in an MFC application.

Routing Command Messages

There is more flexibility in routing a command message than there is with a Windows message. The framework routes command messages through a standard sequence of command-target objects, one of which is expected to have a handler for the command. Each command-target object checks its message map to see if it can handle the incoming message. The following illustration shows the routing sequence for command messages.



The various command-target objects check their own message maps at different times. Typically, a class routes the command to certain other objects to give them first chance at the command. If none of those objects handle the command, the original class checks its own message map. Then, if the class can't supply a handler, it may route the command message to yet more command targets. The following table shows the standard command routing sequence for the various classes.

When an object of this type receives a command It gives itself and other command-target objects a chance to handle the command in this order


MDI frame window

(CMDIFrameWnd) 1. Active CMDIChildWnd

2. This frame window

3. Application (CWinApp object)

Document frame window

(CFrameWnd,

CMDIChildWnd) 1. Active view

2. This frame window

3. Application (CWinApp object)



View 1. This view

2. Document attached to the view


Document 1. This document

2. Document template attached to the document



Dialog box 1. This dialog box

2. Window that owns the dialog box

3. Application (CWinApp object)


When entries in the second column of the table refer to other objects, go to the referred object in the first column to follow the routing of the command further. For example, when you read in the second column that the view forwards a command message to its document, see the "Document" entry in the first column and follow the command routing shown in the second column.

Sending Command Messages

Most messages result from user interaction with the program. Command messages are generated by mouse clicks in menu items or toolbar buttons, or by accelerator keystrokes.

The CWinApp::Run member function retrieves messages and dispatches them to the appropriate window. Most command messages are sent to the main frame window of the application. The WindowProc function gets the messages and routes them appropriately based on the type of message received.

Receiving Command Messages

The initial receiver of a message must be a window object. Command messages, usually originating in the application's main frame window, get routed by the framework through the command-target chain described previously under "Routing Command Messages."

Each object capable of receiving messages or commands has its own message map that pairs a command message with its handler function. When a command-target object receives a command message, it searches its message map for a match. If it finds a handler for the message, it calls the handler. Otherwise the message is handled by the default window procedure, DefWindowProc.


Using Wizards to Handle Messages


In this section, you will learn how to use wizards to make message-map connections between messages and message handlers. ClassWizard and WizardBar are the tools that are most often used to manage these message-handling tasks in your applications.

When you use AppWizard to create the starter files for your application, it automatically adds common message handlers. However, it is often necessary to add, modify, or delete message handlers in your application, a task for which you use ClassWizard.

This section includes the following topics:



  1. Adding Handlers with ClassWizard


ClassWizard is a tool designed specifically to connect messages to message handlers. Some possible scenarios where ClassWizard can be used to help create your application are listed below:

® You determine that one of your classes must handle a certain Windows message, so you run ClassWizard to make the connection.

® You create a menu or accelerator resource, then invoke ClassWizard to connect the command associated with that object to a handler.



As you develop MFC applications, you'll find that ClassWizard greatly simplifies your message-management tasks. ClassWizard writes the following information to your source files:

® A declaration of the handler as a member function of the class in the header file, xxx.h

® The appropriate message map entry for the connection in the implementation file, xxx.cpp

® An empty function template for you to fill in with the handler's code in the implementation file, xxx.cpp



ClassWizard does not make changes to code that you have written.

a Use ClassWizard to create and edit all message-map entries. If you add them manually, you may not be able to edit them with ClassWizard later. If you add them outside the bracketing comments as recommended, //{{AFX_MSG_MAP(classname) and //}}AFX_MSG_MAP, ClassWizard cannot edit them at all. By the same token, ClassWizard will not touch any entries you add outside the comments, so feel free to add messages outside the comments if you do not want them to be modified.

To see a demonstration showing how to add a message handler using ClassWizard, click this icon.

Deleting Handlers with ClassWizard

You can delete a message handler with ClassWizard, provided the handler you want to delete was created using ClassWizard.

When you delete a handler, ClassWizard deletes the message map entry and the prototype for the message handler. However, it does not remove the actual handler code — you must do this manually. This protects you against accidentally deleting important code that you may have written.

To see a demonstration showing how to delete a message handler using ClassWizard, click this icon.

a

For more information about using ClassWizard to manage message maps, see the Visual C++online documentation.



  1. Handling Messages with the WizardBar


Most of the message-handling tasks that you can accomplish with ClassWizard can also be performed with the WizardBar. To view the WizardBar, right-click in an unused portion of the Developer Studio menu bar. On the shortcut menu that appears, click WizardBar. The following illustration shows a sample display of the WizardBar.


Note The WizardBar remains inactive unless you have a project open.


To see a demonstration showing how to use the WizardBar to add or edit message handlers, click this icon.

a

WizardBar Combo Boxes

The WizardBar contains three combo boxes, as shown in the previous illustration. These combo boxes are drop-down lists of the classes, filters, and members related to the active project. The three combo boxes have a hierarchical relationship: the class that you select determines available filters, and the filter that you select determines what is displayed in the Members list.

The WizardBar Class list always displays classes in the active project. If your workspace contains projects written in more than one language, or a subproject, you can change the active project. The Filters list provides useful filters related to the current class. You can select the All Class Members filter, or you can select specific resource IDs. The Members list displays the result of the filter selected — for example, the specified class members, or the Windows message handlers defined for the selected resource ID.

WizardBar Action Control

The WizardBar Action control provides a direct way to perform common tasks such as jumping to a function or method definition. The Action control consists of two parts: the Action button on the left, and the drop-down Action menu on the right.

The task performed when you click the Action button, or the default action, is displayed in the ToolTip for the button. (It is also the item listed in bold on the Action menu.) The default action changes depending on the current selection in the WizardBar combo lists.

The Action button has three possible states:

® Tracking

® Active

® Disabled (no project open)



The Tracking state indicates that the WizardBar is currently tracking your context. The Active state indicates that the WizardBar itself is active—that is, there is a project open in the workspace. The Disabled state indicates that there is no open project.

The WizardBar Action menu appears when you click the arrow next to the Action button, or when you click the right mouse button when the focus is on a WizardBar combo control.


Self-Check Questions


1. The MFC message map is:

a A. A tool provided in Developer Studio to add message handlers to an application.

a B. A system that connects a message to the class member functions handling the message.

a C. An MFC class that defines how messages should be routed.

a D. A file generated by AppWizard used to maintain message-handling data structures.



2. Which one of the following is the responsibility of the function CWnd::WindowProc?

aa A. Retrieves messages from the application’s message queue.

a B. Contains a large switch statement that has the code to handle the specific messages.

a C. Simply calls CWnd::DefWindowProc to handle all messages.

a D. Searches the message map in the current CWnd-derived class for a handler for the current message.



3. In an MFC application, what is the message map macro name and handler name associated with the WM_RBUTTONDOWN Windows message?

a A. The macro is WM_RBUTTON_MESSAGE and the handler is OnRButtonMessage.

a B. The macro is OnRButtonDown and the handler is ON_WM_RBUTTONDOWN.

a C. The macro is ON_WM_RBUTTONDOWN and the handler is OnRButtonDown.

a D. Since general window messages are handled by the MFC framework, they never need message map entries.



4. Which tools inside Developer Studio are typically used to manage message map entries?

a A. ClassWizard only

a B. The WizardBar and ClassWizard

a C. ClassWizard and the Dialog editor

a D. AppWizard and ClassWizard



5. Which of the following MFC classes is most crucial to the message mapping system?

a A. CCmdTarget

a B. CDocument

a C. CObject

a D. CMenu



6. There is more flexibility in routing a command message than a Windows message since command messages can be handled by a wide variety of objects.

a A. True

aaaa B. False


Lab 6.1: Messages with MFC


In this lab, you will create a simple MDI application in which you examine Windows messages and add message handlers for mouse messages.

To see a demonstration that shows what you will accomplish during the lab, click this icon.

a

Estimated time to complete this lab: 45 minutes

To complete the exercises in this lab, you must have the required software. For detailed information about the labs and setup for the labs, see Labs in this course.

Objectives

After completing this lab, you will be able to:

® Create the framework for a simple MDI application.

® Add message handlers to an application using ClassWizard.

Prerequisites
There are no prerequisites for this lab.
Exercise
The following exercise provides practice with the concepts and techniques covered in this chapter.
® Exercise 1: Adding Message Handlers
In this exercise, you will build and run a simple MDI application using AppWizard. Then you will use ClassWizard to add message handlers and implement message boxes for the MDI application.

Exercise 1: Adding Message Handlers In this exercise, you will create a simple MDI application using AppWizard. You will add message handlers for several mouse-generated Windows messages to the MDI application you create. You will modify two of these message handlers, OnLButtonDown and OnRButtonDown, to display messages that provide information to the end user.


Create a simple MDI application

1. In Developer Studio, use AppWizard to create an MDI (exe) application named Msgr.
2. In Step 1 of AppWizard, click Multiple Documents.
3. In Steps 2 through 6, accept the defaults presented by AppWizard to create the starter files for the Msgr application.
After AppWizard creates the starter files for the application, you will be returned to the Developer Studio environment.

Add message handlers for mouse messages
1. On the View menu, click ClassWizard or press CTRL+W.
The MFC ClassWizard property page appears. The Message Maps tab is displayed by default.
2. In the combo boxes provided on the property page, click the Msgr project, the CMsgrView class, and the CMsgrView Object ID.
3. In the Messages combo box, scroll down and click the WM_LBUTTONDOWN message. Click Add Function.
4. Repeat Step 3 for the WM_RBUTTONDOWN message.
Display the position of the mouse clicks in a message box
1. In the Member functions combo box (at the bottom of the MFC ClassWizard property page), select the OnLButtonDown message handler, and then click Edit Code.

This places you in the empty function template for the OnLButtonDown message handler in the implementation file, MsgrView.cpp.

2. Note that the button-click message handlers are called with two parameters as shown by the following statement in the code:

void CMsgrView::OnLButtonDown(UINT nFlags, CPoint point)

® nFlags is a set of flags that indicate the possible virtual keys:
MK_CONTROL — Set if the CTRL key is down.
MK_LBUTTON — Set if the left mouse button is down.
MK_MBUTTON — Set if the middle mouse button is down.
MK_RBUTTON — Set if the right mouse button is down.
MK_SHIFT — Set if the SHIFT key is down.
® CPoint is a pointer to the location of the click relative to the origin, or upper-left corner of the window.
3. Display selected flags as strings that the user can understand. Create two strings, one for the state of MK_CONTROL and the other for MK_SHIFT, as follows:
CString CtrlPressed, ShiftPressed;
4. Mask the nFlags parameter to produce "Yes" or "No" to specify whether or not the SHIFT key is pressed and store the string in ShiftPressed, as follows:
ShiftPressed = (nFlags & MK_SHIFT)?"Yes":"No";
5. Repeat Step 4 for the CtrlPressed string, as follows:
CtrlPressed = (nFlags & MK_CONTROL)?"Yes":"No";
6. Declare a CString variable to hold a string copy of the point passed into the function and convert the point passed value into a string as follows:
CString MousePosition;
MousePosition.Format("[%d, %d]", point.x, point.y);
7. Since you are working in an MDI application, you can have multiple active views and documents. To see the view that was clicked, get the title of the document as follows:
CString DocName = GetDocument()->GetTitle();
8. Concatenate all of the strings, and add a few line breaks to make it easier to read, as follows:

CString Position =
"Left mouse button pressed in:\nDocument: \t" + DocName +
"\nMouse Position: \t" + MousePosition +
"\nControl: \t\t" + CtrlPressed +
"\nShift: \t\t" + ShiftPressed;
9. Display the resulting string in a message box, as follows:
AfxMessageBox(Position);
10. Save MsgrView.cpp. The completed code for the OnLButtonDown function should look like the following:

void CMsgrView::OnLButtonDown(UINT nFlags, CPoint point)
{
//unpack nFlags to show shift and control keys
CString CtrlPressed, ShiftPressed;

ShiftPressed = (nFlags & MK_SHIFT)?"Yes":"No";

CtrlPressed = (nFlags & MK_CONTROL)?"Yes":"No";
//convert position to string
CString MousePosition;
MousePosition.Format("[%d, %d]", point.x, point.y);
CString DocName = GetDocument()->GetTitle();
CString Position =
"Left mouse button pressed in:\nDocument: \t" + DocName +
"\nMouse Position: \t" + MousePosition +
"\nControl: \t\t" + CtrlPressed +
"\nShift: \t\t" + ShiftPressed;
AfxMessageBox(Position);
CView::OnLButtonDown(nFlags, point);
}
11. Implement the same functionality for the right button message handler, OnRButtonDown. To do this, you can copy the OnLButtonDown function and change the message from "Left mouse button..." to "Right mouse button..." If this were more than a simple exercise, you would put this interpretive code in a single function and call it from both handlers. The completed code for the OnRButtonDown function should look like the following:

void CMsgrView::OnRButtonDown(UINT nFlags, CPoint point)
{
//unpack nFlags to show shift and control keys
CString CtrlPressed, ShiftPressed;

ShiftPressed = (nFlags & MK_SHIFT)?"Yes":"No";

CtrlPressed = (nFlags & MK_CONTROL)?"Yes":"No";
//convert position to string
CString MousePosition;
MousePosition.Format("[%d, %d]", point.x, point.y);
CString DocName = GetDocument()->GetTitle();
CString Position =
"Right mouse button pressed in:\nDocument: \t" + DocName +
"\nMouse Position: \t" + MousePosition +
"\nControl: \t\t" + CtrlPressed +
"\nShift: \t\t" + ShiftPressed;
AfxMessageBox(Position);
CView::OnRButtonDown(nFlags, point);

}
12. Save the implementation file, MsgrView.cpp.
13. Build and run the application, Msgr.exe.

You can find the code for this completed exercise in \Labs\Ch06\Lab01\Ex01

No comments:

Post a Comment