MFC and the Multiple Document Interface

From a user's point of view, five fundamental characteristics distinguish MDI applications from SDI applications:

Without help from a framework such as MFC, MDI applications require more effort to create than SDI applications. For example, it's the developer's responsi-bility to update the menu that appears in the top-level frame window as documents are opened, closed, and switched between. It's the developer's responsibility to implement the Window menu. And it's the developer's responsibility to create and manage the document frames that float within the top-level frame window. Under the hood, these and other features of the MDI user interface model translate into dozens of annoying little implementation details that you (or someone) must account for.

That's the bad news. The good news is that MFC's document/view architecture abstracts the user interface model so that writing MDI applications is only slightly different than writing SDI applications. Like their SDI counterparts, MDI document/view applications store data in document objects based on CDocument and present views of that data in view objects based on CView or one of its derivatives. The chief structural differences between MDI and SDI applications built with MFC are as follows:

These are the differences that you see. On the inside, MFC devotes hundreds of lines of code to MDI-specific chores such as dynamically switching menus and creating new views of open documents. In short, the framework manages almost every aspect of an MDI application's user interface to spare you the chore of having to do it yourself. And to a large extent, details that aren't automatically handled for you by MFC are handled by AppWizard. If you choose Multiple Documents instead of Single Documents in AppWizard's Step 1 dialog box (shown in Figure 11-1), AppWizard emits an MDI application skeleton. From that point on, writing an MDI application is just like writing an SDI application. You just write a document/view application; MFC handles all the rest.

Well, MFC handles almost all the rest. You mustn't forget one important implementation detail. That "detail" is the subject of the next section.

Click to view at full size.

Figure 11-1. Using AppWizard to create an MDI application.

Synchronizing Multiple Views of a Document

When you elect to use the MDI user interface model, you implicitly afford your users the freedom to display multiple concurrent views of a document. A user editing a 100-page document might use this feature of your application to display pages 1 and 100 side by side for comparison.

When the New Window command is selected from the Window menu, an MFC-provided command handler pulls up the document template, extracts CRuntimeClass pointers identifying the view class and the frame window class, and instantiates a new view and a new frame window (a child frame, not a top-level frame) to go with it. Under the hood, the secondary view's address is added to the linked list of views maintained by the document object so that the document is aware that two independent views of it are visible on the screen. If either view is asked to repaint, it calls GetDocument to acquire a pointer to the document object, queries the document for the data it needs, and repaints. Because both views are connected to the same document object (that is, GetDocument returns the same pointer in either view), each enjoys access to the same set of document data. Moreover, the architecture is scalable: it works just as well for hundreds of open views as it does for two.

So far, so good. Now consider what happens if the user edits the document in one of the views. If the change is visible (or has consequences that are visible) in the other views, the other views should be updated to reflect the change. The catch is that the update doesn't happen automatically; it's up to you to make sure that when the document is edited in one view, other views—if they exist—are updated, too. The framework provides the mechanism to make this happen in the form of CDocument::UpdateAllViews and CView::OnUpdate, which were briefly discussed in Chapter 9. It's now time to examine these functions more closely.

Suppose you write a program editor that uses the MDI architecture to allow the user to display multiple views of a source code file. If a change made to a file in one view is visible in the others, all views of that file should be updated to reflect the change. That's what UpdateAllViews is for. When a document's data is modified in a multiple-view application, someone—either the object that made the modification (usually a view object) or the document object—should call UpdateAllViews to update the views. UpdateAllViews iterates through the list of views associated with the document, calling each view's virtual OnUpdate function.

CView provides a trivial implementation of OnUpdate that invalidates the view and forces a call to OnDraw. If a full repaint is what you want, there's no need to override OnUpdate. If, however, you want to make updates as efficient as possible by repainting only the part of the view that changed, you can override OnUpdate in the view class and make use of hint information passed to UpdateAllViews. UpdateAllViews is prototyped as follows:

void UpdateAllViews (CView* pSender, LPARAM lHint = 0L,
    CObject* pHint = NULL)

