Messages are at the heart of every Windows program. Even the 50-line MFC program in Section 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 section, you will learn
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.
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.
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.
The Document/View architecture.
You will learn more about the Document/View architecture in Section 9, "The Document/View Architecture." For now, just be aware that four main "super classes" are used in a Document/View application:
The opening screen for MFC AppWizard.
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:
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:
There are two reasons why messages are used so heavily in Windows programs:
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.
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.
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.
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.
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.
| 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.
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.
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.
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:
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.
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.
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.
Figure 8.4 shows the MouseTst program after a mouse button has been clicked.
The MouseTst program displaying a mouse event.
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.
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 Section 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 Section 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.
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:
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.
Four different levels of support are offered by CObject to its derived classes:
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.
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.
#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.
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.
#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:
2. Change the current directory to the project directory.
3. Type Debug\Runtime in the DOS window. The program executes and outputs FooBar.
Three macros are commonly used in an MFC program:
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.
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.
#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.
#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.
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.
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?
2. Add an ASSERT macro to ensure that pObj is not NULL after it is created in Runtime.cpp.
Download Sample Programs - MouseTst.zip , Runtime.zip
|
|
|
|