Programming Windows with MFC, Second Edition
Let's put what we've learned so far to work by building an application that uses menus and accelerators and also uses MFC's UI update mechanism to keep menu items in sync with data members whose values reflect internal application states. For the first time, we'll use AppWizard to generate the initial source code for the application and ClassWizard to write message handlers. We'll also use ClassWizard to write command handlers and update handlers for the application's menu items. AppWizard and ClassWizard are MFC code generators that conserve development time by reducing the amount of code you have to write.
The application, which is named Shapes, is shown in Figure 4-2. Shapes displays a polygon in the center of a frame window. You can change the polygon's shape by selecting a command from the Shape menu (Circle, Triangle, or Square) or pressing the corresponding keyboard accelerator key (F7, F8, or F9).
Figure 4-2. The Shapes window.
The program's source code is reproduced in Figure 4-3. When you write an application using the wizards, however, the source code doesn't tell the whole story; it's just as important to understand how the source code was created, and by whom. Therefore, I'll begin with a step-by-step description of how to create the initial source code for Shapes with the MFC AppWizard. Then we'll pause to examine what AppWizard has wrought.
Figure 4-3. The Shapes program.
Shapes.h
// Shapes.h : main header file for the SHAPES application // #if !defined(AFX_SHAPES_H__437C8B37_5C45_11D2_8E53_006008A82731__INCLUDED_) #define AFX_SHAPES_H__437C8B37_5C45_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #ifndef __AFXWIN_H__ #error include `stdafx.h' before including this file for PCH #endif #include "resource.h" // main symbols /////////////////////////////////////////////////////////////////////////// // CShapesApp: // See Shapes.cpp for the implementation of this class // class CShapesApp : public CWinApp { public: CShapesApp(); // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: //AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined(AFX_SHAPES_H__437C8B37_5C45_11D2_8E53_006008A82731__INCLUDED_) |
Shapes.cpp
// Shapes.cpp : Defines the class behaviors for the application. // #include "stdafx.h" "#include "Shapes.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CShapesApp BEGIN_MESSAGE_MAP(CShapesApp, CWinApp) //AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CShapesApp construction CShapesApp::CShapesApp() { } /////////////////////////////////////////////////////////////////////////// // The one and only CShapesApp object CShapesApp theApp; /////////////////////////////////////////////////////////////////////////// // CShapesApp initialization BOOL CShapesApp::InitInstance() { // Standard initialization // Change the registry key under which our settings are stored. SetRegistryKey(_T("Local AppWizard-Generated Applications")); CMainFrame* pFrame = new CMainFrame; m_pMainWnd = pFrame; // create and load the frame with its resources pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW ¦ FWS_ADDTOTITLE, NULL, NULL); pFrame->ShowWindow(SW_SHOW); pFrame->UpdateWindow(); return TRUE; } /////////////////////////////////////////////////////////////////////////// // CShapesApp message handlers /////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About class CAboutDlg : public CDialog { public: CAboutDlg(); // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation protected: //AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //AFX_MSG_MAP END_MESSAGE_MAP() // App command to run the dialog void CShapesApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); } /////////////////////////////////////////////////////////////////////////// // CShapesApp message handlers |
MainFrm.h
// MainFrm.h : interface of the CMainFrame class // //////////////////////////////////////////////////////////////////////////// #if !defined(AFX_MAINFRM_H__437C8B3B_5C45_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MAINFRM_H__437C8B3B_5C45_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "ChildView.h" class CMainFrame : public CFrameWnd { public: CMainFrame(); protected: DECLARE_DYNAMIC(CMainFrame) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif CChildView m_wndView; // Generated message map functions protected: //AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined(AFX_MAINFRM_H__437C8B3B_5C45_11D2_8E53_006008A82731__INCLUDED_) |
MainFrm.cpp
// MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "Shapes.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { } CMainFrame::~CMainFrame() { } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; cs.dwExStyle &= ~WS_EX_CLIENTEDGE; cs.lpszClass = AfxRegisterWndClass(0); return TRUE; } /////////////////////////////////////////////////////////////////////////// // CMainFrame diagnostics #ifdef _DEBUG void CMainFrame::AssertValid() const { CFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CFrameWnd::Dump(dc); } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////// // CMainFrame message handlers void CMainFrame::OnSetFocus(CWnd* pOldWnd) { // forward focus to the view window m_wndView.SetFocus(); } BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // let the view have first crack at the command if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise, do default handling return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL)) { TRACE0("Failed to create view window\n"); return -1; } return 0; } |
ChildView.h
// ChildView.h : interface of the CChildView class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_CHILDVIEW_H__437C8B3D_5C45_11D2_8E53_006008A82731__INCLUDED_) #define AFX_CHILDVIEW_H__437C8B3D_5C45_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 /////////////////////////////////////////////////////////////////////////// // CChildView window class CChildView : public CWnd { // Construction public: CChildView(); // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: virtual ~CChildView(); // Generated message map functions protected: int m_nShape; //AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined(AFX_CHILDVIEW_H__437C8B3D_5C45_11D2_8E53_006008A82731__INCLUDED_) |
ChildView.cpp
// ChildView.cpp : implementation of the CChildView class // #include "stdafx.h" #include "Shapes.h" #include "ChildView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CChildView CChildView::CChildView() { m_nShape = 1; // Triangle } CChildView::~CChildView() { } BEGIN_MESSAGE_MAP(CChildView,CWnd ) //AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CChildView message handlers BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) { if (!CWnd::PreCreateWindow(cs)) return FALSE; cs.dwExStyle ¦= WS_EX_CLIENTEDGE; cs.style &= ~WS_BORDER; cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW¦CS_VREDRAW¦CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW+1), NULL); return TRUE; } void CChildView::OnPaint() { CPoint points[3]; CPaintDC dc(this); CRect rcClient; GetClientRect (&rcClient); int cx = rcClient.Width () / 2; int cy = rcClient.Height () / 2; CRect rcShape (cx - 45, cy - 45, cx + 45, cy + 45); CBrush brush (RGB (255, 0, 0)); CBrush* pOldBrush = dc.SelectObject (&brush); switch (m_nShape) { case 0: // Circle dc.Ellipse (rcShape); break; case 1: // Triangle points[0].x = cx - 45; points[0].y = cy + 45; points[1].x = cx; points[1].y = cy - 45; points[2].x = cx + 45; points[2].y = cy + 45; dc.Polygon (points, 3); break; case 2: // Square dc.Rectangle (rcShape); break; } dc.SelectObject (pOldBrush); } void CChildView::OnShapeCircle() { m_nShape = 0; Invalidate (); } void CChildView::OnShapeTriangle() { m_nShape = 1; Invalidate (); } void CChildView::OnShapeSquare() { m_nShape = 2; Invalidate (); } void CChildView::OnUpdateShapeCircle(CCmdUI* pCmdUI) { pCmdUI->SetCheck (m_nShape == 0); } void CChildView::OnUpdateShapeTriangle(CCmdUI* pCmdUI) { pCmdUI->SetCheck (m_nShape == 1); } void CChildView::OnUpdateShapeSquare(CCmdUI* pCmdUI) { pCmdUI->SetCheck (m_nShape == 2); } |
Resource.h
// // Microsoft Developer Studio generated include file. // Used by Shapes.rc // #define IDD_ABOUTBOX 100 #define IDR_MAINFRAME 128 #define IDR_SHAPESTYPE 129 #define ID_SHAPE_CIRCLE 32771 #define ID_SHAPE_TRIANGLE 32772 #define ID_SHAPE_SQUARE 32773 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 130 #define _APS_NEXT_COMMAND_VALUE 32774 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif |
Shapes.rc
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS /////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" /////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS /////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) ¦¦ defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "#define _AFX_NO_SPLITTER_RESOURCES\r\n" "#define _AFX_NO_OLE_RESOURCES\r\n" "#define _AFX_NO_TRACKER_RESOURCES\r\n" "#define _AFX_NO_PROPERTY_RESOURCES\r\n" "\r\n" "#if !defined(AFX_RESOURCE_DLL) ¦¦ defined(AFX_TARG_ENU)\r\n" "#ifdef _WIN32\r\n" "LANGUAGE 9, 1\r\n" "#pragma code_page(1252)\r\n" "#endif //_WIN32\r\n" "#include ""res\\Shapes.rc2" " // non-Microsoft Visual C++ edited resources\r\n" "#include ""afxres.rc"" // Standard components\r\n" "#endif\r\n" "\0" END #endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDR_MAINFRAME ICON DISCARDABLE "res\\Shapes.ico" /////////////////////////////////////////////////////////////////////////// // // Menu // IDR_MAINFRAME MENU PRELOAD DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_APP_EXIT END POPUP "&Shape" BEGIN MENUITEM "&Circle\tF7", ID_SHAPE_CIRCLE MENUITEM "&Triangle\tF8", ID_SHAPE_TRIANGLE MENUITEM "&Square\tF9", ID_SHAPE_SQUARE END END /////////////////////////////////////////////////////////////////////////// // // Accelerator // IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE BEGIN VK_F7, ID_SHAPE_CIRCLE, VIRTKEY, NOINVERT VK_F8, ID_SHAPE_TRIANGLE, VIRTKEY, NOINVERT VK_F9, ID_SHAPE_SQUARE, VIRTKEY, NOINVERT END /////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 235, 55 STYLE DS_MODALFRAME ¦ WS_POPUP ¦ WS_CAPTION ¦ WS_SYSMENU CAPTION "About Shapes" FONT 8, "MS Sans Serif" BEGIN ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 LTEXT "Shapes Version 1.0",IDC_STATIC,40,10,119,8,SS_NOPREFIX LTEXT "Copyright (C) 1998",IDC_STATIC,40,25,119,8 DEFPUSHBUTTON "OK",IDOK,178,7,50,14,WS_GROUP END #ifndef _MAC /////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "Shapes MFC Application\0" VALUE "FileVersion", "1, 0, 0, 1\0" VALUE "InternalName", "Shapes\0" VALUE "LegalCopyright", "Copyright (C) 1998\0" VALUE "LegalTrademarks", "\0" VALUE "OriginalFilename", "Shapes.EXE\0" VALUE "ProductName", "Shapes Application\0" VALUE "ProductVersion", "1, 0, 0, 1\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // !_MAC /////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_ABOUTBOX, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 228 TOPMARGIN, 7 BOTTOMMARGIN, 48 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE PRELOAD DISCARDABLE BEGIN IDR_MAINFRAME "Shapes" END STRINGTABLE PRELOAD DISCARDABLE BEGIN AFX_IDS_APP_TITLE "Shapes" AFX_IDS_IDLEMESSAGE "Ready" END STRINGTABLE DISCARDABLE BEGIN ID_INDICATOR_EXT "EXT" ID_INDICATOR_CAPS "CAP" ID_INDICATOR_NUM "NUM" ID_INDICATOR_SCRL "SCRL" ID_INDICATOR_OVR "OVR" ID_INDICATOR_REC "REC" END STRINGTABLE DISCARDABLE BEGIN ID_APP_ABOUT "Display program information, version number and copyright\nAbout" ID_APP_EXIT "Quit the application; prompts to save documents\nExit" END STRINGTABLE DISCARDABLE BEGIN ID_NEXT_PANE "Switch to the next window pane\nNext Pane" ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" END STRINGTABLE DISCARDABLE BEGIN ID_WINDOW_SPLIT "Split the active window into panes\nSplit" END STRINGTABLE DISCARDABLE BEGIN ID_EDIT_CLEAR "Erase the selection\nErase" ID_EDIT_CLEAR_ALL "Erase everything\nErase All" ID_EDIT_COPY "Copy the selection and put it on the Clipboard\nCopy" ID_EDIT_CUT "Cut the selection and put it on the Clipboard\nCut" ID_EDIT_FIND "Find the specified text\nFind" ID_EDIT_PASTE "Insert Clipboard contents\nPaste" ID_EDIT_REPEAT "Repeat the last action\nRepeat" ID_EDIT_REPLACE "Replace specific text with different text\nReplace" ID_EDIT_SELECT_ALL "Select the entire document\nSelect All" ID_EDIT_UNDO "Undo the last action\nUndo" ID_EDIT_REDO "Redo the previously undone action\nRedo" END STRINGTABLE DISCARDABLE BEGIN AFX_IDS_SCSIZE "Change the window size" AFX_IDS_SCMOVE "Change the window position" AFX_IDS_SCMINIMIZE "Reduce the window to an icon" AFX_IDS_SCMAXIMIZE "Enlarge the window to full size" AFX_IDS_SCNEXTWINDOW "Switch to the next document window" AFX_IDS_SCPREVWINDOW "Switch to the previous document window" AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" END STRINGTABLE DISCARDABLE BEGIN AFX_IDS_SCRESTORE "Restore the window to normal size" AFX_IDS_SCTASKLIST "Activate Task List" END #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // #define _AFX_NO_SPLITTER_RESOURCES #define _AFX_NO_OLE_RESOURCES #define _AFX_NO_TRACKER_RESOURCES #define _AFX_NO_PROPERTY_RESOURCES #if !defined(AFX_RESOURCE_DLL) ¦¦ defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE 9, 1 #pragma code_page(1252) #endif //_WIN32 #include "res\Shapes.rc2" // non-Microsoft Visual C++ edited resources #include "afxres.rc" // Standard components #endif /////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED |
Running the MFC AppWizard
Shapes' source code is a combination of wizard-generated code and handwritten code. The first step in creating it is to run the MFC AppWizard. Here's how to get started:
- Create a new Visual C++ project named Shapes. Select MFC AppWizard (Exe) as the application type, as shown in Figure 4-4. This will start AppWizard, which will ask you a series of questions before generating the project.
- In AppWizard's Step 1 dialog box, select Single Document as the application type and uncheck the box labeled Document/View Architecture Support, as shown in Figure 4-5. The latter is a new option in Visual C++ 6; it prevents AppWizard from generating an MFC document/view application. The meaning of Single Document is discussed in Chapter 8.
- In AppWizard's Step 2 dialog box, accept the defaults.
- In AppWizard's Step 3 dialog box, uncheck the ActiveX Controls box. When checked, this option adds infrastructure that allows MFC windows to host ActiveX controls—a subject that we'll cover in Chapter 21.
- In AppWizard's Step 4 dialog box, uncheck the Docking Toolbar, Initial Status Bar, and 3D Controls check boxes, as shown in Figure 4-6. Accept the defaults elsewhere in this dialog box.
- Accept the defaults in the remaining AppWizard dialog boxes, and allow AppWizard to create the project. You don't even have to see the Step 5 and Step 6 dialog boxes to accept the defaults in them; just click the Finish button in the Step 4 dialog box.
Figure 4-4. Creating the Shapes project.
Figure 4-5. AppWizard's Step 1 dialog box.
After you click Finish, AppWizard will display a summary of the code it is about to create. Click OK to affirm or click Cancel and then use the Back and Next buttons to move backward and forward through the dialog boxes, making changes as needed.
Figure 4-6. AppWizard's Step 4 dialog box.
NOTE
Because of a bug in Visual C++ 6.0, the most important part of CMainFrame might not appear in your source code if you follow the steps prescribed above. One of the frame window's most important tasks is to create the view window. It's supposed to do so with the following WM_CREATE handler:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL)) { TRACE0("Failed to create view window\n"); return -1; } return 0; } |
Unfortunately, the Visual C++ 6.0 AppWizard erroneously omits this handler when the toolbar and status bar options are turned off in the Step 4 dialog box. Therefore, you must add it yourself. Don't forget to add an ON_WM_CREATE statement to the message map, too.
Analyzing AppWizard's Output
So what exactly did AppWizard do? First, it created a new project that includes all the build settings required for an MFC application. Second, it derived several classes from MFC base classes and inserted them into the project. Third, it created a set of resources for the application to use and inserted them into the project, too. A good way to familiarize yourself with AppWizard's output is to look at the files that it generated. Note that this output can vary widely depending on what options you selected in the AppWizard dialog boxes. The following sections provide a quick tour of the source code files that AppWizard generated for the Shapes application and a brief look at some of the important program elements found inside them.
StdAfx.h and StdAfx.cpp
AppWizard-generated projects speed program builds by taking advantage of a feature of Visual C++ known as precompiled headers. As a result of build settings implemented by AppWizard, all header files that are #included in StdAfx.h are precompiled into files named projectname.pch and StdAfx.obj so that once compiled, they don't have to be compiled again. AppWizard #includes StdAfx.h in the CPP files that it generates, and inside StdAfx.h, it adds #includes for core MFC header files such as Afxwin.h. You can add #includes of your own for other MFC header files, for C run-time header files, and for static header files of other types. Do not #include header files that are subject to change as the application is being developed, or you'll lose the benefits of using precompiled headers.
An interesting aside to a discussion of precompiled headers is the fact that Visual C++ effectively ignores statements that appear in a source code file before the statement that #includes StdAfx.h. That means code like this will compile just fine:
kjasdfj;oai4efj #include "Stdafx.h" |
Why is this fact important? Because more than one MFC programmer has been bitten by code like this:
#include <math.h> #include "Stdafx.h" |
Put the #include for Math.h after the #include for StdAfx.h (or better yet, put it inside StdAfx.h) to avoid this kind of error.
Resource.h and Shapes.rc
Among the source code files that AppWizard generates are an RC file containing definitions for all the application's resources and a header file (Resource.h) containing #defines for the command IDs and other symbols the RC file uses. Look inside the RC file and you'll find, among other things, a menu template and an accelerator table. Rather than edit these resources by hand, you can use Visual C++'s resource editor, which allows you to edit menus, accelerators, icons, and other resources visually and then applies your changes to the RC file. To see the menu editor firsthand, click the ResourceView tab in Visual C++'s workspace window and then double-click the menu resource IDR_MAINFRAME. This will open the menu in the menu editor, where making changes is as simple as pointing and clicking and typing information into dialog boxes. You can also edit the RC file directly, but if you decide to do this, be sure to use the Open dialog box's Open As Text option to open the file as if it were an ordinary text file.
Shapes.h and Shapes.cpp
You already know that every MFC application contains a global instance of a CWinApp-derived class representing the application itself. AppWizard has already derived an application class named CShapesApp and placed the source code in Shapes.h and Shapes.cpp. It has also declared a global instance of the class by including the statement
CShapesApp theApp; |
in Shapes.cpp.
CShapesApp::InitInstance looks a little different than the InitInstance functions in Chapters 1, 2, and 3. It creates a frame window by instantiating a class named CMainFrame and calling LoadFrame on the resulting object:
CMainFrame* pFrame = new CMainFrame; m_pMainWnd = pFrame; |
CMainFrame is another AppWizard-generated class, one that represents the application's top-level window. Like the CMainWindow class featured in previous chapters, CMainFrame's base class is CFrameWnd. Unlike CMainWindow, CMainFrame's constructor doesn't call Create. Therefore, it's up to InitInstance to create the frame window object and the frame window that goes with it.
AppWizard's CShapesApp class also includes a command handler named OnAppAbout:
// In the message map ON_COMMAND(ID_APP_ABOUT, OnAppAbout) |
This code will make more sense to you after you read about dialog boxes in Chapter 8. Its purpose is to display an "About box"—a dialog box containing information about the program, such as its author and copyright. CAboutDlg is the class that represents the About box; its source code is also found in Shapes.h and Shapes.cpp. AppWizard inserts an About Shapes command (ID=ID_APP_ABOUT) into the application's Help menu in support of this feature. Selecting the Help-About Shapes command executes CShapesApp::OnAppAbout and displays a simple About box.
ChildView.h and ChildView.cpp
The greatest difference between the AppWizard-generated Shapes application and the applications we built by hand in earlier chapters is the addition of a new class named CChildView. CChildView is a CWnd derivative that represents the application's "view"—a special window that is sized to fit the client area of the application's frame window and then placed neatly over the top of it. What appears to be the frame window's client area is actually the view window, which means that we'll write our WM_PAINT handler in CChildView, not CMainFrame. In fact, AppWizard has already included a do-nothing OnPaint function in CChildView. It has also overridden CWnd::PreCreateWindow and, in the override, included code that registers a special WNDCLASS for the view and adds WS_EX_CLIENTEDGE to the view's window style. WS_EX_CLIENTEDGE gives the window a three-dimensional look by making the view appear to be recessed inside the frame window. MFC's CFrameWnd class includes code that keeps the view glued to the frame window by automatically resizing the view window whenever the frame window is resized.
In effect, AppWizard has created an application that uses a view in much the same way that a document/view application uses a view. The question is, Why? Is this an inherently better way to architect an application? The primary reason AppWizard inserts a view is that a view-based architecture simplifies the task of managing space inside a frame window that hosts toolbars and other UI objects. If you were to draw directly to the client area of a frame window that contains a toolbar, you'd have to subtract the toolbar rectangle from the frame window's client-area rectangle to compute an "effective" client area every time you called GetClientRect. Such shenanigans aren't necessary in view-based applications because MFC resizes the view to fit the frame window's effective client area whenever the frame window's size changes or a change occurs in the size, position, or visibility of a toolbar or status bar. Call GetClientRect in a view class and you get a precise measure of the space available to you.
The effect that a view-based application architecture will have on the code that you write can be summarized as follows:
- WM_PAINT messages should be processed in the view, not in the frame window.
- Client-area mouse messages should be processed in the view, not in the frame window. Because the view completely obscures the frame window's client area, the frame window won't receive any client-area mouse messages.
- Keyboard message handlers, too, should be processed in the view, not in the frame window.
Writing view-based applications now will help prepare you to write full-blown document/view MFC applications beginning in Chapter 9.
MainFrm.h and MainFrm.cpp
These files contain the source code for the AppWizard-generated frame window class named CMainFrame. This frame window class differs from the CMainWindow class we've been using in several respects:
- It overrides CFrameWnd::PreCreateWindow. Because CMainFrame doesn't create a window in its class constructor, overriding PreCreateWindow is the only way it can exercise control over the window style and other window characteristics.
- It overrides AssertValid and Dump, two CObject functions used for diagnostic testing.
- It includes a CChildView member variable named m_wndView that represents the view window.
- It includes a WM_SETFOCUS handler that shifts the input focus to the view anytime the frame window receives the input focus. This transfer is important because it is the view, not the frame window, that is the primary source of mouse and keyboard input. If the input focus were given to the frame window and not transferred to the view, keyboard message handlers in the view class wouldn't work.
- It overrides CFrameWnd::OnCmdMsg and routes commands to the view and (indirectly) to the application object using a simplified form of the command routing architecture used in document/view applications. The practical effect is that command handlers and update handlers for the program's menu items can be placed in the frame window class, the view class, or the application class. Without OnCmdMsg, command and update handlers would be restricted to the frame window. Command routing is discussed in Chapters 9 and 11.
Beyond AppWizard
AppWizard generates a generic application skeleton. Once AppWizard has run its course, it's up to you to write the code that makes your application different from all the rest. You don't have to write all that code by hand; you can use ClassWizard to perform basic tasks such as adding message handlers, command handlers, and update handlers. In effect, ClassWizard writes the mundane code, so you can concentrate on the application-specific code. With that thought in mind, here are the steps required to duplicate the source code presented in Figure 4-3:
- With the Shapes project open in Visual C++, add a protected int member variable named m_nShape to the CChildView class. You can add this member variable manually, or you can add it visually. To add it visually, click the ClassView tab in the workspace window, right-click CChildView in ClassView, select Add Member Variable from the context menu, and fill in the Add Member Variable dialog box as shown in Figure 4-7.
- Initialize m_nShape to 1 by adding the following statement to CChildView's constructor:
- Modify the view's OnPaint handler so that it looks like the one in Figure 4-3. AppWizard has already added an empty OnPaint handler to the view class; all you have to do is edit it.
- Click the ResourceView tab at the bottom of the workspace window to see a list of the resources that AppWizard created. Double-click the IDR_MAINFRAME menu resource to open it for editing, and delete the Edit and Help menus. Then add a Shape menu to the right of the File menu, and add these three items to the Shape menu:
- Add command handlers to the view class for the Circle, Triangle, and Square commands. Here's the finished code:
- Add update handlers to the view class for the Circle, Triangle, and Square commands. Here's the completed code:
- Click the ResourceView tab in the workspace window, and open the accelerator resource IDR_MAINFRAME for editing. Add the following accelerators to serve as shortcuts for the items in the Shape menu:
- If CMainFrame doesn't include the OnCreate handler discussed in the previous note, add it now. Rather than add the message handler by hand, you can add it with ClassWizard. How? Right-click CMainFrame in the ClassView window, select Add Windows Message Handler, double-click WM_CREATE, and click Edit Existing. You'll find yourself in the empty message handler body, poised to type in the finished code. ClassWizard has already done everything else, including adding an ON_WM_CREATE entry to the message map.
Figure 4-7. The Add Member Variable dialog box.
m_nShape = 1; // Triangle |
m_nShape will hold 0, 1, or 2, indicating that the shape drawn in the view is a circle, a triangle, or a square, respectively. Initializing m_nShape to 1 makes a triangle the default.
Menu Item Text | Command ID |
---|---|
&Circle\tF7 | ID_SHAPE_CIRCLE |
&Triangle\tF8 | ID_SHAPE_TRIANGLE |
&Square\tF9 | ID_SHAPE_SQUARE |
To delete an item from a menu, click the item once to select it and then press the Delete key. To add an item, double-click the empty rectangle that appears in the menu and type the menu item text and command ID into the Menu Item Properties dialog box. (See Figure 4-8.) Top-level menu items don't need command IDs, so for them the ID box is disabled. For other menu items, you can type in the command ID or let Visual C++ choose one for you. If you dismiss the Menu Item Properties dialog box and the ID box is blank, Visual C++ will generate a command ID of the form ID_ top_ item, where top is the top-level menu item name and item is the text assigned to the menu item. Regardless of how the command ID is generated, Visual C++ adds a #define statement to Resource.h assigning the ID a numeric value. The completed Shape menu is shown in Figure 4-9.
Figure 4-8. The Menu Item Properties dialog box.
Figure 4-9. The Shape menu.
// In CChildView's message map ON_COMMAND(ID_SHAPE_CIRCLE, OnShapeCircle) ON_COMMAND(ID_SHAPE_TRIANGLE, OnShapeTriangle) ON_COMMAND(ID_SHAPE_SQUARE, OnShapeSquare) |
You can add these command handlers by hand, or you can let ClassWizard add them for you. To use ClassWizard to add a command handler for the Circle command, click the ClassView tab at the bottom of the workspace window, right-click CChildView in ClassView, and select Add Windows Message Handler from the context menu to display the New Windows Message And Event Handlers dialog box. (See Figure 4-10.) Find ID_SHAPE_CIRCLE in the Class Or Object To Handle list box, and click it. Then double-click COMMAND in the New Windows Messages/Events list box. When ClassWizard asks you for a function name, accept the default— OnShapeCircle. COMMAND will move to the Existing Message/Event Handlers list box, indicating that a command handler now exists for the ID_SHAPE_CIRCLE menu item. Finish up by clicking the Edit Existing button to go to the empty command handler and adding the statements
m_nShape = 0; Invalidate (); |
to the function body. Repeat this process to write command handlers for the Triangle and Square commands, but set m_nShape to 1 and 2, respectively, in their function bodies.
Figure 4-10. The New Windows Message And Event Handlers dialog box.
ON_UPDATE_COMMAND_UI(ID_SHAPE_CIRCLE, OnUpdateShapeCircle) ON_UPDATE_COMMAND_UI(ID_SHAPE_TRIANGLE, OnUpdateShapeTriangle) ON_UPDATE_COMMAND_UI(ID_SHAPE_SQUARE, OnUpdateShapeSquare) |
Once more, you can add these handlers by hand or you can add them with ClassWizard. To write an update handler with ClassWizard, follow the same procedure used to write a command handler, but double-click UPDATE_COMMAND_UI rather than COMMAND in the New Windows Messages/Events list box.
Shortcut Key | Command ID |
---|---|
F7 | ID_SHAPE_CIRCLE |
F8 | ID_SHAPE_TRIANGLE |
F9 | ID_SHAPE_SQUARE |
To add an accelerator, double-click the empty rectangle at the bottom of the edit window and define the accelerator in the Accel Properties dialog box. (See Figure 4-11.) If you don't carry virtual key codes around in your head, you can click the Next Key Typed button and press the shortcut key rather than type the key code into the Key combo box. While you're at it, delete the other accelerators (the ones that AppWizard created) since Shapes doesn't use them. To delete an accelerator, simply click it once to select it and press the Delete key.
Figure 4-11. The Accel Properties dialog box.
With that, you've successfully built the Shapes application depicted in Figure 4-2. It's a simple application whose OnPaint handler examines a member variable ( m_nShape) and draws a circle, a triangle, or a square. Command handlers for the items in the Shape menu set m_nShape to 0, 1, or 2 and force a repaint by calling CWnd::Invalidate. Update handlers place a check mark by the shape that is currently selected. All painting and processing of menu commands is done in the view class, which serves as a proxy of sorts for the frame window's client area. The function keys F7, F8, and F9 provide shortcuts for the Circle, Triangle, and Square commands by virtue of the accelerators that you added. Given this basis to work from, you should be able to add menu items to any application and write command and update handlers for them.
An interesting point to ponder regarding Shapes is that the File-Exit command closes the application, yet nowhere in the program's source code will you find a command handler for File-Exit. The secret is the following statement in CWinApp's message map, which is found in the MFC source code file Appcore.cpp:
ON_COMMAND(ID_APP_EXIT, OnAppExit) |
Remember that message maps are passed to derived classes by inheritance just like function and data members. Even though this entry doesn't appear in CShapesApp's message map, it's there implicitly because CShapesApp derives from CWinApp. Because AppWizard assigned the Exit command the ID ID_APP_EXIT, selecting the command invokes OnAppExit, which also comes to CShapesApp via inheritance. OnAppExit sends a WM_CLOSE message to the application's main window. You can view its source code in Appui.cpp.
The Process in Review
Building an application with AppWizard and ClassWizard is altogether different than building an application by hand. It's important to realize that the wizards do nothing you can't do yourself; they're simply code-generating tools that make the development process more efficient. It makes sense to use the wizards if you understand the code that they generate. That's why the first three chapters of this book didn't use the wizards—to help build your fundamental knowledge of MFC. As the applications that you build become more complex, the code that the wizards generate will become more complex, too. You'll see what I mean in the last few chapters of this book, when we use MFC to build COM-enabled applications and a few button clicks with the wizards will touch not just one or two source code files, but several. The wizards never do anything you can't do yourself, but they can save you a lot of time and effort that you'd otherwise spend re-creating the basic plumbing common to all Windows applications.