The function prototype for OnUpdate looks very similar:

virtual void OnUpdate (CView* pSender, LPARAM lHint,
    CObject* pHint)

lHint and pHint carry hint information from UpdateAllViews to OnUpdate. How you use these parameters is highly application-specific. A simple use for hint information is to pass the address of a RECT structure or a CRect object specifying what part of the view needs updating. OnUpdate can use that information in a call to InvalidateRect, as demonstrated here:

// In the document class
UpdateAllViews (NULL, 1, (CObject*) pRect);

      

// In the view class
void CMyView::OnUpdate (CView* pSender, LPARAM lHint, CObject* pHint)
{
    if (lHint == 1) {
        CRect* pRect = (CRect*) pHint;
        InvalidateRect (pRect);
        return;
    }
    CView::OnUpdate (pSender, lHint, pHint);
}

If the document's data consists of an array of CObjects and UpdateAllViews is called because a new CObject was added to the document, pHint might be used to pass the new CObject's address. The following example assumes that pLine holds a pointer to an instance of a CObject-derived class named CLine and that CLine includes a public member function named Draw that can be called to render the CLine on the screen:

// In the document class
UpdateAllViews (NULL, 1, pLine);

    

// In the view class
void CMyView::OnUpdate (CView* pSender, LPARAM lHint, CObject* pHint)
{
    if (lHint == 1) {
        CLine* pLine = (CLine*) pHint;
        CClientDC dc (this);
        pLine->Draw (&dc);
        return;
    }
    CView::OnUpdate (pSender, lHint, pHint);
}

In both examples, OnUpdate forwards the call to the base class if lHint is anything other than the application-specific value passed to UpdateAllViews. That's important, because MFC sometimes calls OnUpdate itself with lHint equal to 0. You can use any nonzero value that you like for lHint. You can even define multiple "hint sets" that assign different meanings to pHint and use lHint to identify the hint type.

You can use UpdateAllViews' first parameter, pSender, to omit a view from the update cycle. If pSender is NULL, UpdateAllViews calls each view's OnUpdate function. If pSender is non-NULL, UpdateAllViews calls OnUpdate on every view except the one identified by pSender. When a function in the document class calls UpdateAllViews, it typically sets pSender to NULL so that all the views will be updated. If a view calls UpdateAllViews, however, it can set pSender to this to prevent its own OnUpdate function from being called. If the view has already updated itself in response to user input, its OnUpdate function doesn't need to be called. If, however, the view hasn't already updated itself because it performs all of its updating in OnUpdate, it should pass UpdateAllViews a NULL first parameter.

The sample program in the next section makes trivial use of UpdateAllViews by calling it without hint parameters. Secondary views are updated by the default implementation of OnUpdate. Later in this chapter, we'll develop a more ambitious multiple-view application that passes hint information to UpdateAllViews and makes use of that information in OnUpdate.

The MdiSquares Application

The MdiSquares application shown in Figure 11-2 is an MDI version of Chapter 9's SdiSquares. The document and view classes that it uses are identical to those used in SdiSquares, save for the fact that MdiSquares' view class draws the squares slightly smaller to conserve screen space.

Click to view at full size.

Figure 11-2. MdiSquares with two documents open.

When you run MdiSquares, the first document is opened automatically. You can open additional documents by selecting New from the File menu. To open another view of a document, select New Window from the Window menu. Observe that if you have two views of a document displayed and you click a square in one view, the square's color changes in both views. That's because the document's SetSquare function, which the view calls to add a color to a square, calls UpdateAllViews after recording the square's color in m_clrGrid. Here's the relevant statement in SquaresDoc.cpp:

UpdateAllViews (NULL);

Because no hint information is passed in the call, and because CSquaresView doesn't override OnUpdate, each view is repainted in its entirety when SetSquare is called. If you look closely, you can see the views flash each time you click a square. The flashing is a consequence of the fact that the entire view is being erased and repainted each time UpdateAllViews is called.

