Figure 2   Key CDocument Operations

Function

Description

GetFirstViewPosition

Returns a POSITION value that can be passed to GetNextView to enumerate the document's views.

GetNextView

Returns a CView pointer to the next view in the list of views attached to this document.

GetPathName

Retrieves the document's file name and path-for example, "C:\Documents\Personal\MyFile.Lif." Returns a null string if the document has not been named.

GetTitle

Retrieves the document's title-for example, "MyFile." Returns a null string if the document has not been named.

IsModified

Returns a nonzero value if the document contains unsaved data, and 0 if it does not.

SetModifiedFlag

Sets or clears the document's modified flag.

UpdateAllViews

Updates all views associated with the document by calling each view's OnUpdate function.

OnFileSendMail

Implements the Send Mail command in the File menu.

Figure 3   Key CDocument Overridables

Function

Description

OnNewDocument

Called by the framework when a new document is created. Override to reinitialize the document object when a new document is created.

OnOpenDocument

Called by the framework when a document is loaded from disk. Override to reinitialize the unserialized data members of the document object before a new document is loaded.

DeleteContents

Called by the framework to delete the document's contents. Override to free memory and other resources allocated to a document before it is closed.

Serialize

Called by the framework to serialize a document to or from disk. Override to provide document-specific serialization code so your documents can be loaded and saved.

Figure 5   Key Predefined Command IDs and Command Handlers

Command ID

Menu Item Name

Default Implementation

Prewired?

File menu

ID_FILE_NEW

New

CWinApp::OnFileNew

No

ID_FILE_OPEN

Open

CWinApp::OnFileOpen

No

ID_FILE_CLOSE

Close

CDocument::OnFileClose

Yes

ID_FILE_SAVE

Save

CDocument::OnFileSave

Yes

ID_FILE_SAVE_AS

Save As

CDocument::OnFileSaveAs

Yes

ID_FILE_PAGE_SETUP

Page Setup

None

N/A

ID_FILE_PRINT_SETUP

Print Setup

CWinApp::OnFilePrintSetup

No

ID_FILE_PRINT

Print

CView::OnFilePrint

No

ID_FILE_PRINT_PREVIEW

Print Preview

CView::OnFilePrintPreview

No

ID_FILE_SEND_MAIL

Send Mail

CDocument::OnFileSendMail

No

ID_FILE_MRU_FILE1-16

N/A

CWinApp::OnOpenRecentFile

Yes

ID_APP_EXIT

Exit

CWinApp::OnAppExit

Yes

Edit menu

ID_EDIT_CLEAR

Clear

None*

N/A

ID_EDIT_CLEAR_ALL

Clear All

None

N/A

ID_EDIT_COPY

Copy

None*

N/A

ID_EDIT_CUT

Cut

None*

N/A

ID_EDIT_PASTE

Paste

None*

N/A

ID_EDIT_PASTE_LINK

Paste Link

None

N/A

ID_EDIT_PASTE_SPECIAL

Paste Special

None

N/A

ID_EDIT_FIND

Find

None*

N/A

ID_EDIT_REPLACE

Replace

None*

N/A

ID_EDIT_REPEAT

Repeat

None*

N/A

ID_EDIT_SELECT_ALL

Select All

None*

N/A

ID_EDIT_UNDO

Undo

None*

N/A

ID_EDIT_REDO

Redo

None

N/A

View menu

ID_VIEW_TOOLBAR

Toolbar

CFrameWnd::OnBarCheck

Yes

ID_VIEW_STATUS_BAR

Status Bar

CFrameWnd::OnBarCheck

Yes

Window menu

ID_WINDOW_NEW

New Window

CMDIFrameWnd::OnWindowNew

Yes

ID_WINDOW_ARRANGE

Arrange Icons

CMDIFrameWnd::OnMDIWindowCmd

Yes

ID_WINDOW_CASCADE

Cascade

CMDIFrameWnd::OnMDIWindowCmd

Yes

ID_WINDOW_TILE_HORZ

Tile Horizontal

CMDIFrameWnd::OnMDIWindowCmd

