[Previous] [Next]

The Shapes Application

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).

Click to view at full size.

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(CShapesApp)
        public:
        virtual BOOL InitInstance();
        //}}AFX_VIRTUAL

// Implementation

public:
        //{{AFX_MSG(CShapesApp)
        afx_msg void OnAppAbout();
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
};


///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// 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(CShapesApp)
        ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
        //}}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(CAboutDlg)
        protected:
        virtual void DoDataExchange(CDataExchange* pDX);  // DDX/DDV support
        //}}AFX_VIRTUAL

// Implementation
protected:
        //{{AFX_MSG(CAboutDlg)
                // No message handlers
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
        //{{AFX_DATA_INIT(CAboutDlg)
        //}}AFX_DATA_INIT
}


void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
        CDialog::DoDataExchange(pDX);
        //{{AFX_DATA_MAP(CAboutDlg)
        //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
        //{{AFX_MSG_MAP(CAboutDlg)
                // No message handlers
        //}}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(CMainFrame)
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, 
        AFX_CMDHANDLERINFO* pHandlerInfo);
    //}}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(CMainFrame)
    afx_msg void OnSetFocus(CWnd *pOldWnd);
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// 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(CMainFrame)
    ON_WM_SETFOCUS()
    ON_WM_CREATE()
    //}}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(CChildView)
    protected:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CChildView();

    // Generated message map functions
protected:
    int m_nShape;
    //{{AFX_MSG(CChildView)
    afx_msg void OnPaint();
    afx_msg void OnShapeCircle();
    afx_msg void OnShapeTriangle();
    afx_msg void OnShapeSquare();
    afx_msg void OnUpdateShapeCircle(CCmdUI* pCmdUI);
    afx_msg void OnUpdateShapeTriangle(CCmdUI* pCmdUI);
    afx_msg void OnUpdateShapeSquare(CCmdUI* pCmdUI);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// 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(CChildView)
    ON_WM_PAINT()
    ON_COMMAND(ID_SHAPE_CIRCLE, OnShapeCircle)
    ON_COMMAND(ID_SHAPE_TRIANGLE, OnShapeTriangle)
    ON_COMMAND(ID_SHAPE_SQUARE, OnShapeSquare)
    ON_UPDATE_COMMAND_UI(ID_SHAPE_CIRCLE, OnUpdateShapeCircle)
    ON_UPDATE_COMMAND_UI(ID_SHAPE_TRIANGLE, OnUpdateShapeTriangle)
    ON_UPDATE_COMMAND_UI(ID_SHAPE_SQUARE, OnUpdateShapeSquare)
    //}}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

//{{NO_DEPENDENCIES}}
// 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:

  1. 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.
  2. Click to view at full size.

    Figure 4-4. Creating the Shapes project.

  3. 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.
  4. Click to view at full size.

    Figure 4-5. AppWizard's Step 1 dialog box.

  5. In AppWizard's Step 2 dialog box, accept the defaults.
  6. 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.
  7. 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.
  8. 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.

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.

Click to view at full size.

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;

  
pFrame->LoadFrame(IDR_MAINFRAME,
    WS_OVERLAPPEDWINDOW ¦ FWS_ADDTOTITLE, NULL,
    NULL);

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)
  
  
void CShapesApp::OnAppAbout()
{
    CAboutDlg aboutDlg;
    aboutDlg.DoModal();
}

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:

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:

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:

  1. 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.
  2. Figure 4-7. The Add Member Variable dialog box.

  3. Initialize m_nShape to 1 by adding the following statement to CChildView's constructor:
  4. 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.

  5. 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.
  6. 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:
  7. 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.

  8. Add command handlers to the view class for the Circle, Triangle, and Square commands. Here's the finished code:
  9. // In CChildView's message map
    ON_COMMAND(ID_SHAPE_CIRCLE, OnShapeCircle)
    ON_COMMAND(ID_SHAPE_TRIANGLE, OnShapeTriangle)
    ON_COMMAND(ID_SHAPE_SQUARE, OnShapeSquare)
    
      
    void CChildView::OnShapeCircle() 
    {
        m_nShape = 0;
        Invalidate ();        
    }
    
    void CChildView::OnShapeTriangle() 
    {
        m_nShape = 1;
        Invalidate ();        
    }
    
    void CChildView::OnShapeSquare() 
    {
        m_nShape = 2;
        Invalidate ();        
    }
    

    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.

    Click to view at full size.

    Figure 4-10. The New Windows Message And Event Handlers dialog box.

  10. Add update handlers to the view class for the Circle, Triangle, and Square commands. Here's the completed code:
  11. ON_UPDATE_COMMAND_UI(ID_SHAPE_CIRCLE, OnUpdateShapeCircle)
    ON_UPDATE_COMMAND_UI(ID_SHAPE_TRIANGLE, OnUpdateShapeTriangle)
    ON_UPDATE_COMMAND_UI(ID_SHAPE_SQUARE, OnUpdateShapeSquare)
    
      
    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);        
    }
    

    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.

  12. 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:
  13. 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.

  14. 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.

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.

The CHM file was converted to HTML by chm2web software.