SquaresDoc.cpp and other MdiSquares source code files are shown in Figure 11-3. The main frame window class, CMainFrame, represents the application's top-level window. Views are displayed in instances of the child frame window class, CChildFrame. Notice that in InitInstance, CChildFrame, not CMainFrame, is identified as the frame window class when the document template is initialized:

CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
    IDR_MDISQUTYPE,
    RUNTIME_CLASS(CSquaresDoc),
    RUNTIME_CLASS(CChildFrame), // custom MDI child frame
    RUNTIME_CLASS(CSquaresView));

Consequently, calling ProcessShellCommand in an MDI application creates a new child frame but not a top-level frame window. As a result, an MDI application must create the top-level frame window itself before calling ProcessShellCommand. The code that creates MdiSquares' main window is found elsewhere in InitInstance:

CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
    return FALSE;
m_pMainWnd = pMainFrame;

This code and all the other code in CMdiSquaresApp, CMainFrame, and CChildFrame was generated by AppWizard. Unless you code an MDI application by hand, you'll perform the bulk of your work in the document and view classes.

If you open MdiSquares in Visual C++ and browse its list of resources, you'll see that it has two icons, two menus, and two document strings. Their resource IDs are IDR_MAINFRAME and IDR_MDISQUTYPE. Here's how these resources are used:

Except for the relatively minor differences discussed in this section, MdiSquares and SdiSquares are virtually identical. That's one of the benefits of using MFC's document/view architecture: once you know how to write SDI applications, you know how to write MDI applications, too.

Figure 11-3. The MdiSquares application.

MdiSquares.h

// MdiSquares.h : main header file for the MDISQUARES application
//

#if !defined(AFX_MDISQUARES_H__36D513DB_9CA0_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_MDISQUARES_H__36D513DB_9CA0_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

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

/ CMdiSquaresApp:
// See MdiSquares.cpp for the implementation of this class
//

class CMdiSquaresApp : public CWinApp
{
public:
    CMdiSquaresApp();

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CMdiSquaresApp)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL


	// Implementation
    //{{AFX_MSG(CMdiSquaresApp)
    afx_msg void OnAppAbout();
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

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

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
//     AFX_MDISQUARES_H__36D513DB_9CA0_11D2_8E53_006008A82731__INCLUDED_)

MdiSquares.cpp

// MdiSquares.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "MdiSquares.h"

#include "MainFrm.h"
#include "ChildFrm.h"
#include "SquaresDoc.h"
#include "SquaresView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CMdiSquaresApp

BEGIN_MESSAGE_MAP(CMdiSquaresApp, CWinApp)
    //{{AFX_MSG_MAP(CMdiSquaresApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    // Standard file based document commands
    ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CMdiSquaresApp construction

CMdiSquaresApp::CMdiSquaresApp()
{
}

///////////////////////////////////////////////////////////////////////////
// The one and only CMdiSquaresApp object

CMdiSquaresApp theApp;

///////////////////////////////////////////////////////////////////////////
// CMdiSquaresApp initialization

BOOL CMdiSquaresApp::InitInstance()
{
    SetRegistryKey(_T("Local AppWizard-Generated Applications"));

    LoadStdProfileSettings();  // Load standard INI file 
                               // options (including MRU)

    CMultiDocTemplate* pDocTemplate;
    pDocTemplate = new CMultiDocTemplate(
        IDR_MDISQUTYPE,
        RUNTIME_CLASS(CSquaresDoc),
        RUNTIME_CLASS(CChildFrame), // custom MDI child frame
        RUNTIME_CLASS(CSquaresView));
    AddDocTemplate(pDocTemplate);

    // create main MDI Frame window
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
        return FALSE;
    m_pMainWnd = pMainFrame;

    // Enable drag/drop open
    m_pMainWnd->DragAcceptFiles();
   // Enable DDE Execute open
    EnableShellOpen();
    RegisterShellFileTypes(TRUE);

    // Parse command line for standard shell commands, DDE, file open
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);

    // Dispatch commands specified on the command line
    if (!ProcessShellCommand(cmdInfo))
        return FALSE;

    // The main window has been initialized, so show and update it.
    pMainFrame->ShowWindow(m_nCmdShow);
    pMainFrame->UpdateWindow();

    return TRUE;
}


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

