Chapter 4

Constructing an MFC Program from Scratch


AppWizard is great for getting an application started quickly. However, because AppWizard does so much of the work for you, you never get to learn many of the details of MFC programming. Knowing how MFC really works, not only enables you to forgo AppWizard when you'd rather do the work yourself, but it also helps you understand AppWizard programs better so you can customize them more effectively. For these reasons, some of this book is dedicated to writing MFC programs without the help of AppWizard. This chapter is your first step toward that goal.

Writing the Minimum MFC Program

In Chapter 1, "An Introduction to MFC," you get a quick overview of the many classes that you can use to create your MFC-based applications. From the brief descriptions included in that chapter, you already might have figured out that an MFC program must have at least two classes: the application class and the window class. To get a window up on the screen, you need do very little with these two classes. In fact, you can create a minimal MFC program in fewer lines of code than you'd believe. The following list outlines the steps you must take to create the smallest, functional MFC program:

1. Create an application class derived from CWinApp.

2. Create a window class derived from CFrameWnd.

3. Instantiate a global instance of your application object.

4. Instantiate your main window object in the application object's InitInstance() function.

5. Create the window element (the Windows window) associated with the window object by calling Create() from the window object's constructor.

That's all there is to getting a window up on the screen. In the following sections, you look at these steps in greater detail, examining the actual code that performs the listed tasks.

Creating the Application Class

As you learned in the previous section, the first step in writing an MFC program is to create your application class, in which you must override the InitInstance() function. Listing 4.1 shows the header file for your first handwritten application class, called CMFCApp1.

Listing 4.1 MFCAPP1.H The Header File for the CMFCApp1 Class

///////////////////////////////////////////////////////////
// MFCAPP1.H: Header file for the CMFCApp1 class, which
//            represents the application object.
///////////////////////////////////////////////////////////
class CMFCApp1 : public CWinApp
{
public:
    CMFCApp1();
    BOOL InitInstance();
};

In the first line of the preceding code (not counting the comments), you can see that the CMFCApp1 class is derived from MFC's CWinApp class. The CMFCApp1 class has a constructor and overrides the InitInstance() function, which is the function MFC calls to create the application's window object. Because the base class's InitInstance() does nothing but return a value of TRUE, if you want your application to have a window, you must override InitInstance() in your application class.

To see how the application class actually creates its main window object, take a look at Listing 4.2, which is the CMFCApp1 class's implementation file.

Listing 4.2 MFCAPP1.CPP The Implementation File for the CMFCApp1 Class

///////////////////////////////////////////////////////////
// MFCAPP1.CPP: Implementation file for the CMFCApp1 class,
//              which represents the application object.
///////////////////////////////////////////////////////////
#include <afxwin.h>
#include "mfcapp1.h"
#include "mainfrm.h"
CMFCApp1 MFCApp1;
CMFCApp1::CMFCApp1()
{
}
BOOL CMFCApp1::InitInstance()
{
    m_pMainWnd = new CMainFrame();
    m_pMainWnd->ShowWindow(m_nCmdShow);
    return TRUE;
}

The first thing to notice here is the line

#include <afxwin.h>

near the top of the file. AFXWIN.H is the header file for the MFC classes, so it must be included in any file that references those classes. Failure to include AFXWIN.H in an MFC program causes the compiler to crank out a long list of error messages, because it won't recognize the MFC classes you're using.

After the include statements, you can see where the program instantiates its global application object with the following line:

CMFCApp1 MFCApp1;

The application object must be global to ensure that it is the first object instantiated in the program.

In the CMFCApp1 class, the constructor does nothing, although you can add initialization code to the constructor as needed. In this first program, it's the application class's InitInstance() function that's most important. MFC calls InitInstance() once, at the beginning of the application's run. InitInstance()'s main task is to instantiate the application's main window object, which the CMFCApp1 class does with the following line:

m_pMainWnd = new CMainFrame();

This line creates a new CMainFrame() object on the heap, calling the CMainFrame() class's constructor, where, as you'll soon see, the class creates the main window element. A pointer to the new window object gets stored in m_pMainWnd, which is a data member of the application class. You must be sure to initialize m_pMainWnd properly, as it's through this pointer that the application class maintains contact with its main window.

After instantiating the window object, the window element must usually be displayed. This is done by calling the window object's ShowWindow() function, like this:

m_pMainWnd->ShowWindow(m_nCmdShow);

ShowWindow()'s single argument indicates how the window should be displayed initially. You need only pass along the application object's m_nCmdShow data member, which contains the value of the nCmdShow parameter that Windows passed to WinMain().

