Your First MFC Application
It's time to build your first MFC application. And what better place to start than with one that displays "Hello, MFC" in a window? Based on the classic "Hello, world" program immortalized in Brian Kernighan and Dennis Ritchie's The C Programming Language (1988, Prentice-Hall), this very minimal program, which I'll call Hello, demonstrates the fundamental principles involved in using MFC to write a Windows application. Among other things, you'll get a close-up look at MFC's CWinApp and CFrameWnd classes and see firsthand how classes are derived from them and plugged into the application. You'll also learn about the all-important CPaintDC class, which serves as the conduit through which text and graphics are drawn in a window in response to WM_PAINT messages. Finally, you'll be introduced to message mapping, the mechanism MFC uses to correlate the messages your application receives with the member functions you provide to handle those messages.
Figure 1-3 lists the source code for Hello. Hello.h contains the declarations for two derived classes. Hello.cpp contains the implementations of those classes. Among C++ programmers, it's traditional to put class definitions in .h files and source code in .cpp files. We'll honor that tradition here and throughout the rest of this book. For large applications containing tens or perhaps hundreds of classes, it's also beneficial to put class declarations and implementations in separate source code files. That's overkill for the programs in the first few chapters of this book, but later on, when we begin working with documents and views, we'll give each class its own .h and .cpp files. On the CD in the back of the book, in the folder named Chap01, you'll find a folder with copies of Hello.h and Hello.cpp as well as a folder containing a copy of the compiled executable (Hello.exe).
Figure 1-3. The Hello application.
Hello.hclass CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance ();
};
class CMainWindow : public CFrameWnd
{
public:
CMainWindow ();
protected:
afx_msg void OnPaint ();
DECLARE_MESSAGE_MAP ()
};
Hello.cpp#include <afxwin.h>
#include "Hello.h"
CMyApp myApp;
/////////////////////////////////////////////////////////////////////////
// CMyApp member functions
BOOL CMyApp::InitInstance ()
{
m_pMainWnd = new CMainWindow;
m_pMainWnd->ShowWindow (m_nCmdShow);
m_pMainWnd->UpdateWindow ();
return TRUE;
}
/////////////////////////////////////////////////////////////////////////
// CMainWindow message map and member functions
BEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd)
ON_WM_PAINT ()
END_MESSAGE_MAP ()
CMainWindow::CMainWindow ()
{
Create (NULL, _T ("The Hello Application"));
}
void CMainWindow::OnPaint ()
{
CPaintDC dc (this);
CRect rect;
GetClientRect (&rect);
dc.DrawText (_T ("Hello, MFC"), -1, &rect,
DT_SINGLELINE ¦ DT_CENTER ¦ DT_VCENTER);
}
Figure 1-4 shows the output from Hello. When you run the application, notice that the window is entirely functional; you can move it, resize it, minimize it, maximize it, and close it. And when the window is resized, "Hello, MFC" is redrawn in the center of the window.
Most of Hello's functionality comes from Windows. Windows, for example, draws the exterior, or nonclient area, of the window: the title bar, the buttons on the title bar, and the window's border. It's your responsibility to create the window and process WM_PAINT messages indicating that all or part of the window's interior, or client area, needs updating. Let's examine the source code to see how Hello works.
Figure 1-4. The Hello window.
The Application Object
The heart of an MFC application is an application object based on the CWinApp class. CWinApp provides the message loop that retrieves messages and dispatches them to the application's window. It also includes key virtual functions that can be overridden to customize the application's behavior. CWinApp and other MFC classes are brought into your application when you include the header file Afxwin.h. An MFC application can have one—and only one—application object, and that object must be declared with global scope so that it will be instantiated in memory at the very outset of the program.
Hello's application class is named CMyApp. It is instantiated in Hello.cpp with the statement
CMyApp myApp;
CMyApp's class declaration appears in Hello.h:
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance ();
};
CMyApp declares no data members and overrides just one function inherited from CWinApp. InitInstance is called early in the application's lifetime, right after the application starts running but before the window is created. In fact, unless InitInstance creates a window, the application doesn't have a window. That's why even a minimal MFC application must derive a class from CWinApp and override CWinApp::InitInstance.
The InitInstance Function
CWinApp::InitInstance is a virtual function whose default implementation contains just one statement:
return TRUE;
The purpose of InitInstance is to provide the application with the opportunity to initialize itself. The value returned by InitInstance determines what the framework does next. Returning FALSE from InitInstance shuts down the application. If initialization goes as planned, InitInstance should return TRUE in order to allow the program to proceed. InitInstance is the perfect place to perform initializations that need to be done each time the program starts. At the very least, this means creating the window that will represent the application on the screen.
CMyApp's implementation of InitInstance, which appears in Hello.cpp, creates the Hello window by instantiating Hello's CMainWindow class. The statement
m_pMainWnd = new CMainWindow;
constructs a CMainWindow object and copies its address to the application object's m_pMainWnd data member. After the window is created, InitInstance displays it—remember, a window is not initially visible unless it is created with a WS_VISIBLE attribute—by calling ShowWindow and UpdateWindow through the CMainWindow pointer:
m_pMainWnd->ShowWindow (m_nCmdShow);
m_pMainWnd->UpdateWindow ();
ShowWindow and UpdateWindow are CWnd member functions common to all window objects, including objects of the CFrameWnd class from which CMainWindow is derived. These functions are little more than wrappers around the API functions of the same name. To call a regular Windows API function from an MFC program, make it a practice to preface the function name with the global scope resolution operator ::, as in
::UpdateWindow (hwnd);
This notation will ensure that the API function is called even if the object that makes the call has a member function with the same name. In the remainder of this book, Windows API functions will be prefaced with :: to distinguish them from MFC member functions.
ShowWindow accepts just one parameter: an integer that specifies whether the window should initially be shown minimized, maximized, or neither minimized nor maximized. In accordance with Windows programming protocol, Hello passes ShowWindow the value stored in the application object's m_nCmdShow variable, which holds the nCmdShow parameter passed to WinMain. The m_nCmdShow value is usually SW_SHOWNORMAL, indicating that the window should be displayed in its normal unmaximized and unminimized state. However, depending on how the user starts an application, Windows will occasionally slip in a value such as SW_SHOWMAXIMIZED or SW_SHOWMINIMIZED. Unless there is a specific reason for it to do otherwise, InitInstance should always pass the m_nCmdShow variable instead of a hardcoded SW_ value to ShowWindow.
UpdateWindow completes the job that ShowWindow started by forcing an immediate repaint. Its work done, InitInstance returns TRUE to allow the application to proceed.
Other CWinApp Overridables
InitInstance is just one of several virtual CWinApp member functions you can override to customize the behavior of the application object. Look up the CWinApp overridables in your MFC documentation and you'll see a list of more than a dozen others with names such as WinHelp and ProcessWndProcException. Many of these functions are seldom overridden, but they're handy to have around nonetheless. For example, you can use ExitInstance to clean up when an application terminates. If you use InitInstance to allocate memory or other resources, ExitInstance is the perfect place to free those resources. The default implementation of ExitInstance performs some routine cleanup chores required by the framework, so you should be sure to call the base class version if you've overridden ExitInstance. Ultimately, the value returned by ExitInstance is the exit code returned by WinMain.
Other interesting CWinApp overridables include OnIdle, Run, and PreTranslateMessage. OnIdle is handy for performing background processing chores such as garbage collection. Because OnIdle is called when an application is "idle"—that is, when there are no messages waiting to be processed—it provides an excellent mechanism for performing low-priority background tasks without spawning a separate thread of execution. OnIdle is discussed at length in Chapter 14. You can override Run to customize the message loop, replacing it with a message loop of your own. If you just want to perform some specialized preprocessing on certain messages before they are dispatched, you can override PreTranslateMessage and save yourself the trouble of writing a whole new message loop.
How MFC Uses the Application Object
To someone who has never seen an MFC application's source code, one of the more remarkable aspects of Hello will be that it contains no executable code outside of the classes it defines. It has no main or WinMain function, for example; the only statement in the entire program that has global scope is the statement that instantiates the application object. So what actually starts the program running, and when does the application object come into the picture?
The best way to understand what goes on under the hood is to look at the framework's source code. One of the source code files provided with MFC—Winmain.cpp—contains an AfxWinMain function that is MFC's equivalent of WinMain. (That's right: when you purchase Visual C++, you get the source code for MFC, too.) AfxWinMain makes extensive use of the application object, which is why the application object must be declared globally. Global variables and objects are created before any code is executed, and the application object must be extant in memory before AfxWinMain starts.
Right after starting, AfxWinMain calls a function named AfxWinInit to initialize the framework and copy hInstance, nCmdShow, and other AfxWinMain function parameters to data members of the application object. Then it calls InitApplication and InitInstance. In 16-bit versions of MFC, InitApplication is called only if the hPrevInstance parameter passed to AfxWinMain is NULL, indicating that this is the only instance of the application currently running. In the Win32 environment, hPrevInstance is always NULL, so the framework doesn't bother to check it. A 32-bit application could just as easily use InitApplication to initialize itself as InitInstance, but InitApplication is provided for compatibility with previous versions of MFC and should not be used in 32-bit Windows applications. If AfxWinInit, InitApplication, or InitInstance returns 0, AfxWinMain terminates instead of proceeding further and the application is shut down.
Only if all of the aforementioned functions return nonzero values does AfxWinMain perform the next critical step. The statement
pThread->Run();
calls the application object's Run function, which executes the message loop and begins pumping messages to the application's window. The message loop repeats until a WM_QUIT message is retrieved from the message queue, at which point Run
breaks out of the loop, calls ExitInstance, and returns to AfxWinMain. After doing some last-minute cleaning up, AfxWinMain executes a return to end the application.