///////////////////////////////////////////////////////////////////////////
// CMdiSquaresApp message handlers

MainFrm.h

// MainFrm.h : interface of the CMainFrame class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__36D513DF_9CA0_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_MAINFRM_H__36D513DF_9CA0_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CMainFrame : public CMDIFrameWnd

    DECLARE_DYNAMIC(CMainFrame)
public:
    CMainFrame();

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CMainFrame)
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CMainFrame();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

// Generated message map functions
protected:
    //{{AFX_MSG(CMainFrame)
        // NOTE - the ClassWizard will add and remove member functions here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}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__36D513DF_9CA0_11D2_8E53_006008A82731__INCLUDED_)

MainFrm.cpp

// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "MdiSquares.h"

#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CMDIFrameWnd::PreCreateWindow(cs) )
        return FALSE;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
    CMDIFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
    CMDIFrameWnd::Dump(dc);
}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers

ChildFrm.h

// ChildFrm.h : interface of the CChildFrame class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(AFX_CHILDFRM_H__36D513E1_9CA0_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_CHILDFRM_H__36D513E1_9CA0_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CChildFrame : public CMDIChildWnd
{
    DECLARE_DYNCREATE(CChildFrame)
public:
    CChildFrame();

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CChildFrame)
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CChildFrame();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

// Generated message map functions
protected:
    //{{AFX_MSG(CChildFrame)
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

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

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
// AFX_CHILDFRM_H__36D513E1_9CA0_11D2_8E53_006008A82731__INCLUDED_)

ChildFrm.cpp

// ChildFrm.cpp : implementation of the CChildFrame class
//

#include "stdafx.h"
#include "MdiSquares.h"

#include "ChildFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CChildFrame

IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWnd)

BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
    //{{AFX_MSG_MAP(CChildFrame)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CChildFrame construction/destruction

CChildFrame::CChildFrame()
{
}

CChildFrame::~CChildFrame()
{
}

BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CMDIChildWnd::PreCreateWindow(cs) )
        return FALSE;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CChildFrame diagnostics

#ifdef _DEBUG
void CChildFrame::AssertValid() const
{
    CMDIChildWnd::AssertValid();
}

void CChildFrame::Dump(CDumpContext& dc) const
{
    CMDIChildWnd::Dump(dc);
}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CChildFrame message handlers

SquaresDoc.h

// SquaresDoc.h : interface of the CSquaresDoc class
//
/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_SQUARESDOC_H__36D513E3_9CA0_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_SQUARESDOC_H__36D513E3_9CA0_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CSquaresDoc : public CDocument
{
protected: // create from serialization only
    CSquaresDoc();
    DECLARE_DYNCREATE(CSquaresDoc)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CSquaresDoc)
    public:
    virtual BOOL OnNewDocument();
    virtual void Serialize(CArchive& ar);
    //}}AFX_VIRTUAL

// Implementation
public:
    void SetSquare (int i, int j, COLORREF color);
    COLORREF GetSquare (int i, int j);
    COLORREF GetCurrentColor();
    virtual ~CSquaresDoc();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    COLORREF m_clrCurrentColor;
    COLORREF m_clrGrid[4][4];
    //{{AFX_MSG(CSquaresDoc)
    afx_msg void OnColorRed();
    afx_msg void OnColorYellow();
    afx_msg void OnColorGreen();
    afx_msg void OnColorCyan();
    afx_msg void OnColorBlue();
    afx_msg void OnColorWhite();
    afx_msg void OnUpdateColorRed(CCmdUI* pCmdUI);
    afx_msg void OnUpdateColorYellow(CCmdUI* pCmdUI);
    afx_msg void OnUpdateColorGreen(CCmdUI* pCmdUI);
    afx_msg void OnUpdateColorCyan(CCmdUI* pCmdUI);
    afx_msg void OnUpdateColorBlue(CCmdUI* pCmdUI);
    afx_msg void OnUpdateColorWhite(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_SQUARESDOC_H__36D513E3_9CA0_11D2_8E53_006008A82731__INCLUDED_)