When an application starts up, there are several ways that its window can be initially displayed. These different display styles are represented by a set of constants defined by Windows, including SW_HIDE, SW_MINIMIZE, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED, and SW_SHOWNORMAL, among others. Luckily, with MFC, you don't need to worry about these different styles unless you want to do something special with a window. Just passing m_nCmdShow to ShowWindow() as the display style shows the window in the default manner.

Creating the Frame-Window Class

Now that you know how to create your application class, which is responsible for instantiating your main window, it'd be nice to see how the window class works. Listing 4.3 is the header file for the CMainFrame class, which represents the CMFCApp1 class's main window.

Listing 4.3 MAINFRM.H The Header File for the CMainFrame Class

///////////////////////////////////////////////////////////
// MAINFRM.H: Header file for the CMainFrame class, which
//            represents the application's main window.
///////////////////////////////////////////////////////////
class CMainFrame : public CFrameWnd
{
public:
    CMainFrame();
    ~CMainFrame();
};

As you can see from the class's declaration, CMainFrame couldn't get much simpler, containing only a constructor and a destructor. Of course, CMainFrame is backed with all the power of MFC's CFrameWnd class, from which it's derived. Although the CMainFrame class's header file is interestingly stark, it doesn't provide too many clues about how the application's main window is created. That information lies in the class's implementation file, which is shown in Listing 4.4.

Listing 4.4 MAINFRM.CPP The Implementation File for the CMainFrame Class

///////////////////////////////////////////////////////////
// MAINFRM.CPP: Implementation file for the CMainFrame
//              class, which represents the application's
//              main window.
///////////////////////////////////////////////////////////
#include <afxwin.h>
#include "mainfrm.h"
CMainFrame::CMainFrame()
{
    Create(NULL, "MFC App1");
}
CMainFrame::~CMainFrame()
{
}

If you turn your attention to the class's constructor, you can see that the application's window element is created by a simple call to the window class's Create() member function, like this:

Create(NULL, "MFC App1");

Actually, this call to Create() is deceptively simple. Create() really requires eight arguments, although all but the first two have default values. Create()'s prototype looks like Listing 4.5.

Listing 4.5 LST04_05.CPP The Create() Function's Prototype

BOOL Create(LPCTSTR lpszClassName,
            LPCTSTR lpszWindowName,
            DWORD dwStyle = WS_OVERLAPPEDWINDOW,
            const RECT& rect = rectDefault,
            CWnd* pParentWnd = NULL,        // != NULL for popups
            LPCTSTR lpszMenuName = NULL,
            DWORD dwExStyle = 0,
            CCreateContext* pContext = NULL);

Create()'s arguments are a pointer to the following:

If you use NULL for the class name, MFC uses its default window class. As for the title string, this is the title that appears in the window's title bar. If you want to create different types of windows, you can use different style flags for dwStyle. For example, the line

Create(NULL, "MFC App1", WS_OVERLAPPED | WS_BORDER | WS_SYSMENU);

creates a window with a system menu, a Close button, and a thin border. Because such a window is missing the Minimize and Maximize buttons, as well as the usual thick border, the user cannot change the size of the window but can only move or close it. The styles you can use include WS_BORDER, WS_CAPTION, WS_CHILD, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_THICKFRAME, and more. Please refer to your Windows programming documentation for more information.

By changing the default values of the fourth argument, you can position and size a window. For example, the line

Create(NULL, "MFC App1", WS_OVERLAPPEDWINDOW, CRect(0, 0, 100, 100));

creates a 100 [ts] 100 window located in the desktop's upper-left corner.

Your main window doesn't have a parent window, so the fifth argument should be NULL. As for the sixth argument, you'll learn about menus in Chapter 6, "Using Menus." In most cases, you won't need to worry about the last two arguments, dwExStyle and pContext.

Compiling an MFC Program

Creating your application and window classes is all well and good. However, because you haven't used AppWizard to start your new project, you have to manually create a project for your files so that Developer Studio can compile and link the files into your final executable application. In this section, you create a new project for the MFC App1 application that you just examined. To create a project for the application, perform the easy steps that follow.