Yes

ID_WINDOW_TILE_VERT

Tile Vertical

CMDIFrameWnd::OnMDIWindowCmd

Yes

ID_WINDOW_SPLIT

Split

CView::OnSplitCmd

Yes

Help menu

ID_APP_ABOUT

About AppName

None

N/A

* CEditView and CRichEditView provide default implementations and message-map entries for these commands

Figure 6   Life

RESOURCE.H

 //***************************************************************************
//
//  Resource.h (Life)
//
//***************************************************************************

#define ID_OPTIONS_STEP             100
#define ID_OPTIONS_OPEN_BOUNDARY    101
#define ID_OPTIONS_CLOSED_BOUNDARY  102

#define IDC_ICONRECT                200
#define IDC_HORZ                    210
#define IDC_VERT                    211

#define IDR_MAINFRAME               300

#define CELLWIDTH                    16
#define CELLHEIGHT                   16

LIFE.RC

 //***************************************************************************
//
//  Life.rc
//
//***************************************************************************

#include <afxres.h>
#include "Resource.h"

IDR_MAINFRAME ICON Life.ico

GrayCell BITMAP GrayCell.bmp
BlueCell BITMAP BlueCell.bmp

IDR_MAINFRAME MENU
BEGIN
    POPUP "&File" {
        MENUITEM "&New\tCtrl+N",            ID_FILE_NEW
        MENUITEM "&Open...\tCtrl+O",        ID_FILE_OPEN
        MENUITEM SEPARATOR
        MENUITEM "&Save\tCtrl+S",           ID_FILE_SAVE
        MENUITEM "Save &As...\tCtrl+A",     ID_FILE_SAVE_AS
        MENUITEM SEPARATOR
        MENUITEM "Recent File",             ID_FILE_MRU_FILE1
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                   ID_APP_EXIT
    }
    POPUP "&Options" {
        MENUITEM "Ste&p\tF10",              ID_OPTIONS_STEP
        MENUITEM SEPARATOR
        MENUITEM "&Open Boundary",          ID_OPTIONS_OPEN_BOUNDARY
        MENUITEM "Closed Boundary",         ID_OPTIONS_CLOSED_BOUNDARY
    }
    POPUP "&View" {
        MENUITEM "&Status Bar",             ID_VIEW_STATUS_BAR
    }
    POPUP "&Help" {
        MENUITEM "&About Life...",          ID_APP_ABOUT
    }
END

IDR_MAINFRAME ACCELERATORS
BEGIN
    "N",    ID_FILE_NEW,        VIRTKEY, CONTROL
    "O",    ID_FILE_OPEN,       VIRTKEY, CONTROL
    "S",    ID_FILE_SAVE,       VIRTKEY, CONTROL
    "A",    ID_FILE_SAVE_AS,    VIRTKEY, CONTROL
    VK_F10, ID_OPTIONS_STEP,    VIRTKEY
END

STRINGTABLE
BEGIN
    IDR_MAINFRAME    "Life\n\n\nLife Grids (*.lif)\n.lif\nLife.Grid\nLife Grid"
END

STRINGTABLE
BEGIN
    AFX_IDS_IDLEMESSAGE         "Ready"
    ID_OPTIONS_STEP             "Evolve the grid one generation"
    ID_OPTIONS_OPEN_BOUNDARY    "Enable wrap-around boundaries"
    ID_OPTIONS_CLOSED_BOUNDARY  "Disable wrap-around boundaries"
END

GridSize DIALOG 0, 0, 148, 60
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Grid Size"
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "&Horizontal", -1, 12, 16, 40, 8
    LTEXT           "&Vertical", -1, 12, 36, 40, 8
    EDITTEXT        IDC_HORZ, 56, 12, 32, 12, ES_AUTOHSCROLL
    EDITTEXT        IDC_VERT, 56, 32, 32, 12, ES_AUTOHSCROLL
    DEFPUSHBUTTON   "OK", IDOK, 100, 12, 40, 12, WS_GROUP
END