SquaresDoc.cpp

// SquaresDoc.cpp : implementation of the CSquaresDoc class
//

#include "stdafx.h"
#include "MdiSquares.h"

#include "SquaresDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CSquaresDoc

IMPLEMENT_DYNCREATE(CSquaresDoc, CDocument)

BEGIN_MESSAGE_MAP(CSquaresDoc, CDocument)
    //{{AFX_MSG_MAP(CSquaresDoc)
    ON_COMMAND(ID_COLOR_RED, OnColorRed)
    ON_COMMAND(ID_COLOR_YELLOW, OnColorYellow)
    ON_COMMAND(ID_COLOR_GREEN, OnColorGreen)
    ON_COMMAND(ID_COLOR_CYAN, OnColorCyan)
    ON_COMMAND(ID_COLOR_BLUE, OnColorBlue)
    ON_COMMAND(ID_COLOR_WHITE, OnColorWhite)
    ON_UPDATE_COMMAND_UI(ID_COLOR_RED, OnUpdateColorRed)
    ON_UPDATE_COMMAND_UI(ID_COLOR_YELLOW, OnUpdateColorYellow)
    ON_UPDATE_COMMAND_UI(ID_COLOR_GREEN, OnUpdateColorGreen)
    ON_UPDATE_COMMAND_UI(ID_COLOR_CYAN, OnUpdateColorCyan)
    ON_UPDATE_COMMAND_UI(ID_COLOR_BLUE, OnUpdateColorBlue)
    ON_UPDATE_COMMAND_UI(ID_COLOR_WHITE, OnUpdateColorWhite)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CSquaresDoc construction/destruction

CSquaresDoc::CSquaresDoc()
{
}

CSquaresDoc::~CSquaresDoc()
{
}

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

    for (int i=0; i<4; i++)
        for (int j=0; j<4; j++)
            m_clrGrid[i][j] = RGB (255, 255, 255);

    m_clrCurrentColor = RGB (255, 0, 0);
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CSquaresDoc serialization

void CSquaresDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        for (int i=0; i<4; i++)
            for (int j=0; j<4; j++)
                ar << m_clrGrid[i][j];
        ar << m_clrCurrentColor;
    }
    else
    {
        for (int i=0; i<4; i++)
            for (int j=0; j<4; j++)
                ar >> m_clrGrid[i][j];
        ar >> m_clrCurrentColor;
    }
}

///////////////////////////////////////////////////////////////////////////
// CSquaresDoc diagnostics

#ifdef _DEBUG
void CSquaresDoc::AssertValid() const
{
    CDocument::AssertValid();
}