The complete source code and executable file for the MFC App1 application can be found in the CHAP04\MFCAPP1 directory of this book's CD-ROM.

  1. Select File, New from Developer Studio's menu bar. The New property sheet appears.
  2. If necessary, select the Projects tab. The Projects page appears (Figure 4.1).

    FIG. 4.1

    Selecting Projects brings up the Projects page.
  3. Select Win32 Application in the left hand pane, type mfcapp1 in the Project Name box, set the Location box to the folder in which you want to create the project, and click the OK button. Developer Studio creates the new project workspace.
  4. Copy the MFCAPP1.H, MFCAPP1.CPP, MAINFRM.H, and MAINFRM.CPP files from the CHAP04\MFCAPP1 folder on this book's CD-ROM into the project folder that you created in Step 3. (Of course, if you were actually creating this application from scratch, you'd have to create the application's source code files by typing them into the editor.)
  5. Select Project, Add to Project, Files from Developer Studio's menu bar. The Insert Files into Project dialog box appears, as shown in Figure 4.2.

    FIG. 4.2

    When you select Insert, Files into Project, the Insert Files into Project dialog box appears.
  6. Click on MFCAPP1.CPP and MAINFRM.CPP to highlight them (hold down the Ctrl key), and then click the OK button. Developer Studio adds these two source code files to the project.
  7. Select the Project, Settings command from the menu bar. The Project Settings property sheet appears, as shown in Figure 4.3.

    FIG. 4.3

    If you select Project, Settings, the Project Settings property sheet appears.
  8. Change the Microsoft Foundation Classes box to Use MFC In A Shared DLL, and then click the OK button. Developer Studio adds MFC support to the project. (You can also select Use MFC In A Static Library if you want the MFC code to be linked into your application's executable file. This makes your executable file larger but eliminates the need to distribute the MFC DLLs with your application. In most cases, the shared DLL is preferable.)

At this point, you're ready to compile and link your new project. To do this, just click the Build button on the toolbar or select Build, Build from the menu bar. When the project has been compiled and linked, select Build, Execute to run the application. When you do, you see the main window screen. Your first handwritten MFC application might not be fancy, but it's up on the screen!

Responding to Windows Messages

To do something useful, your new MFC application must be able to respond to messages sent to its window. This is how a Windows application knows what the user and the system are doing. To respond to Windows messages, an MFC application must have a message map that tells MFC what functions handle what messages. In this section, you'll add message mapping to the MFC application.

Declaring a Message Map

Any class that has CCmdTarget as an ancestor can respond to Windows messages. These include application, frame-window, view-window, document, and control classes. Where you decide to intercept a message depends a great deal upon your program's design. However, as you discover in Chapter 3, "Documents and Views," the document object often gets to handle messages generated from the File menu, whereas the view object takes control of messages needed to edit and display the document's data.

In your new MFC application, you have no File menu to worry about. Moreover, you don't have a document or view class. So, as is typical in a case like this, you'll add message mapping to the frame window, represented by the CMainFrame class.

The first step in adding message mapping is to declare a message map in the class's header file. Placing the line

DECLARE_MESSAGE_MAP()

at the end of the class's declaration, just before the closing brace, takes care of this easy task. DECLARE_MESSAGE_MAP() is a macro defined by MFC. This macro takes care of all the details required to declare a message map for a class. You can look for DECLARE_MESSAGE_MAP()'s definition in MFC's source code if you want a better look, but you really don't need to know how it works. Just place the preceding code line in your class's declaration and whisper a quiet thank-you to Microsoft's programmers for making message maps so easy to declare.

Defining a Message Map

After you have declared your message map in your class's header, it's time to define the message map in your class's implementation file. Usually, programmers place the message map definition near the top of the file, right after the include lines. However, you can place it just about anywhere in the file as long as it's not nested inside a function or other structure.

What does a message map definition look like? The one that follows maps WM_LBUTTONDOWN messages to a message map function called OnLButtonDown():

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

The first line in the preceding code is a macro defined by MFC that takes care of initializing the message map definition. The macro's two arguments are the class for which you're defining the message map and that class's immediate base class. You need to tell MFC about the base class so that it can pass any unhandled messages on up the class hierarchy, where another message map might contain an entry for the message.

After the starting macro, you place the message map entries, which match messages with their message map functions. How you do this depends upon the type of message you're trying to map. For example, MFC has previously defined message map functions for all of the Windows system messages, such as WM_LBUTTONDOWN. To write a message map for such a message, you simply add an ON_ prefix to the message name and tack on the parentheses. So WM_LBUTTONDOWN becomes ON_WM_LBUTTONDOWN(); WM_MOUSEMOVE becomes ON_WM_MOUSEMOVE(); WM_KEYDOWN becomes ON_WM_KEYDOWN(), and so on. (Notice that you don't place semicolons after message map entries.)