About DIALOG 0, 0, 256, 98
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "About Life"
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "", IDC_ICONRECT, 14, 12, 80, 74
    LTEXT           "Life Version 1.0", -1, 108, 12, 136, 8
    LTEXT           "From the book", -1, 108, 32, 136, 8
    LTEXT           """Programming Windows 95 with MFC""", -1, 108, 42, 136, 8
    LTEXT           "Copyright © 1996 by Jeff Prosise", -1, 108, 52, 136, 8
    DEFPUSHBUTTON   "OK", IDOK, 108, 72, 50, 14
END

LIFE.H

 //***************************************************************************
//
//  Life.h
//
//***************************************************************************

class CLifeApp : public CWinApp
{
public:
    virtual BOOL InitInstance ();

protected:
    afx_msg void OnAppAbout ();
    DECLARE_MESSAGE_MAP ()
};

LIFE.CPP

 //***************************************************************************
//
//  Life.cpp
//
//***************************************************************************

#include <afxwin.h>
#include <afxext.h>

#include "Resource.h"
#include "Life.h"
#include "LifeDoc.h"
#include "MainFrm.h"
#include "LifeView.h"
#include "AboutDlg.h"

CLifeApp myApp;

BEGIN_MESSAGE_MAP (CLifeApp, CWinApp)
    ON_COMMAND (ID_FILE_NEW, CWinApp::OnFileNew)
    ON_COMMAND (ID_FILE_OPEN, CWinApp::OnFileOpen)
    ON_COMMAND (ID_APP_ABOUT, OnAppAbout)
END_MESSAGE_MAP ()

BOOL CLifeApp::InitInstance ()
{
    SetRegistryKey ("Microsoft Systems Journal");
    LoadStdProfileSettings ();

    CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate (
        IDR_MAINFRAME,                  // Resource ID
        RUNTIME_CLASS (CLifeDoc),       // Document class
        RUNTIME_CLASS (CMainFrame),     // Frame window class
        RUNTIME_CLASS (CLifeView)       // View class
    );

    AddDocTemplate (pDocTemplate);

    EnableShellOpen ();
    RegisterShellFileTypes ();

    CCommandLineInfo cmdInfo;
    ParseCommandLine (cmdInfo);

    if (!ProcessShellCommand (cmdInfo))
        return FALSE;

    m_pMainWnd->DragAcceptFiles ();
    return TRUE;
}

void CLifeApp::OnAppAbout ()
{
    CAboutDialog dlg;
    dlg.DoModal ();
}

MAINFRM.H

 //***************************************************************************
//
//  MainFrame.h
//
//***************************************************************************

class CMainFrame : public CFrameWnd
{
    DECLARE_DYNCREATE (CMainFrame)

private:
    CStatusBar m_wndStatusBar;

protected:
    afx_msg int OnCreate (LPCREATESTRUCT);
    DECLARE_MESSAGE_MAP ()
};

MAINFRM.CPP

 //***************************************************************************
//
//  MainFrame.cpp
//
//***************************************************************************

#include <afxwin.h>
#include <afxext.h>

#include "Resource.h"
#include "MainFrm.h"