void CSquaresDoc::Dump(CDumpContext& dc) const
{
    CDocument::Dump(dc);
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CSquaresDoc commands

COLORREF CSquaresDoc::GetCurrentColor()
{
    return m_clrCurrentColor;
}

COLORREF CSquaresDoc::GetSquare(int i, int j)
{
    ASSERT (i >= 0 && i <= 3 && j >= 0 && j <= 3);
    return m_clrGrid[i][j];
}

void CSquaresDoc::SetSquare(int i, int j, COLORREF color)
{
    ASSERT (i >= 0 && i <= 3 && j >= 0 && j <= 3);
    m_clrGrid[i][j] = color;
    SetModifiedFlag (TRUE);
    UpdateAllViews (NULL);
}

void CSquaresDoc::OnColorRed() 
{
    m_clrCurrentColor = RGB (255, 0, 0);    
}

void CSquaresDoc::OnColorYellow() 
{
    m_clrCurrentColor = RGB (255, 255, 0);    
}


{
    m_clrCurrentColor = RGB (0, 255, 0);    
}

void CSquaresDoc::OnColorCyan() 
{
    m_clrCurrentColor = RGB (0, 255, 255);    
}

void CSquaresDoc::OnColorBlue() 
{
    m_clrCurrentColor = RGB (0, 0, 255);    
}

void CSquaresDoc::OnColorWhite() 
{
    m_clrCurrentColor = RGB (255, 255, 255);    
}

void CSquaresDoc::OnUpdateColorRed(CCmdUI* pCmdUI) 
{
    pCmdUI->SetRadio (m_clrCurrentColor == RGB (255, 0, 0));    
}

void CSquaresDoc::OnUpdateColorYellow(CCmdUI* pCmdUI) 
{
    pCmdUI->SetRadio (m_clrCurrentColor == RGB (255, 255, 0));    
}

void CSquaresDoc::OnUpdateColorGreen(CCmdUI* pCmdUI) 
{
    pCmdUI->SetRadio (m_clrCurrentColor == RGB (0, 255, 0));    
}

void CSquaresDoc::OnUpdateColorCyan(CCmdUI* pCmdUI) 
{
    pCmdUI->SetRadio (m_clrCurrentColor == RGB (0, 255, 255));    
}

void CSquaresDoc::OnUpdateColorBlue(CCmdUI* pCmdUI) 
{
    pCmdUI->SetRadio (m_clrCurrentColor == RGB (0, 0, 255));    
}

void CSquaresDoc::OnUpdateColorWhite(CCmdUI* pCmdUI) 
{
    pCmdUI->SetRadio (m_clrCurrentColor == RGB (255, 255, 255));    
}

SquaresView.h

// SquaresView.h : interface of the CSquaresView class
//
/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_SQUARESVIEW_H__36D513E5_9CA0_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_SQUARESVIEW_H__36D513E5_9CA0_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CSquaresView : public CView
{
protected: // create from serialization only
    CSquaresView();
    DECLARE_DYNCREATE(CSquaresView)

// Attributes
public:
    CSquaresDoc* GetDocument();

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CSquaresView)
    public:
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    protected:
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CSquaresView();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    //{{AFX_MSG(CSquaresView)
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in SquaresView.cpp
inline CSquaresDoc* CSquaresView::GetDocument()
   { return (CSquaresDoc*)m_pDocument; }
#endif

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

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
//     AFX_SQUARESVIEW_H__36D513E5_9CA0_11D2_8E53_006008A82731__INCLUDED_)

SquaresView.cpp

// SquaresView.cpp : implementation of the CSquaresView class
//

#include "stdafx.h"
#include "MdiSquares.h"

#include "SquaresDoc.h"
#include "SquaresView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CSquaresView

IMPLEMENT_DYNCREATE(CSquaresView, CView)

BEGIN_MESSAGE_MAP(CSquaresView, CView)
    //{{AFX_MSG_MAP(CSquaresView)
    ON_WM_LBUTTONDOWN()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CSquaresView construction/destruction

CSquaresView::CSquaresView()
{
}

CSquaresView::~CSquaresView()
{
}

BOOL CSquaresView::PreCreateWindow(CREATESTRUCT& cs)
{
    return CView::PreCreateWindow(cs);
}

///////////////////////////////////////////////////////////////////////////
// CSquaresView drawing

void CSquaresView::OnDraw(CDC* pDC)
{
    CSquaresDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    //
    // Set the mapping mode to MM_LOENGLISH.
    //
    pDC->SetMapMode (MM_LOENGLISH);
    //
    // Draw the 16 squares.
    //
    for (int i=0; i<4; i++) {
        for (int j=0; j<4; j++) {
            COLORREF color = pDoc->GetSquare (i, j);
            CBrush brush (color);
            int x1 = (j * 70) + 35;
            int y1 = (i * -70) - 35;
            int x2 = x1 + 70;
            int y2 = y1 - 70;
            CRect rect (x1, y1, x2, y2);
            pDC->FillRect (rect, &brush);
        }
    }

    //
    // Then draw the grid lines surrounding them.
    //
    for (int x=35; x<=315; x+=70) {
        pDC->MoveTo (x, -35);
        pDC->LineTo (x, -315);
    }

    for (int y=-35; y>=-315; y-=70) {
        pDC->MoveTo (35, y);
        pDC->LineTo (315, y);
    }
}

///////////////////////////////////////////////////////////////////////////
// CSquaresView diagnostics

#ifdef _DEBUG
void CSquaresView::AssertValid() const
{
    CView::AssertValid();
}

void CSquaresView::Dump(CDumpContext& dc) const
{
    CView::Dump(dc);
}

CSquaresDoc* CSquaresView::GetDocument() // non-debug version is inline
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSquaresDoc)));
    return (CSquaresDoc*)m_pDocument;
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CSquaresView message handlers

