Teach Yourself Visual C++® 5 in 24 Hours

Previous chapterNext chapterContents


- Hour 8 -
Messages and Event-Driven Programming

Messages are at the heart of every Windows program. Even the 50-line MFC program in Hour 2, "Writing Simple C++ Programs," had to handle the WM_PAINT message. A good understanding of how the Windows operating system sends messages will be a great help to you as you write your own programs.

In this hour, you will learn

In this hour, you will also create a small sample program to learn how messages are passed to applications by the Windows operating system.

Understanding the Windows Programming Model

Programs written for Windows differ from most console-mode programs. The console-mode programs that you have seen in this book have consisted of short listings that created small sequential programs that assumed complete control over a console-mode window.

Although sequential programs work well for explaining simple concepts like the basics of the C++ language, they don't work well in a multitasking environment like Microsoft Windows. In the Windows environment everything is shared: the screen, the keyboard, the mouse--even the user. Programs written for Windows must cooperate with Windows and with other programs that might be running at the same time.

In a cooperative environment like Windows, messages are sent to a program when an event that affects the program occurs. Every message sent to a program has a specific purpose. For example, messages are sent when a program should be initialized, when menu selections are made, and when a window should be redrawn. Responding to event messages is a key part of most Windows programs.

Another characteristic of Windows programs is that they must share resources. Many resources must be requested from the operating system before they are used and, after they are used, must be returned to the operating system so that they can be used by other programs. This is one way Windows controls access to resources like the screen and other physical devices.

In short, a program that runs in a window must be a good citizen. It cannot assume that it has complete control over the computer on which it is running; it must ask permission before taking control of any central resource, and it must be ready to react to events that are sent to it.

Using AppWizard with Document/View

As you saw in the first few hours, the Developer Studio includes a tool, AppWizard (also called MFC AppWizard), which is used to create a skeleton application. AppWizard asks you a series of questions about your program; then it generates a project and much of the source code for you, letting you concentrate on the code that makes your program unique.

So far you have used AppWizard to create programs that use a dialog box as their main window. However, the real strength of AppWizard is its capability to help you create full Windows applications. Much of the code that is used as a starting point for Windows programs is generic "skeleton" code; AppWizard will generate this code for you.


Just a Minute: The MFC class library is built around the Document/View programming model. Using AppWizard to create the skeleton code for your program is the quickest way to get started writing Document/View programs.

A Quick Overview of the Document/View Architecture

The AppWizard uses MFC classes to create applications that are based on the MFC Document/View architecture. The basic idea behind Document/View is to separate the data-handling classes from the classes that handle the user interface.

Separating the data from the user interface enables each class to concentrate on performing one job, with a set of interfaces defined for interaction with the other classes involved in the program. A view class is responsible for providing a "viewport" through which you can see and manage the document. A document class is responsible for controlling all the data, including storing it when necessary. Figure 8.1 shows how the document and view classes interact with each other.

Figure 8.1.
The Document/View architecture.

You will learn more about the Document/View architecture in Hour 9, "The Document/View Architecture." For now, just be aware that four main "super classes" are used in a Document/View application:

In addition to the four classes listed here, you will learn in Hour 9 some specialized classes that are used in Document/View programs.

Types of Applications Built by AppWizard

AppWizard sets up the following types of generic programs for you:

After you select one of these application types, you are asked for more information about the new program. The opening MFC AppWizard screen is shown in Figure 8.2.

Figure 8.2.
The opening screen for MFC AppWizard.

Using AppWizard to Create an SDI Application

To create a simple SDI program, select Single Document on the opening MFC AppWizard screen. AppWizard displays six Wizard pages filled with default information for a typical SDI program. You can move to the next page by pressing the button labeled Next and to the previous page by pressing the button labeled Back. At any time you can tell AppWizard to create the project for you by pressing the button labeled Finish.

AppWizard will create several classes and files for you and create a project that you can use to manage the process of compiling the program. AppWizard creates these classes for a program named Hello:

In addition, AppWizard creates several files that are not used for C++ classes. Some of these files are

