MFC and the Multiple Document Interface

[Previous] [Next]

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

  • MDI applications permit the user to have two or more documents open for editing at once. SDI applications, by contrast, require the user to close the currently open document before opening another.
  • MDI applications sometimes support multiple document types. For example, an all-in-one word processing, spreadsheet, and charting program might be implemented as an MDI application that supports three document types: word processing documents containing text, spreadsheet documents containing spreadsheets, and chart documents containing charts.
  • MDI applications feature a Window menu with a New Window command for opening secondary views of a document and commands for arranging the windows in which the views appear. The Window menu also contains a list of open views. Selecting a view from this menu makes that view the active view and the document associated with that view the active document.
  • SDI applications generally feature just one menu. MDI applications have at least two: one that's displayed when no documents are open and another that's displayed when at least one document is open. Some MDI applications have more than two menus. An MDI application that supports multiple document types generally implements one menu per document type.
  • SDI applications use just one frame window—the top-level frame window that serves as the application's main window and frames views of open documents. MDI applications use two: a top-level frame window and child frames or document frames that float within the top-level frame window and frame views of open documents.

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:

  • MDI applications derive their top-level frame window classes from CMDI-FrameWnd rather than CFrameWnd.
  • MDI applications use classes based on CMDIChildWnd to represent the child frame windows that frame views of their documents.
  • MDI applications use CMultiDocTemplate rather than CSingleDocTemplate to create document templates. The frame window class referenced in CMultiDocTemplate's constructor is the child frame window class rather than the top-level frame window class.
  • MDI applications have at least two menu resources, as opposed to SDI's one. One is displayed when no documents are open; the other is displayed when at least one document is open.

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:

  • The IDR_MAINFRAME icon is displayed in the title bar of the top-level window. The IDR_MDISQUTYPE icon is displayed in the title bars of the child frames. You can use the same icon for both if you like, but most MDI applications use a different icon for document windows.
  • The IDR_MAINFRAME menu is displayed when no documents are open. The IDR_MDISQUTYPE menu is displayed when at least one document is open. The IDR_MAINFRAME menu is a minimal menu that features a File menu with New, Open, and Exit commands and a recently used file list, but little else. IDR_MDISQUTYPE, on the other hand, is a full-blown menu with all the commands that pertain to MdiSquares documents.
  • The IDR_MAINFRAME document string contains nothing more than the title that appears in the main window's title bar. The IDR_MDISQUTYPE document string contains all relevant information about the document type, including the default file name extension.

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:

  • A workspace-based model that groups related documents in objects called workspaces and allows documents contained in a workspace to be viewed and edited in MDI-like document frames that are children of a top-level frame window. Visual C++ is one example of an application that uses the workspace containment model.
  • A workbook model in which individual views occupy the full client area of a top-level frame window but only one view at a time is visible. The appearance is similar to that of a maximized document frame in an MDI application. Each view is tabbed so that the user can switch from one view to another with a button click as if the views were pages in a property sheet.
  • A project model that groups related documents in projects but allows individual documents to be edited in SDI-like frame windows. The primary difference between the project model and the MDI and workspace models is that in the project model there is no top-level frame window providing containment for document frames.

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.



Programming Windows with MFC
Programming Windows with MFC, Second Edition
ISBN: 1572316950
EAN: 2147483647
Year: 1999
Pages: 101
Authors: Jeff Prosise

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net