void CSquaresView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    CView::OnLButtonDown(nFlags, point);

    //
    // Convert click coordinates to MM_LOENGLISH units.
    //
    CClientDC dc (this);
    dc.SetMapMode (MM_LOENGLISH);
    CPoint pos = point;
    dc.DPtoLP (&pos);

    //
    // If a square was clicked, set its color to the current color.
    //
    if (pos.x >= 35 && pos.x <= 315 && pos.y <= -35 && pos.y >= -315) {
        int i = (-pos.y - 35) / 70;
        int j = (pos.x - 35) / 70;
        CSquaresDoc* pDoc = GetDocument ();
        COLORREF clrCurrentColor = pDoc->GetCurrentColor ();
        pDoc->SetSquare (i, j, clrCurrentColor);
    }
}

Supporting Multiple Document Types

An MDI application written with MFC supports multiple document instances by default. A new document instance is created each time the user executes a File/New command. MDI applications can also support multiple document types, each characterized by a unique document template.

Suppose you want to add a second document type—say, circles documents—to MdiSquares so that when File/New is selected, the user is given a choice of whether to create a squares document or a circles document. Here's how you'd do it.

  1. Derive a new document class and a new view class to serve the new document type. For the sake of this example, assume the classes are named CCirclesDoc and CCirclesView. Make the classes dynamically creatable, just like the document and view classes AppWizard generates.
  2. Add four new resources to the project for circles documents: an icon, a menu, an accelerator (optional), and a document string. Assign all four resources the same resource ID—for example, IDR_CIRCLETYPE.
  3. Modify InitInstance to create a new document template containing the resource ID and CRuntimeClass pointers for the document, view, and frame window classes. Then call AddDocTemplate and pass in the address of the document template object.

Here's an excerpt from an InitInstance function modified to register two document templates:

// AppWizard-generated code
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
    IDR_MDISQUTYPE,
    RUNTIME_CLASS(CSquaresDoc),
    RUNTIME_CLASS(CChildFrame), // custom MDI child frame
    RUNTIME_CLASS(CSquaresView));
AddDocTemplate(pDocTemplate);

// Your code
pDocTemplate = new CMultiDocTemplate(
    IDR_CIRCLETYPE,
    RUNTIME_CLASS(CCirclesDoc),
    RUNTIME_CLASS(CChildFrame),
    RUNTIME_CLASS(CCirclesView));
AddDocTemplate(pDocTemplate);

That's basically all there is to it. This example uses CChildFrame as the child frame class for both document types, but you can derive a separate child frame class if you'd prefer.

When multiple document types are registered in this manner, MFC's File-New command handler displays a dialog box presenting the user with a choice of document types. The string that identifies each document type in the dialog box comes from the document string—specifically, from the third of the document string's seven possible substrings. With this infrastructure in place, it's relatively simple to write multifunction MDI applications that permit users to create and edit different kinds of documents. You can write SDI applications that support two or more document types, too, but the multiple document type paradigm is rarely used in single document applications.

Alternatives to MDI

The multiple document interface isn't the only game in town if you want to give your users the ability to edit several documents at once in one instance of your application. The Windows Interface Guidelines for Software Design outlines three alternatives to the MDI programming model:

MFC doesn't support any of these alternatives directly, but you can always code them yourself. Alternative user interface models are on the radar screen of the MFC team at Microsoft, so it's very possible that a future version of MFC will support user interface models other than SDI and MDI.