Creating a Windows program using AppWizard is easy. In fact, you can compile and run the program as it is now, although it doesn't really do anything.

What Are Messages?

Programs written for Microsoft Windows react to events that are sent to a program's main window. Examples of events include moving the mouse pointer, clicking a button, or pressing a key. These events are sent to the window in the form of messages. Each message has a specific purpose: redraw the window, resize the window, close the window, and so on.

New Term: The default window procedure is a special message-handling function supplied by Windows that handles the message if no special processing is required.

For many messages, the application can just pass the message to the default window procedure.

A Windows program can also send messages to other windows. Because every control used in a Windows program is also a window, messages are also often used to communicate with controls.

Two different types of messages are handled by a Windows program:

Examples of messages sent from the operating system include messages used to tell the program that it should start or close or to tell a window that it is being resized or moved. Messages sent to controls can be used to change the font used by a window or its title. Messages received from a control include notifications that a button has been pressed or that a character has been entered in an edit control.

There are two reasons why messages are used so heavily in Windows programs:

Queues work well for event-driven programming. When an event occurs, a message can be created and quickly queued to the appropriate window or program. Each message that is queued can then be handled in an orderly manner.

The fact that messages are language independent has enabled Windows to grow over the years. Today, you can write a Windows program using diverse languages such as Visual Basic, Delphi, Visual C++, or PowerBuilder. Because messages are language independent, messages can easily be sent between these programs. The message interface enables you to add new features to the programs you write and also enables Windows to grow in the future.


Just a Minute: Since it was first introduced, every release of Microsoft Windows has added new messages and new functionality. However, most of the core messages used in the initial version of Windows still are available, even on multiprocessor machines that are running Windows NT.


CAUTION: When using an event-driven programming model such as Microsoft Windows, you cannot always be certain about message order. A subtle difference in the way different users use a program can cause messages to be received in a different sequence. This means that every time you handle an event, you should handle only that particular event and not assume that any other activity has taken place.

A Program to Test for Mouse Clicks

As an example, you're about to create a program that actually shows how messages are used to notify your application about events. This program, MouseTst, will be an SDI application that displays a message whenever the mouse is clicked inside the client area. The first step in creating MouseTst is to use AppWizard to create an SDI application. Feel free to select or remove any options offered by AppWizard, because none of the options have any bearing on the demonstration. Name the application MouseTst.

What Are Message Queues?

Messages are delivered to all windows that must receive events. For example, the simple act of moving the mouse cursor across the main window of a Windows program generates a large number of messages. Messages sent to a window are placed in a queue, and a program must examine each message in turn. Typically, a program examines messages that are sent to it and responds only to messages that are of interest, as shown in Figure 8.3.

Figure 8.3.
Messages queued and handled in order by an application.

As shown in Figure 8.3, messages sent to a program are handled by a window procedure that is defined for the program.

How Are Messages Handled?

When a user moves the mouse over a program's main window, two messages are sent to the program's window procedure.

Another type of mouse message is the WM_LBUTTONDOWN message, sent when the primary mouse button is pressed. Because this is the left button for most mouse users, the message is named WM_LBUTTONDOWN. A similar message is WM_RBUTTONDOWN, sent when the secondary, usually right, mouse button is pressed.

These and other messages are sent to a window's window procedure. A window procedure is a function that handles messages sent to it. When a window procedure receives the message, the parameters passed along with the message are used to help decide how the message should be handled.

Handling Messages with ClassWizard

ClassWizard (also called MFC ClassWizard) adds code that typically is used for a particular message-handling function. This commonly reused, or "boilerplate," code can help reduce the number of errors still further, because it's guaranteed to be correct. Listing 8.1 is an example of a function created by ClassWizard to handle the WM_LBUTTONDOWN message.

TYPE: Listing 8.1. The OnLButtonDown function created by ClassWizard.

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

{

    //TODO: Add your message handler code here and/or call default



    CView::OnLButtonDown(nFlags, point);

} 

New Term: A message map connects messages sent to a program with the functions that are meant to handle those messages.