IMPLEMENT_DYNCREATE (CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP (CMainFrame, CFrameWnd)
    ON_WM_CREATE ()
END_MESSAGE_MAP ()

int CMainFrame::OnCreate (LPCREATESTRUCT lpcs)
{
    if (CFrameWnd::OnCreate (lpcs) == -1)
        return -1;

    m_wndStatusBar.Create (this);
    UINT nIndicator = ID_SEPARATOR;
    m_wndStatusBar.SetIndicators (&nIndicator, 1);
    return 0;
}

LIFEDOC.H

 //***************************************************************************
//
//  LifeDoc.h
//
//***************************************************************************

class CLifeDoc : public CDocument
{
    DECLARE_DYNCREATE (CLifeDoc)

private:
    int         m_cx;
    int         m_cy;
    BOOL        m_bOpenEdge;
    CByteArray  m_byGrid;
    CByteArray  m_byBuffer;

    void    SetBackgroundCell (int, int, BOOL);

public:
    CLifeDoc ();
    virtual BOOL OnNewDocument ();
    virtual void DeleteContents ();
    virtual void Serialize (CArchive&);

    CSize   GetGridSize ();
    BOOL    GetCell (int, int);
    BOOL    ToggleCell (int, int);
    int     Evolve ();  

protected:
    afx_msg void OnOptionsStep ();
    afx_msg void OnOptionsOpenBoundary ();
    afx_msg void OnOptionsClosedBoundary ();
    afx_msg void OnUpdateOptionsOpenBoundaryUI (CCmdUI*);
    afx_msg void OnUpdateOptionsClosedBoundaryUI (CCmdUI*);

    DECLARE_MESSAGE_MAP ()
};

LIFEDOC.CPP

 //***************************************************************************
//
//  LifeDoc.cpp
//
//***************************************************************************

#include <afxwin.h>

#include "Resource.h"
#include "LifeDoc.h"
#include "SizeDlg.h"

IMPLEMENT_DYNCREATE (CLifeDoc, CDocument)

BEGIN_MESSAGE_MAP (CLifeDoc, CDocument)
    ON_COMMAND (ID_OPTIONS_STEP, OnOptionsStep)
    ON_COMMAND (ID_OPTIONS_OPEN_BOUNDARY, OnOptionsOpenBoundary)
    ON_COMMAND (ID_OPTIONS_CLOSED_BOUNDARY, OnOptionsClosedBoundary)
    ON_UPDATE_COMMAND_UI (ID_OPTIONS_OPEN_BOUNDARY,
        OnUpdateOptionsOpenBoundaryUI)
    ON_UPDATE_COMMAND_UI (ID_OPTIONS_CLOSED_BOUNDARY,
        OnUpdateOptionsClosedBoundaryUI)
END_MESSAGE_MAP ()

CLifeDoc::CLifeDoc ()
{
    m_cx = -1;
    m_cy = -1;
}

BOOL CLifeDoc::OnNewDocument ()
{
    if (!CDocument::OnNewDocument ())
        return FALSE;

    if ((m_cx == -1) && (m_cy == -1)) {
        m_cx = 24;
        m_cy = 24;
    }
    else { // Prompt for grid size
        CGridSizeDialog dlg;
        dlg.m_cx = m_cx;
        dlg.m_cy = m_cy;

        dlg.DoModal ();

        m_cx = dlg.m_cx;
        m_cy = dlg.m_cy;
    }

    int nByteCount = ((m_cx * m_cy) + 7) / 8;

    try { // Allocate grid memory
        m_byGrid.SetSize (nByteCount);
        m_byBuffer.SetSize (nByteCount);
    }
    catch (CMemoryException* e) { // Just in case...
        e->Delete ();
        return FALSE;
    }

    for (int i=0; i<nByteCount; i++) {
        m_byGrid[i] = 0;
        m_byBuffer[i] = 0;
    }
    return TRUE;
}

void CLifeDoc::DeleteContents ()
{
    m_byGrid.RemoveAll ();
    m_byBuffer.RemoveAll ();
}

void CLifeDoc::Serialize (CArchive& archive)
{
    if (archive.IsStoring ())
        archive << (WORD) m_cx << (WORD) m_cy;
    else {
        WORD cx, cy;
        archive >> cx >> cy;
        m_cx = (int) cx;
        m_cy = (int) cy;
    }
    m_byGrid.Serialize (archive);
    m_byBuffer.SetSize (m_byGrid.GetSize ());
}

void CLifeDoc::OnOptionsStep ()
{
    Evolve ();
}

void CLifeDoc::OnOptionsOpenBoundary ()
{
    m_bOpenEdge = TRUE;
}

void CLifeDoc::OnOptionsClosedBoundary ()
{
    m_bOpenEdge = FALSE;
}

void CLifeDoc::OnUpdateOptionsOpenBoundaryUI (CCmdUI* pCmdUI)
{
    pCmdUI->SetCheck (m_bOpenEdge ? 1 : 0);
}

void CLifeDoc::OnUpdateOptionsClosedBoundaryUI (CCmdUI* pCmdUI)
{
    pCmdUI->SetCheck (m_bOpenEdge ? 0 : 1);
}

CSize CLifeDoc::GetGridSize ()
{
    return CSize (m_cx, m_cy);
}

BOOL CLifeDoc::GetCell (int nRow, int nCol)
{
    if ((nRow < 0) || (nRow >= m_cy) ||
        (nCol < 0) || (nCol >= m_cx))
        return FALSE;

    int nBitNumber = ((nRow * m_cx) + nCol) % 8;
    int nByteNumber = ((nRow * m_cx) + nCol) / 8;

    return m_byGrid[nByteNumber] & (1 << nBitNumber);
}

BOOL CLifeDoc::ToggleCell (int nRow, int nCol)
{
    if ((nRow < 0) || (nRow >= m_cy) ||
        (nCol < 0) || (nCol >= m_cx))
        return FALSE;

    int nBitNumber = ((nRow * m_cx) + nCol) % 8;
    int nByteNumber = ((nRow * m_cx) + nCol) / 8;

    m_byGrid[nByteNumber] ^= (1 << nBitNumber);
    SetModifiedFlag ();

    return m_byGrid[nByteNumber] & (1 << nBitNumber);
}
 int CLifeDoc::Evolve ()
{
    int nPrevRow, nPrevCol;
    int nNextRow, nNextCol;
    int nNeighbors, i, j;
    int nLiveCells = 0;
    BOOL bAlive;
     for (i=0; i<m_cy; i++) { // Do rows on the outside loop...
        if (m_bOpenEdge) {
            nPrevRow = (i == 0) ? m_cy - 1 : i - 1;
            nNextRow = (i == (m_cy - 1)) ? 0 : i + 1;
        }
        else {
            nPrevRow = (i == 0) ? -1 : i - 1;
            nNextRow = (i == (m_cy - 1)) ? -1 : i + 1;
        }

        for (j=0; j<m_cx; j++) { // ...and columns on the inside
            if (m_bOpenEdge) {
                nPrevCol = (j == 0) ? m_cx - 1 : j - 1;
                nNextCol = (j == (m_cx - 1)) ? 0 : j + 1;
            }
            else {
                nPrevCol = (j == 0) ? -1 : j - 1;
                nNextCol = (j == (m_cx - 1)) ? -1 : j + 1;
            }

            nNeighbors = 0;

            if (GetCell (nPrevRow, nPrevCol))
                nNeighbors++;
            if (GetCell (nPrevRow, j))
                nNeighbors++;
            if (GetCell (nPrevRow, nNextCol))
                nNeighbors++;

            if (GetCell (i, nPrevCol))
                nNeighbors++;
            if (GetCell (i, nNextCol))
                nNeighbors++;

            if (GetCell (nNextRow, nPrevCol))
                nNeighbors++;
            if (GetCell (nNextRow, j))
                nNeighbors++;
            if (GetCell (nNextRow, nNextCol))
                nNeighbors++;

            bAlive = GetCell (i, j);

            if ((bAlive && ((nNeighbors == 2) ||
                (nNeighbors == 3))) || (!bAlive && (nNeighbors == 3))) {
                SetBackgroundCell (i, j, TRUE);
                nLiveCells++;
            }
            else
                SetBackgroundCell (i, j, FALSE);
        }
    }

    int nByteCount = ((m_cx * m_cy) + 7) / 8;
    for (i=0; i<nByteCount; i++)
        m_byGrid[i] = m_byBuffer[i];

    SetModifiedFlag ();
    UpdateAllViews (NULL, 1);
    return nLiveCells;
}

void CLifeDoc::SetBackgroundCell (int nRow, int nCol, BOOL bState)
{
    int nBitNumber = ((nRow * m_cx) + nCol) % 8;
    int nByteNumber = ((nRow * m_cx) + nCol) / 8;

    if (bState)
        m_byBuffer[nByteNumber] |= (1 << nBitNumber);
    else
        m_byBuffer[nByteNumber] &= ~(1 << nBitNumber);
}

LIFEVIEW.H

 //***************************************************************************
//
//  LifeView.h
//
//***************************************************************************

class CLifeView : public CScrollView
{
    DECLARE_DYNCREATE (CLifeView)

private:
    int     m_nPrevRow;
    int     m_nPrevCol;
    CBitmap m_bmGrayCell;
    CBitmap m_bmBlueCell;
    CPoint  m_ptOrg;

    void DrawCell (CDC*, int, int, BOOL);
    void SetGridOrigin (int, int);
    void SetScrollParms ();

public:
    CLifeView ();
    CLifeDoc* GetDocument ();
    virtual void OnInitialUpdate ();

protected:
    virtual void OnDraw (CDC*);
    virtual void OnUpdate (CView*, LPARAM, CObject*);

    afx_msg void OnSize (UINT, int, int);
    afx_msg BOOL OnEraseBkgnd (CDC*);
    afx_msg void OnLButtonDown (UINT, CPoint);
    afx_msg void OnMouseMove (UINT, CPoint);

    DECLARE_MESSAGE_MAP ()
};

LIFEVIEW.CPP

 //***************************************************************************
//
//  LifeView.cpp
//
//***************************************************************************

#include <afxwin.h>
#include <afxext.h>

#include "Resource.h"
#include "MainFrm.h"
#include "LifeDoc.h"
#include "LifeView.h"

IMPLEMENT_DYNCREATE (CLifeView, CScrollView)

BEGIN_MESSAGE_MAP (CLifeView, CScrollView)
    ON_WM_SIZE ()
    ON_WM_ERASEBKGND ()
    ON_WM_LBUTTONDOWN ()
    ON_WM_MOUSEMOVE ()
END_MESSAGE_MAP ()

CLifeView::CLifeView ()
{
    m_bmGrayCell.LoadBitmap ("GrayCell");
    m_bmBlueCell.LoadBitmap ("BlueCell");
}

void CLifeView::OnInitialUpdate ()
{
    m_nPrevRow = -1;
    m_nPrevCol = -1;

    SetScrollParms ();
    CRect rect;
    GetClientRect (&rect);
    SetGridOrigin (rect.Width (), rect.Height ());
    CScrollView::OnInitialUpdate ();
}

void CLifeView::OnUpdate (CView* pSender, LPARAM lHint, CObject* pHint)
{
    Invalidate (lHint ? FALSE : TRUE);
}

void CLifeView::OnSize (UINT nType, int cx, int cy)
{
    CScrollView::OnSize (nType, cx, cy);
    SetGridOrigin (cx, cy);
}

BOOL CLifeView::OnEraseBkgnd (CDC* pDC)
{
    CRect rect;
    GetClientRect (&rect);
    CBrush brush ((COLORREF) ::GetSysColor (COLOR_APPWORKSPACE));
    pDC->FillRect (rect, &brush);
    return TRUE;
}

void CLifeView::OnDraw (CDC* pDC)
{
    CLifeDoc* pDoc = GetDocument ();
    CSize size = pDoc->GetGridSize ();

    CDC mdcGray, mdcBlue;
    mdcGray.CreateCompatibleDC (pDC);
    mdcBlue.CreateCompatibleDC (pDC);

    CBitmap* pOldGrayBitmap = mdcGray.SelectObject (&m_bmGrayCell);
    CBitmap* pOldBlueBitmap = mdcBlue.SelectObject (&m_bmBlueCell);

    CRect rect;
    rect = ((CPaintDC*) pDC)->m_ps.rcPaint;
    pDC->DPtoLP (&rect);

    int x1, y1, x2, y2;
    x1 = max (0, (rect.left - m_ptOrg.x) / CELLWIDTH);
    y1 = max (0, (rect.top - m_ptOrg.y) / CELLHEIGHT);
    x2 = min (size.cx, ((rect.right - m_ptOrg.x) / CELLWIDTH) + 1);
    y2 = min (size.cy, ((rect.bottom - m_ptOrg.y) / CELLHEIGHT) + 1);

    int i, j;
    for (i=y1; i<y2; i++)
        for (j=x1; j<x2; j++)
            pDC->BitBlt (m_ptOrg.x + (j * CELLWIDTH),
                m_ptOrg.y + (i * CELLHEIGHT), CELLWIDTH, CELLHEIGHT,
                pDoc->GetCell (i, j) ? &mdcBlue : &mdcGray, 0, 0, SRCCOPY);

    mdcBlue.SelectObject (pOldBlueBitmap);
    mdcGray.SelectObject (pOldGrayBitmap);
}

void CLifeView::OnLButtonDown (UINT nFlags, CPoint point)
{
    CLifeDoc* pDoc = GetDocument ();
    CSize size = pDoc->GetGridSize ();

    if ((point.x < m_ptOrg.x) ||
        (point.x > (m_ptOrg.x + (size.cx * CELLWIDTH))) ||
        (point.y < m_ptOrg.y) ||
        (point.y > (m_ptOrg.y + (size.cy * CELLHEIGHT))))
        return;

    point.x -= m_ptOrg.x;
    point.y -= m_ptOrg.y;

    CClientDC dc (this);
    OnPrepareDC (&dc);
    dc.DPtoLP (&point);

    int nRow = point.y / CELLHEIGHT;
    int nCol = point.x / CELLWIDTH;

    m_nPrevRow = nRow;
    m_nPrevCol = nCol;

    BOOL bState = pDoc->ToggleCell (nRow, nCol);
    DrawCell (&dc, nRow, nCol, bState);
}

void CLifeView::OnMouseMove (UINT nFlags, CPoint point)
{
    if (!(nFlags & MK_LBUTTON))
        return;

    CLifeDoc* pDoc = GetDocument ();
    CSize size = pDoc->GetGridSize ();

    if ((point.x < m_ptOrg.x) ||
        (point.x > (m_ptOrg.x + (size.cx * CELLWIDTH))) ||
        (point.y < m_ptOrg.y) ||
        (point.y > (m_ptOrg.y + (size.cy * CELLHEIGHT))))
        return;

    point.x -= m_ptOrg.x;
    point.y -= m_ptOrg.y;

    CClientDC dc (this);
    OnPrepareDC (&dc);
    dc.DPtoLP (&point);

    int nRow = point.y / CELLHEIGHT;
    int nCol = point.x / CELLWIDTH;

    if ((nRow == m_nPrevRow) && (nCol == m_nPrevCol))
        return;

    m_nPrevRow = nRow;
    m_nPrevCol = nCol;

    BOOL bState = pDoc->ToggleCell (nRow, nCol);
    DrawCell (&dc, nRow, nCol, bState);
}

void CLifeView::DrawCell (CDC* pDC, int nRow, int nCol, BOOL bState)
{
    CDC mdc;
    mdc.CreateCompatibleDC (pDC);
    CBitmap* pOldBitmap = mdc.SelectObject (bState ?
        &m_bmBlueCell : &m_bmGrayCell);

    pDC->BitBlt (m_ptOrg.x + (nCol * CELLWIDTH),
        m_ptOrg.y + (nRow * CELLHEIGHT), CELLWIDTH, CELLHEIGHT, &mdc,
        0, 0, SRCCOPY);

    mdc.SelectObject (pOldBitmap);
}

void CLifeView::SetGridOrigin (int cx, int cy)
{
    m_ptOrg.x = m_ptOrg.y = 0;
    CSize size = GetDocument ()->GetGridSize ();
    if (cx > (size.cx * CELLWIDTH))
        m_ptOrg.x = (cx - (size.cx * CELLWIDTH)) / 2;
    if (cy > (size.cy * CELLHEIGHT))
        m_ptOrg.y = (cy - (size.cy * CELLHEIGHT)) / 2;
}

void CLifeView::SetScrollParms ()
{
    CSize sizeTotal = GetDocument ()->GetGridSize ();
    sizeTotal.cx *= CELLWIDTH;
    sizeTotal.cy *= CELLHEIGHT;

    CSize sizePage (CELLWIDTH * 10, CELLHEIGHT * 10);
    CSize sizeLine (CELLWIDTH, CELLHEIGHT);

    SetScrollSizes (MM_TEXT, sizeTotal, sizePage, sizeLine);
}
 CLifeDoc* CLifeView::GetDocument ()
{
    return (CLifeDoc*) m_pDocument;
}

SIZEDLG.H

 //***************************************************************************
//
//  SizeDlg.h
//
//***************************************************************************
 class CGridSizeDialog : public CDialog
{
public:
    int m_cx;
    int m_cy;

    CGridSizeDialog (CWnd* pParentWnd = NULL) :
        CDialog ("GridSize", pParentWnd) {}

protected:
    virtual BOOL OnInitDialog ();
    virtual void OnCancel ();
    virtual void DoDataExchange (CDataExchange*);
};

SIZEDLG.CPP

 //***************************************************************************
//
//  SizeDlg.cpp
//
//***************************************************************************

#include <afxwin.h>

#include "Resource.h"
#include "SizeDlg.h"

BOOL CGridSizeDialog::OnInitDialog ()
{
    CDialog::OnInitDialog ();

    CEdit* pEditHorz = (CEdit*) GetDlgItem (IDC_HORZ);
    CEdit* pEditVert = (CEdit*) GetDlgItem (IDC_VERT);
    pEditHorz->LimitText (4);
    pEditVert->LimitText (4);

    CenterWindow ();
    return TRUE;
}

void CGridSizeDialog::OnCancel ()
{
    // Do nothing so the dialog box will not be destroyed
}

void CGridSizeDialog::DoDataExchange (CDataExchange* pDX)
{
    CDialog::DoDataExchange (pDX);

    DDX_Text (pDX, IDC_HORZ, m_cx);
    DDV_MinMaxInt (pDX, m_cx, 8, 512);
    DDX_Text (pDX, IDC_VERT, m_cy);
    DDV_MinMaxInt (pDX, m_cy, 8, 512);
}

ABOUTDLG.H

 //***************************************************************************
//
//  AboutDlg.h
//
//***************************************************************************

class CAboutDialog : public CDialog
{
private:
    CRect m_rect;

public:
    CAboutDialog (CWnd* pParentWnd = NULL) :
        CDialog ("About", pParentWnd) {}

protected:
    virtual BOOL OnInitDialog ();

    afx_msg void OnPaint ();
    DECLARE_MESSAGE_MAP ()
};

ABOUTDLG.CPP

 //***************************************************************************
//
//  AboutDlg.cpp
//
//***************************************************************************

#include <afxwin.h>

#include "Resource.h"
#include "AboutDlg.h"

BEGIN_MESSAGE_MAP (CAboutDialog, CDialog)
    ON_WM_PAINT ()
END_MESSAGE_MAP ()

BOOL CAboutDialog::OnInitDialog ()
{
    CDialog::OnInitDialog ();

    CStatic* pStatic = (CStatic*) GetDlgItem (IDC_ICONRECT);
    pStatic->GetWindowRect (&m_rect);
    pStatic->DestroyWindow ();
    ScreenToClient (&m_rect);

    CenterWindow ();
    return TRUE;
}

void CAboutDialog::OnPaint ()
{
    CPaintDC dc (this);
    HICON hIcon = (HICON) ::GetClassLong (AfxGetMainWnd ()->m_hWnd,
        GCL_HICON);

    if (hIcon != NULL) {
        CDC mdc;
        mdc.CreateCompatibleDC (&dc);

        CBrush brush;
        brush.CreateStockObject (LTGRAY_BRUSH);

        CBitmap bitmap;
        bitmap.CreateCompatibleBitmap (&dc, 32, 32);
        CBitmap* pOldBitmap = mdc.SelectObject (&bitmap);

        CRect rcIcon (0, 0, 32, 32);
        mdc.FillRect (&rcIcon, &brush);
        mdc.DrawIcon (0, 0, hIcon);

        dc.StretchBlt (m_rect.left, m_rect.top, m_rect.Width(),
            m_rect.Height (), &mdc, 0, 0, 32, 32, SRCCOPY);

        mdc.SelectObject (pOldBitmap);
    }
}