|
Reference 1. Introducing Digital Mars C++ 2. Introducing the IDDE Part 2: Creating an Application with Digital Mars C++ 3. Starting a Project and Defining Workspaces 4. Generating an Application Framework 5. Defining Classes and Their Hierarchies 6. Editing Program Code 7. Adding Look and Feel with Resources 8. Testing an Application Part 3: Learning Digital Mars C++ by Example 9. Introduction to the Tutorial 10. Lesson 1: Create the DOS Application 11. Lesson 2: Generate an Application Framework 12. Lesson 3: Customize the Interface 13. Lesson 4: Add Messages with ClassExpress 14. Lesson 5: Add a Dialog Box with ClassExpress Part 4: More about Creating Programs 15. More about Projects and Workspaces 16. More about Project Build Settings 17. More about AppExpress 18. More about ClassExpress 19. Class Editor Reference 20. Hierarchy Editor Reference 21. Text Editor Reference 22. Using Version Control Part 5: More about Testing Programs 23. Controlling and Configuring the Debugger 24. Commands Available in Debugging Mode Part 6: About Managing Resources 25. ResourceStudio Resource Editor 26. Dialog Editor 27. Menu, Accelerator and String Table Editors 28. Bitmap, Cursor, Icon, and Font Editors 29. Version Information and Custom Resource Editors Part 7: Appendixes A. Expression Evaluation B. IDDE Settings and Command-Line Options C. Using NetBuild |
13. Lesson 4: Add Messages with ClassExpressIn 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:
Windows Message Handling in MFCRegardless 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 applicationIn 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 designIn 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 ClassExpressTo launch ClassExpress from the IDDE:
[Figure 13-1 ClassExpress, displaying the Message Maps page]
The lists on the Message Maps pageThe 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 HandlersThe requirements of the TML Reader dictate the messages you will add handlers for.
Adding a handler for WM_SIZETo add a handler for WM_SIZE to your View class:
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 handlersFollow 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 doneWhen you add a handler for a message WM_messagename, ClassExpress generates code in three places.
Saving Your WorkTo 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 HandlersTo add code to the OnLButtonDown and OnSize handlers, follow these steps:
Building and Running the ProjectTo see the effect of what you've done, perform these steps:
SummaryIn this lesson, you have learned:
|