digitalmars.com                      
Last update Thu Jun 2 19:05:07 2011

13. Lesson 4: Add Messages with ClassExpress

In Lessons 2 and 3, you learned how to generate an application framework and customize its user interface. However, the behavior of the resulting application is still generic, because it is supplied entirely by methods of MFC base classes. The inherited methods that handle Windows messages, in particular, often do nothing more than call DefWindowProc.

In this lesson, you use ClassExpress to expand the functionality of the TML Reader and add member functions that handle Windows messages announcing user actions. Member functions that handle Windows messages are called message handlers, or just handlers. You can think of them as callbacks, which are called by MFC when the message they are intended to process is received. A message handler is called in response to only one message.

The Windows message stream is the lifeblood of a Windows application, regardless of whether the application is constructed in an object-oriented or a procedural way. A well-behaved Windows application can interact with the user only if it taps into the message stream and responds to messages in nondefault ways. Using ClassExpress to add MFC message handlers demonstrates how easy it is to enhance the behavior of your MFC application.

In this lesson, you:

At the conclusion of this lesson, you will have: The next section provides a brief introduction to Windows message handling in MFC to help you understand how messages are handled in TML Reader.

Windows Message Handling in MFC

Regardless of how it is written, a Windows application receives messages that inform it of user actions and their consequences, changes in the state of other applications, or changes in the state of Windows itself. MFC transforms this message-driven model into the object-oriented model defined by its class hierarchy, thereby introducing several improvements to the design of applications. To discuss this transformation and its benefits, it is necessary first to review how messages are handled in a traditional Windows program, and to identify the shortcomings of that approach.

Message handling in a traditional Windows application

In a traditional Windows program written in C, you handle a Windows message by adding code directly to the window procedure to which that message is dispatched by your application's message loop. The window procedure usually consists of a large switch statement whose cases are different messages.

For example, to handle the WM_SIZE message, you must add a case WM_SIZE to the switch. The statements following that case statement must have to unpack and coerce the window procedure's parameters, wParam and lParam, into constituent parts in a way that is specific to the WM_SIZE message. The result would look much like the following:

	LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
	{
	    // local and static variables... 
	    switch (msg)
	    {   
		// ... 
		case WM_SIZE:
		{   
		    UINT nType = (UINT) wParam;
		    int cx = LOWORD(lParam); 
		    int cy = HIWORD(lParam);
		    // Now do what you really want to do 
		    // ...
		} 
		break; 
		// ... 
		default:
		    break; 
	    } 
	    return DefWindowProc(hwnd, msg, wParam, lParam); 
	} 
The switch statement usually sprawls across several pages, and the bodies of case statements often spill over the right margin of the page. Data that must be preserved across messages or shared between cases is usually stored in automatic or static variables visible to every case. The result enjoys none of the benefits of encapsulation or data hiding afforded by C++, and it is difficult to read, comprehend, and maintain.

MFC's design

In MFC, window procedures are part of MFC itself; you do not edit them. Instead, the window procedures route each message to a handler- a member function of some class- whose purpose is to process that message. MFC provides default handlers, which collectively define the default behavior of an MFC application. You supply handlers only for those messages you want to process. Each handler you supply is a member function of one of your derived classes and overrides the inherited handler in the base class.

However, you will sometimes find that your handler can call the inherited handler to perform the bulk of its work.

For example, to handle the WM_SIZE message, use ClassExpress to add a handler for this message to your View class. (By convention, the handler for the WM_SIZE message is named OnSize.) Whenever a WM_SIZE message is received by a window represented by an object of your View class, your handler is called. The prototype of this handler is as follows:

	void OnSize(UINT nType, int cx, int cy); 
Notice that the parameters of OnSize contain the same information that the WM_SIZE message bears in the wParam and lParam window procedure arguments, but in an immediately usable form. No unpacking or typecasting is required. In general, the signature of each handler- its return type, the number of its arguments and their types- is specific and appropriate to the message it processes. MFC parses the messages before calling the handler.

To route Windows messages to handlers, MFC consults tables, called message maps, that pair Windows messages with the member functions that handle them. You never have to edit message maps manually because AppExpress and ClassExpress create and maintain them for you. AppExpress creates message maps when you generate an application framework. Their definitions are placed in the implementation (.cpp) files of your derived classes. When you use ClassExpress to add a handler to a class, the message map is updated automatically with a new entry associating the chosen message with your handler. Furthermore, ClassExpress adds a prototype for the new handler to the class's header file and inserts a stub for the member function into the implementation file. All you need to do is to add code to the body of the handler.

Note: This introduction to MFC message handling has necessarily been brief and has glossed over several topics you will want to learn more about. For a definitive discussion of how MFC works, see the expository chapters of the Microsoft Foundation Class Library Reference.

Because you used AppExpress in Lesson 2 to generate an application framework for the TML Reader, message maps have already been created for your derived classes. During the course of this lesson, ClassExpress updates them. ClassExpress can be run either separately- from a Program Manager icon- or from within the IDDE. Because you will be using the Source window to edit the code that ClassExpress generates, you will launch ClassExpress from within the IDDE.

Launching ClassExpress

To launch ClassExpress from the IDDE:
  1. If you are not already in the IDDE, launch it.
  2. Open the project tmlread.prj in directory samples\tutorial\lesson4\start.
  3. Choose ClassExpress from the Tools menu. This launches ClassExpress and automatically loads the project tmlread.prj.