When AppWizard or ClassWizard adds a message-handling function, an entry is added to the class message map. Listing 8.2 shows an example of a message map.

TYPE: Listing 8.2. A message map for the CMyView class.

BEGIN_MESSAGE_MAP(CMyView, CView)

    //{{AFX_MSG_MAP(CMyView)

    ON_WM_LBUTTONDOWN()

    //}}AFX_MSG_MAP

    // Standard printing commands

    ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

    ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()


CAUTION: The message map begins with the BEGIN_MESSAGE_MAP macro and ends with the END_MESSAGE_MAP macro. The lines reserved for use by ClassWizard start with //{{AFX_MSG_MAP and end with //}}AFX_MSG_MAP. If you make manual changes to the message map, do not change the entries reserved for ClassWizard; they are maintained automatically.

Messages Handled by MouseTst

The MouseTst program must handle four messages used to collect mouse events. The messages used by MouseTst are listed in Table 8.1.

Table 8.1. Messages handled by MouseTst.

Message Function Description
WM_LBUTTONDOWN OnLButtonDown Left mouse button clicked
WM_LBUTTONDBLCLK OnLButtonDblClk Left mouse button double-clicked
WM_RBUTTONDOWN OnRButtonDown Right mouse button clicked
WM_RBUTTONDBLCLK OnRButtonDblClk Right mouse button double-clicked

In addition, when the WM_PAINT message is received, the MFC framework calls the OnDraw member function. MouseTst will use OnDraw to update the display with the current mouse position and last message.

Updating the CMouseTst View Class

All the work that keeps track of the mouse events will be done in the CMouseTstView class. There are two steps to displaying the mouse event information in the MouseTst program:

1. When one of the four mouse events occurs, the event type and mouse position are recorded, and the view's rectangle is invalidated. This causes a WM_PAINT message to be generated by Windows and sent to the MouseTst application.

2. When a WM_PAINT message is received by MouseTst, the CMouseTstView::OnDraw member function is called, and the mouse event and position are displayed.


Just a Minute: All output is done in response to a WM_PAINT message. WM_PAINT is sent when a window's client area is invalidated. This often is due to the window being uncovered or reopened. Because the window must be redrawn in response to a WM_PAINT message, most programs written for Windows do all their drawing in response to WM_PAINT and just invalidate their display window or view when the window should be updated.

To keep track of the mouse event and position, you must add two member variables to the CMouseTstView class. Add the three lines from Listing 8.3 as the last three lines before the closing curly brace in CMouseTstView.h.

TYPE: Listing 8.3. New member variables for the CMouseTstView class.

private:

    CPoint   m_ptMouse;

CString m_szDescription; The constructor for CMouseTstView must initialize the new member variables. Edit the constructor for CMouseTstView, found in CMouseTstView.cpp, so it looks like the source code in Listing 8.4.

TYPE: Listing 8.4. The constructor for CMouseTstView.

CMouseTstView::CMouseTstView()

{

    m_ptMouse = CPoint(0,0);

    m_szDescription.Empty();

} Using ClassWizard, add message-handling functions for the four mouse events that you're handling in the MouseTst program. Open ClassWizard by pressing Ctrl+W, or by right-clicking in a source-code window and selecting ClassWizard from the menu. After ClassWizard appears, follow these steps:

1. Select the CMouseTstView class in the Object ID list box; a list of messages sent to the CMouseTstView class will be displayed in the Message list box.

2. Select the WM_LBUTTONDOWN message from the Message list box, and click the Add Function button.

3. Repeat step 2 for the WM_RBUTTONDOWN, WM_LBUTTONDBLCLK, and WM_RBUTTONDBLCLK messages.

4. Click OK to close ClassWizard.

Edit the message-handling functions so they look like the function provided in Listing 8.5. You must remove some source code provided by ClassWizard in each function.

TYPE: Listing 8.5. The four mouse-handling functions for CMouseTstView.

void CMouseTstView::OnLButtonDblClk(UINT nFlags, CPoint point)

{

    m_ptMouse = point;

    m_szDescription = "Left Button Double Click";

    InvalidateRect( NULL );

}



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