To determine the matching message map function for the map entry, you throw away the _WM_ in the macro name and then spell the function name in uppercase and lowercase. For example, ON_WM_LBUTTONDOWN() becomes OnLButtonDown(); ON_WM_MOUSEMOVE() ends up as OnMouseMove(); and ON_WM_KEYDOWN() matches up with OnKeyDown().

There are several other types of macros that you can use to define message map entries. For example, as you'll learn in Chapter 6, "Using Menus," the ON_COMMAND() macro maps menu commands to their response functions, whereas the ON_UPDATE_COMMAND_UI() macro enables you to attach menu items to the functions that keep the menu items' appearance updated (checked, enabled, and so on).

See “Exploring the Menu Application,” (ch. 6)

Throughout this chapter, I refer to message response functions and message map functions. These types of functions are really exactly the same thing. Depending upon context, one term is sometimes more descriptive than the other. Microsoft sometimes refers to these functions as message handlers.

Writing Message Map Functions

Now that you have your message map declared and defined, you're missing only one important element: the message map functions themselves! In the example message map, there's only a single message map entry, which is ON_WM_LBUTTONDOWN(). As you now know, this is the macro for the WM_LBUTTONDOWN Windows message. In order to complete the message mapping, you must now write the matching OnLButtonDown() function.

First, you need to declare the message map functions in your class's header file. And here's where you hit your first stumbling block. How does MFC prototype the various message map functions? Here's a neat trick. You already know the name of the message map function you need, so type the name into your class declaration, place the blinking text cursor on the name, and press F1. (For this to work, you must have Visual C++'s online documentation accessible on your hard disk or CD-ROM.) Developer Studio displays the help topic for the selected function. Just highlight the function's prototype in the help window, copy it to the Clipboard, and then paste it into your class's declaration.

The OnLButtonDown() prototype looks like this:

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

The afx_msg part of the prototype does nothing more than mark the function as a message map function. You could leave the afx_msg off, and the function would still compile and execute just fine. However, Microsoft programming conventions suggest that you use afx_msg to distinguish message map functions from other types of member functions in your class.

Now, as long as you still have the function's prototype in the Clipboard, you might as well use it to create the function's definition in your class's implementation file. In the function's definition, you write the code that you want executed whenever the object receives the associated Windows message. In the case of your slowly growing MFC application, the OnLButtonDown() message response function simply displays a message box, indicating that the message was received and responded to by the frame window.

Exploring the MFC App2 Application

If you're a little confused, take a look at listings 4.6 and 4.7 for the CMainFrame class of the MFC App2 application, which adds message mapping to the MFC App1 application. These listings show the message maps in the context of the source code.

The complete source code and executable file for the MFC App2 application can be found in the CHAP04\MFCAPP2 directory of this book's CD-ROM.

Listing 4.6 MAINFRM.H The Header File for MFC App2's Frame-Window Class

///////////////////////////////////////////////////////////
// MAINFRM.H: Header file for the CMainFrame class, which
//            represents the application's main window.
///////////////////////////////////////////////////////////
class CMainFrame : public CFrameWnd
{
public:
    CMainFrame();
    ~CMainFrame();
    // Message map functions.
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
  
    DECLARE_MESSAGE_MAP()
};

 

Listing 4.7 MAINFRM.CPP The Implementation File of MFC App2's Frame-Window Class

///////////////////////////////////////////////////////////
// MAINFRM.CPP: Implementation file for the CMainFrame
//              class, which represents the application's
//              main window.
///////////////////////////////////////////////////////////
#include <afxwin.h>
#include "mainfrm.h"
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////
// CMainFrame: Construction and destruction.
///////////////////////////////////////////////////////////
CMainFrame::CMainFrame()
{
    Create(NULL, "MFC App2");
}
CMainFrame::~CMainFrame()
{
}
///////////////////////////////////////////////////////////
// Message map functions.
///////////////////////////////////////////////////////////
void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
{
    MessageBox("Got the click!", "MFC App2");
}

When you compile and run the MFC App2 program, you see the application's main window. The application doesn't look much different from MFC App1, does it? Click inside the window, however, and a message box pops up (Figure 4.4) to let you know that your message map is working.


FIG. 4.4

MFC App2 uses message mapping to respond to mouse clicks.

Notice that the call to MessageBox() in the OnLButtonDown() function looks very different from the MessageBox() call that you might be used to seeing in conventional Windows programs. The MessageBox() function being called in OnLButtonDown() is a member function of the CMainFrame class (inherited from CWnd), rather than part of the Windows API. MFC's MessageBox() function requires no window handle (after all, the window class knows its own handle) and can get by with nothing but the text that should appear in the message box. Another MFC advantage!