The ClassExpress window features a multipage interface. Its left column presents a list of the six different pages that can be displayed in the area to the right. You will work on the Message Maps page, which is selected automatically when you launch ClassExpress. The ClassExpress window is displayed, as in Figure 13-1.

[Figure 13-1 ClassExpress, displaying the Message Maps page]

The lists on the Message Maps page

The drop-down combobox at the top of the page labeled Class contains a list of all derived classes in the TML Reader application. The class selected here directly determines the contents of two lists beneath it- those labeled Control IDs in Class and Function Name.

The list labeled Control IDs in Class contains an entry for the class itself, as well as the names of any commands and controls that the selected class could potentially handle.

The list labeled Function Name contains a list of message handlers already generated for the selected class by AppExpress. These methods are referenced in the class's message map.

The contents of the list labeled Windows Messages depends on what you select in the Control IDs in Class list. If you select the class name in the Control IDs list, then the Windows Messages list contains a list of Windows messages. However, if you select a control ID, the Windows messages list contains notification messages appropriate to that type of control. If you select a command ID (usually corresponding to a menu item), the rightmost list contains names of potential message map entries for commands.

Because the TML Reader handles messages that signal user input, the handlers must be added to the class corresponding to the window that receives those messages. Thus, you will add the handlers to the View class, CTMLReadView.

Adding Message Handlers

The requirements of the TML Reader dictate the messages you will add handlers for.
WM_SIZE
Detect when the window is resized, so it can recompute word-wrapping
WM_VSCROLL
Permit scrolling through a document using the vertical scrollbar
WM_KEYDOWN
Permit scrolling through a document using the keyboard
WM_SETCURSOR
Change the cursor when it is positioned over a hyperlink
WM_LBUTTONDOWN
Detect clicks on hyperlink jumps so it can change its display
WM_ERASEBKGND
Repaint the window background

Adding a handler for WM_SIZE

To add a handler for WM_SIZE to your View class:
  1. From the drop-down list labeled Class, select the name of the View class, CTMLReadView.
  2. Use the scrollbar to move through the Windows messages list until the message WM_SIZE is visible.
  3. Double-click on WM_SIZE.
Notice that a new entry appears in the Function Name list:
	afx_msg void OnSize(UINT nType, int cx, int cy); 
This is the prototype of a handler for the WM_SIZE message, as it would appear within a class declaration.

Also notice that a solid square appears to the left of the message name, WM_SIZE, indicating that a handler now exists for that message.

The ClassExpress window is displayed, as in Figure 13-2.

[Figure 13-2 ClassExpress after adding a WM_SIZE handler]

Adding other message handlers

Follow the procedure described in Steps 2 and 3 in the previous section to add a handler for each of the messages WM_VSCROLL, WM_KEYDOWN, WM_SETCURSOR, WM_LBUTTONDOWN and WM_ERASEBKGND: use the scrollbar of the Windows messages list to scroll to the message, then double-click on the message. Again, notice that for each message you double-click on, a new prototype is added to the Function name list, and a solid square appears to the left of the message name.

What you have just done

When you add a handler for a message WM_messagename, ClassExpress generates code in three places. For example, when you add a handler for WM_SIZE, ClassExpress generates the following: Now, whenever a CTMLReadView window receives a WM_SIZE message, the member function OnSize is called to handle it.

Saving Your Work

To save your work and return to the IDDE, click Close at the bottom of the ClassExpress window.

Clicking Close updates your project files and returns you to the IDDE. To observe the handlers in action and confirm that they are indeed called when you expect them to be, you can add code that notifies you when they are called.

Adding Code to Handlers

To add code to the OnLButtonDown and OnSize handlers, follow these steps:
  1. Open the file tmlrdvw.cpp in a Source window.
  2. Find the function CTMLReadView::OnLButtonDown.
  3. Add the following line to the top of the function:
    	AfxMessageBox("Left button clicked!"); 
    	
  4. Now find the function CTMLReadView::OnSize.
  5. Add the following line to the top of the function:
    	::MessageBeep(-1); 
    	
  6. Type Ctrl+S to save your changes.
After the project is rebuilt, you'll want to confirm that the appropriate statement executes whenever you click in the client area or resize the window.

Building and Running the Project

To see the effect of what you've done, perform these steps:
  1. Choose Build from the Project menu to update the executable.
  2. Choose Execute Program from the Project menu to run the program.
  3. With the mouse cursor in the client area of the program's window, press the left mouse button. A box containing the message "Left button clicked!" is displayed (Figure 13-3). This message is displayed each time you press the left mouse button in the client area. It is not displayed when you release the left mouse button, move the mouse, or click with the right mouse button. To remove the message box, click OK.

    [Figure 13-3 TML Reader running, with message box displayed]

  4. Now resize the window by dragging a corner of the window's border to a different position. You hear a beep when you release the mouse button.

    Note: You will already have heard beeps during the initial sequence of messages received by the View window. This is normal: the window receives multiple WM_SIZE messages early in its life-cycle.

  5. After you have confirmed to your satisfaction that the handlers are called when, and only when, they ought to be called, quit the program by choosing Exit from TMLRead's File menu.

Summary

In this lesson, you have learned: In the next lesson, you implement a Preferences dialog box. You learn how MFC and ClassExpress make it easy to create a dialog box, validate its data, and exchange data between the dialog box and your application. In TML Reader's present state, the only preference one could have would be for the application to actually do something. Toward that end, we have added code to the result of this lesson so that your starting point in the next lesson is a functioning TML Reader.
Home | Compiler & Tools | Runtime Library | STL | Search | Download | Forums | Prev | Next