{

    m_ptMouse = point;

    m_szDescription = "Left Button Down";

    InvalidateRect( NULL );

}



void CMouseTstView::OnRButtonDblClk(UINT nFlags, CPoint point)

{

    m_ptMouse = point;

    m_szDescription = "Right Button Double Click";

    InvalidateRect( NULL );

}



void CMouseTstView::OnRButtonDown(UINT nFlags, CPoint point)

{

    m_ptMouse = point;

    m_szDescription = "Right Button Down";

    InvalidateRect( NULL );

} Each of the message-handling functions in Listing 8.5 stores the position of both the mouse event and a text string that describes the event. Each function then invalidates the view rectangle. The next step is to use the CMouseTstView::OnDraw function to display the event. Edit CMouseTstView::OnDraw so it contains the source code in Listing 8.6. Remove any existing source code provided by AppWizard.

TYPE: Listing 8.6. The OnDraw member function for CMouseTstView.

void CMouseTstView::OnDraw(CDC* pDC)

{

    pDC->TextOut( m_ptMouse.x, m_ptMouse.y, m_szDescription );

} The OnDraw member function uses TextOut to display the previously saved event message. The CPoint object, m_ptMouse, was used to store the mouse event's position. A CPoint object has two member variables, x and y, which are used to plot a point in a window.

Running MouseTst

Build and run MouseTst, then click the main window's client area. A message is displayed whenever you click the left or right mouse button.

Figure 8.4 shows the MouseTst program after a mouse button has been clicked.

Figure 8.4.
The MouseTst program displaying a mouse event.

What Are MFC Base Classes?

The MFC class library includes a large number of classes well suited for Windows programming. Most of these classes are derived from CObject, a class that is at the root of the MFC class hierarchy. In addition, any class that represents a window or control is derived from the CWnd class, which handles basic functions that are common to all windows.


Just a Minute: The CObject and CWnd classes use virtual functions, which enable your program to access general-purpose functions through a base pointer. This enables you to easily use any object that is derived from CObject or CWnd when interacting with the MFC framework.

The CObject Base Class

Almost every class used in an MFC program is derived from CObject. The CObject class provides four types of services:

The CWnd Base Class

The CWnd class is derived from CObject and adds a great deal of functionality that is shared by all windows in an MFC program. This also includes dialog boxes and controls, which are just specialized versions of windows. Figure 8.5 shows some of the major MFC classes derived from CWnd.

Figure 8.5.
Some of the major MFC classes derived from CWnd.

The CWnd class defines functions that can be applied to any CWnd object, including objects that are instances of classes derived from CWnd. As first shown in Hour 5, "Button Controls," to set the caption or title for any window, including controls, you can use the CWnd::SetWindowText function.

Almost every significant object in an MFC program is a CObject instance. This enables you to take advantage of the MFC support for discovering many common memory leaks and other types of programming errors. The CObject class also declares functions that can be used to provide diagnostic dumps during runtime and support for serialization. Serialization is discussed in Hour 22.

Every window in an MFC program is a CWnd object. CWnd is derived from CObject so it has all the CObject functionality built in. Using the CWnd class to handle all controls and windows in your program enables you to take advantage of polymorphism; the CWnd class provides all the general window functions for all types of windows. This means you don't need to know exactly what type of control or window is accessed through a CWnd pointer in many cases.

An Example Using the CObject and CWnd Base Classes

The CObject and CWnd classes are used in different ways. The CObject class is normally used as a base class when you create your own classes. The CWnd class is often passed as a function parameter or return value and is used as a generic pointer to any type of window in an MFC program.

In this section, you create a sample console mode project that demonstrates how the CObject class is used. To start the sample, create a new console mode project named Runtime. In addition, configure the project so that it uses the MFC class library by following these steps:

1. Select Settings from the Project menu. This opens the Project Settings dialog box.

2. Click the General tab.

3. Select Use MFC in a Shared DLL from the Microsoft Foundation Classes combo box.

4. Close the dialog box by clicking OK.

Using CObject as a Base Class

The CObject class is always used as a base class; there isn't really anything that can be done with a plain CObject. When used as a base class, the CObject class provides a great deal of basic functionality to a class. You can control the amount of functionality provided by CObject by using macros in the derived class's declaration and definition files.

Four different levels of support are offered by CObject to its derived classes:

Each of the CObject macros is used in a similar way. All DECLARE macros have one parameter--the name of the class. The IMPLEMENT macros generally take two parameters--the name of the class and the name of the immediate base class. IMPLEMENT_SERIAL is an exception because it requires three parameters, as discussed in Hour 22.

Listing 8.7 is the class declaration for CMyObject, a simple class that is derived from CObject. The CMyObject class supports dynamic creation, so it includes the DECLARE_DYNCREATE macro.

TYPE: Listing 8.7. The CMyObject class declaration, using CObject as a base class.

class CMyObject : public CObject

{

    DECLARE_DYNCREATE( CMyObject );

// Constructor

public:

    CMyObject();

//Attributes

public:

    void Set( const CString& szName );

    CString Get() const;

//Implementation

private:

    CString m_szName;

}; Save the source code from Listing 8.7 in the Runtime project directory as MyObj.h. It's just an include file, so don't add it to the project.

The source code for the CMyObject member functions is provided in Listing 8.8. Save this source code as MyObj.cpp and add it to the Runtime project. This source file contains the IMPLEMENT_DYNCREATE macro that matches the DECLARE_DYNCREATE macro from the class declaration.

TYPE: Listing 8.8. Member functions for the CMyObject class.

#include <afx.h>

#include "MyObj.h"

IMPLEMENT_DYNCREATE( CMyObject, CObject );

CMyObject::CMyObject()

{

}

void CMyObject::Set( const CString& szName )

{

    m_szName = szName;

}

CString CMyObject::Get() const

{

    return m_szName;

}


Time Saver: It's important to remember that the DECLARE and IMPLEMENT macros are used in two different places. A DECLARE macro, such as DECLARE_DYNCREATE, is used in the class declaration. An IMPLEMENT macro, such as IMPLEMENT_DYNCREATE, is used only in the class definition.

Creating an Object at Runtime

There are two ways to create objects dynamically. The first method uses the C++ operator new to dynamically allocate an object from free storage.

CMyObject* pObject = new CMyObject;

The second method is used primarily by the MFC framework and uses a special class, CRuntimeClass, and the RUNTIME_CLASS macro. You can use CRuntimeClass to determine the type of an object or to create a new object. Listing 8.9 creates a CMyObject instance using the CRuntimeClass::CreateObject function.

TYPE: Listing 8.9. Creating an object at runtime using CRuntimeClass.

#include <afx.h>

#include <iostream.h>

#include "MyObj.h"

int main()

{

    CRuntimeClass* pRuntime = RUNTIME_CLASS( CMyObject );

    CObject* pObj = pRuntime->CreateObject();

    ASSERT( pObj->IsKindOf(RUNTIME_CLASS(CMyObject)) );



    CMyObject* pFoo = (CMyObject*)pObj;

    pFoo->Set( "FooBar" );



    cout << pFoo->Get() << endl;



    delete pFoo;

    return 0;

} Save the contents of Listing 8.9 as Runtime.cpp and add the file to the Runtime project. Compile the project and if there are no errors, run the project in a DOS window by following these steps:

1. Open a DOS window from the Start button's Programs menu.

2. Change the current directory to the project directory.

3. Type Debug\Runtime in the DOS window. The program executes and outputs FooBar.

Leave the DOS window open for now because you use it for the next example.

Testing for a Valid Object

The MFC class library offers several diagnostic features. Most of these features are in the form of macros that are used only in a debug version of your program. This gives you the best of both worlds. When you are developing and testing your program, you can use the MFC diagnostic functions to help ensure that your program has as few errors as possible, although it runs with the additional overhead required by the diagnostics. Later, when your program is compiled in a release version, the diagnostic checks are removed and your program executes at top speed.

Three macros are commonly used in an MFC program:

The ASSERT and VERIFY macros are used with all expressions, not just those involving CObject. Although they both test to make sure that the evaluated expression is TRUE, there is an important difference in the way these two macros work. When compiled for a release build, the ASSERT macro and the expression it evaluates are completely ignored during compilation. The VERIFY macro is also ignored, but the expression is compiled and used in the release build.


CAUTION: A common source of errors in MFC programs is placing important code inside an ASSERT macro instead of a VERIFY macro. If the expression is needed for the program to work correctly, it belongs in a VERIFY macro, not an ASSERT macro. These functions are used in examples throughout the rest of the book to test for error conditions.

Providing a Dump Function

In addition to the diagnostic functions and macros in the previous section, CObject declares a virtual function for displaying the contents of an object at runtime. This function, Dump, is used to send messages about the current state of the object to a debug window.

If you are debugging an MFC program, the messages are displayed in an output window of the debugger. Add the source code from Listing 8.10 to the CMyObject class declaration. The Dump function is usually placed in the implementation section of the class declaration; in this example, it should be placed after the declaration for m_szName. Because Dump is called only by the MFC framework, it is usually declared as protected or private. Because the Dump function is called only for debug builds, the declaration is surrounded by #ifdef and #endif statements that remove the declaration for Dump for release builds.

TYPE: Listing 8.10. Adding a Dump function to the CMyObject declaration.

#ifdef _DEBUG

    void Dump( CDumpContext& dc ) const;

#endif Add the source code from Listing 8.11 to the MyObj.cpp source code file. The implementation of the function is also bracketed by #ifdef and #endif statements to remove the function for release builds.

TYPE: Listing 8.11. Adding the implementation of Dump to CMyObject.

#ifdef _DEBUG

void CMyObject::Dump( CDumpContext& dc ) const

{

    CObject::Dump( dc );

    dc << m_szName;

}

#endif The Dump function in Listing 8.11 calls the base class version of Dump first. This step is recommended to get a consistent output in the debug window. After calling CObject::Dump, member data contained in the class is sent to the dump context using the insertion operator, <<, just as if the data was sent to cout.

Summary

In this hour, you looked at how messages are handled by a program written for Windows, and you wrote a sample program that handles and displays some commonly used mouse event messages. You also looked at the CObject and CWnd base classes and learned how to add diagnostic features to your classes.

Q&A

Q I want to have runtime class identification support for my class, and I also want to be able to create objects dynamically. I tried using the DECLARE_DYNAMIC and DECLARE_DYNCREATE macros together in my header file and the IMPLEMENT_DYNAMIC and IMPLEMENT_DYNCREATE macros in my source file, but I got lots of errors. What happened?

A The macros are cumulative; The xxx_DYNCREATE macros also include work done by the xxx_DYNAMIC macros. The xxx_SERIAL macros also include xxx_DYNCREATE. You must use only one set of macros for your application.

Q Why does the MouseTst program go to the trouble of invalidating part of the view, then updating the window in OnDraw? Wouldn't it be easier to just draw directly on the screen when a mouse click is received?

A When the MouseTst window is overlapped by another window then uncovered, the view must redraw itself; this code will be located in OnDraw. It's much easier to use this code in the general case to update the display rather than try to draw the output in multiple places in the source code.

Workshop

The Workshop is designed to help you anticipate possible questions, review what you've learned, and begin thinking ahead to putting your knowledge into practice. The answers to the quiz are in Appendix B, "Quiz Answers."

Quiz

1. What is the default window procedure?

2. Why are messages used to pass information in Windows programs?

3. How is an application notified that the mouse is passing over one of its windows?

4. What is a message map used for?

5. What is the base class for most MFC classes?

6. What is the CObject::Dump function used for?

7. What is the difference between the ASSERT and VERIFY macros?

8. What message is sent to an application when the user presses the primary mouse button?

9. How can you determine which source code lines in a message map are reserved for use by ClassWizard?

Exercises

1. Modify the MouseTst program to display the current mouse position as the mouse is moved over the view.

2. Add an ASSERT macro to ensure that pObj is not NULL after it is created in Runtime.cpp.


Previous chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.