The Document/View Architecture

 

Introduction

The Document/View architecture is the foundation used to create applications based on the Microsoft Foundation Classes library. It allows you to make distinct the different parts that compose a computer program including what the user sees as part of your application and the document a user would work on. This is done through a combination of separate classes that work as an ensemble.

The parts that compose the Document/View architecture are a frame, one or more documents, and the view. Put together, these entities make up a usable application.

The View

A view is the platform the user is working on to do his or her job. For example, while performing word processing, the user works on a series of words that compose the text. If a user is performing calculations on a spreadsheet application, the interface the user is viewing is made of small boxes called cells. Another user may be in front of a graphic document while drawing lines and other geometric figures. The thing the user is starring at and performing changes is called a view. The view also allows the user to print a document.

To let the user do anything on an application, you must provide a view, which is an object based on the CView class. You can either directly use one of the classes derived from CView or you can derive your own custom class from CView or one of its child classes.

The Document

A document is similar to a bucket. It can be used to hold or carry water and that water can be retrieved when needed. For a computer application, a document holds the user's data. For example, after working on a text processor, the user may want to save the file. Such an action creates a document and this document must reside somewhere. In the same way, to use an existing file, the user must locate it, open it, and make it available to the application. These two jobs and many others are handled behind the scenes as a document.

To create the document part of this architecture, you must derive an object from the CDocument class.

The Frame

As its name suggests, a frame is a combination of the building blocks, the structure (in the English sense), and the borders of an item. A frame gives "physical" presence to a window. A frame defines the location of an object with regards to the Windows desktop. A frame provides borders to a window, borders that the user can grab to move, size, and resize the object. The frame is also a type of desk that holds the tools needed on an application.

An application cannot exist without a frame. As we saw in previous lessons, to provide a frame to an application, you can derive a class from CFrameWnd.

The Document/View Approach

To create an application, you obviously should start by providing a frame. This can be taken care of by deriving a class from CFrameWnd. Here is an example:

class CMainFrame : public CFrameWnd
{
	DECLARE_DYNCREATE(CMainFrame)
	
	DECLARE_MESSAGE_MAP()
};

To give "physical" presence to the frame of an application, you can declare an OnCreate() method. Here is an example:

class CMainFrame : public CFrameWnd
{
	DECLARE_DYNCREATE(CMainFrame)
	
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	DECLARE_MESSAGE_MAP()
};

The easiest way you can implement this method is to call the parent class, CFrameWnd, to create the window. As we have seen in the past, if this method returns 0, the frame has been created. It returns -1, this indicates that the window has been destroyed. Therefore, you can create a frame as follows:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	// Call the base class to create the window
	if( CFrameWnd::OnCreate(lpCreateStruct) == 0)
		return 0;
		
	// else is implied
	return -1;
}

To allow users to interact with your application, you should provide a document. To do this, you can derive a class from CDocument so you can take advantage of this class. If you do not plan to do anything with the document, you can just make it an empty class. Here is an example:

class CExerciseDoc : public CDocument
{
	DECLARE_DYNCREATE(CExerciseDoc)

	DECLARE_MESSAGE_MAP()
};

Besides the few things we have learned so far, your next big decision may consist on the type of application you want to create. This is provided as a view. The most fundamental class of the view implementations in the MFC is CView. Because CView is an abstract class, you cannot directly use it in your application. You have two main alternatives. You can derive your own class based on CView (the CView class itself is based on CWnd) or you can use one of the many view classes that the MFC provides. The classes that are readily available to you are:

  Class Description
  CEditView Used for a basic text editing application
  CRichEditView Allows creating rich documents that perform text and paragraph formatting
  CScrollView Provides the ability to scroll a view horizontally and vertically
  CListView Provides a view that can display a list of items
  CTreeView Allows displaying a list of items arranged as a tree
  CFormView Used to create a view that resembles a dialog box but provides the document/view features
  CDaoRecordView Provides a view that resembles a dialog box but used for database controls
  CCtrlView Provides parental guidance to the CEditView, CListView, CTreeView, and CRichEditView

As we move on, we will study these classes as needed.

Once you have a frame, a document, and a view, you can create an application, which, as we have learned so far, is done by deriving a class from CWinApp and overriding the InitInstance() method. In the InitInstance() implementation, you must let the compiler know how a document is created in your application. To do this, you must provide a sample document, called a template, that defines the parts that constitute a document for your particular type of application. This is done using a pointer variable declared from CDocTemplate or one of its derived classes. 

 

Overview of the Single Document Interface (SDI)

The expression Single Document Interface or SDI refers to a document that can present only one view to the user. This means that the application cannot display more than one document at a time. If the user wants to view another type of document of the current application, he or she must another instance of the application. Notepad and WordPad are examples of SDI applications:

Notepad as a Single Document Interface

Notepad can be used to open only one document such as an HTML file, to view another file, the user would either replace the current document or launch another copy of Notepad.

To create an SDI, Microsoft Visual C++ provides the MFC wizard which provides all the basic functionality that an application needs, including the resources and classes.

Creating a Single Document Interface

As mentioned earlier, after creating a frame, a document, and a view, you can create an application by deriving a class from CWinApp and overriding the virtual InitInstance() member function. In InitInstance(), you must provide a template for your type of application. This is done using a CDocTemplate type of object.

To create an SDI, the CDocTemplate class provides the CSingleDocTemplate used to create an application that provides only one view. Therefore, you can declare a pointer variable to CSingleDocTemplate. Using this pointer and the new operator, use the CSingleDocTemplate constructor to provide the template. The syntax of the CSingleDocTemplate constructor is:

CSingleDocTemplate(UINT nIDResource,
		 CRuntimeClass* pDocClass,
		 CRuntimeClass* pFrameClass,
		 CRuntimeClass* pViewClass);

The CSingleDocTemplate constructor needs the common identifier for the resources of your application. We saw in Lesson 3 that this can be done by using IDR_MAINFRAME as the common name of most or all main resources of an application. This is provided as the nIDResource argument.

The second argument, pDocClass, is the name of the class you derived from CDocument, as mentioned earlier.

The third argument, pFrameClass, if the frame class you derived from either CFrameWnd or one of its children.

The pViewClass argument can be an MFC CView-derived class. It can also be a class you created based on CView.

Each of these arguments must be passed as a pointer to CRuntimeClass. This can be taken care of by using the RUNTIME_CLASS macro. Its syntax is:

RUNTIME_CLASS(ClassName);

Each one of the classes you want to use must be provided as the ClassName argument. The RUNTIME_CLASS macro in turn returns a pointer to CRuntimeClass. To effectively use the RUNTIME_CLASS macro, you should make sure that the (each) class is created and implemented using the DECLARE_DYNAMIC, the DECLARE_DYNCREATE, or the DECLARE_SERIAL macros.

To actually create the application so it can be displayed to the user, the CWinApp class is equipped with the AddDocTemplate() method. Therefore, After creating a template, pass the CSingleDocTemplate pointer to the CWinApp::AddDocTemplate() method. Its syntax is:

void AddDocTemplate(CDocTemplate *pTemplate);

Everything else is subject to how you want your application to provide a useful experience to the user.

 

 

Practical Learning: Creating a Document/View Application

  1. If necessary, start Microsoft Visual Studio or Visual C++
    Create a new empty Win32 application named DocView1
     
    New Project 
  2. Click OK
  3. Specify that you want to create an empty document:
     
    Win32 Application Wizard
  4. Click Finish
     
    Property Pages
  5. Specify that you will Use MFC In A Shared DLL
  6. To create an icon, display the Add Resource dialog box and click Icon
     
  7. Click New
  8. Design the 32x32 icon as follows:
     
    Icon Design 32x32
  9. Add a 16x16 icon and design it as follows:
     
    Icon Design 16x16
  10. Using the Properties window, change the ID of the icon to IDR_MAINFRAME
  11. Display the Add Resource dialog box and double-click Menu
  12. Change the ID of the menu from IDR_MENU1 to IDR_MAINFRAME
  13. Create the menu items as follows:
     
    Menu Design
  14. (If you are using MSVC 5 or 6, first close both the menu and the icon windows. You will be asked to save the resource script and accept to save it. Save it as DocView. Then, on the main menu, click Project -> Add To Project -> Files... Select the DocView.rc file and click OK.
    Add a new header file named Exercise and, in it, create the necessary classes as follows:
     
    #include <afxext.h>    // For CEditView
    #include "resource.h"
    
    class CExerciseApp : public CWinApp
    {
    	BOOL InitInstance();
    
    	DECLARE_MESSAGE_MAP()
    };
    
    class CMainFrame : public CFrameWnd
    {
    	DECLARE_DYNCREATE(CMainFrame)
    	
    	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    	DECLARE_MESSAGE_MAP()
    };
    
    class CExerciseDoc : public CDocument
    {
    	DECLARE_DYNCREATE(CExerciseDoc)
    
    	DECLARE_MESSAGE_MAP()
    };
    
    class CExerciseView : public CEditView
    {	
    	DECLARE_DYNCREATE(CExerciseView)
    
    	DECLARE_MESSAGE_MAP()
    };
  15. Add a new source file named Exercise and, in it, implement the classes as follows:
     
    #include <afxwin.h>  
    #include "Exercise.h"
    
    BEGIN_MESSAGE_MAP(CExerciseApp, CWinApp)
    
    END_MESSAGE_MAP()
    
    BOOL CExerciseApp::InitInstance()
    {
    	CSingleDocTemplate* pDocTemplate;
    	pDocTemplate = new CSingleDocTemplate(
    		IDR_MAINFRAME,
    		RUNTIME_CLASS(CExerciseDoc),
    		RUNTIME_CLASS(CMainFrame), 
    		RUNTIME_CLASS(CExerciseView));
    	AddDocTemplate(pDocTemplate);
    
    	CCommandLineInfo cmdInfo;
    
    	// Dispatch commands specified on the command line
    	if (!ProcessShellCommand(cmdInfo))
    		return FALSE;
    
    	// The one and only window has been initialized, so show and update it.
    	m_pMainWnd->ShowWindow(SW_SHOW);
    	m_pMainWnd->UpdateWindow();
    
    	return TRUE;
    }
    
    // -- Frame Map -- //
    IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    	ON_WM_CREATE()
    END_MESSAGE_MAP()
    
    // -- Document Map -- //
    IMPLEMENT_DYNCREATE(CExerciseDoc, CDocument)
    BEGIN_MESSAGE_MAP(CExerciseDoc, CDocument)
    	
    END_MESSAGE_MAP()
    
    // -- View Map --
    IMPLEMENT_DYNCREATE(CExerciseView, CEditView)
    BEGIN_MESSAGE_MAP(CExerciseView, CEditView)
    	
    END_MESSAGE_MAP()
    
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    	// Call the base class to create the window
    	if( CFrameWnd::OnCreate(lpCreateStruct) == 0)
    		return 0;
    
    	return -1;
    }
    
    CExerciseApp theApp;
  16. Test the application
     
    New Document/View application
  17. Close it using its System Close button and return to MSVC

SDI Improvements

 

SDI Improvements: The Application 

To make your programming experience a little faster and efficient, the framework provides many other features for each class used in an application.

The Application: The programs we will create in this book use classes of the Microsoft Foundation Classes (MFC) library. MFC classes are created is various libraries called DLLs. In order to use MFC objects in your application as opposed to non-MFC objects, you must let the compiler know. This is done by specifying that you want to Use MFC In A Shared DLL, as we have done so far. Additionally, if you want your windows to have a 3-D appearance, call the Enable3dControls() method. If you do not want the 3-D appearance, call the Enable3dControlsStatic() method. The best way to deal with this is to ask the compiler to check if you had allowed using MFC in a shared DLL or not, and then tell the compiler which of these two functions to execute. This is done using a #ifdef preprocessor in your InitInstance() method. Here is an example:

#include <afxwin.h>

class CSimpleFrame : public CFrameWnd
{
public:
	CSimpleFrame()
	{
		// Create the window's frame
		Create(NULL, "Windows Application");
	}
};

class CSimpleApp : public CWinApp
{
public:
	BOOL InitInstance();
};

BOOL CSimpleApp::InitInstance()
{
#ifdef _AFXDLL
    Enable3dControls( );
#else
    Enable3dControlsStatic();
#endif

	CSimpleFrame *Tester = new CSimpleFrame ();
	m_pMainWnd = Tester;

	Tester->ShowWindow(SW_SHOW);
	Tester->UpdateWindow();

	return TRUE;
}

CSimpleApp theApp;

To provide your application the ability to create a new document, the CWinApp class provides the OnFileNew() method. Its syntax is:

afx_msg void OnFileNew();

To use this method, create a menu item identified as ID_FILE_NEW. You should also create a prompt for it so the menu item can be added to the string table. This menu item is traditionally and obviously added to the File menu. After creating this menu item, in the message table of the application's source, invoke the CWinApp::OnFileNew() method using the ON_COMMAND() macro. This can be done as follows:

BEGIN_MESSAGE_MAP(CExoApp, CWinApp)
    ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
END_MESSAGE_MAP()

CWinApp also provides an application the ability to easily open a document. This is done using the OnFileOpen() method. In the same way, it can help with printing a document. Here is a summary:

  Menu ID CWinApp Message Map
  ID_FILE_NEW ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
  ID_FILE_OPEN ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
  ID_FILE_PRINT_SETUP ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup

One of the last minute assignment you may need to perform when the user is closing an application is to check if the displayed document is "dirty", that is, if the document has been changed since it was last accessed. To help with this, simply create a menu item identified as ID_APP_EXIT and set a caption accordingly, such as the Exit menu we created in the previous Practical Learning section. It is always helpful to add a prompt to a menu item.

These command messages are implemented in the CWinApp class and can be helpful for your application. If their behavior does not fulfill your goal, you can write your own intended implementation of these menu items.

When using an application over and over, sometimes a user may want to open the last accessed document or at least see a list of the last documents opened on an application. To provide this functionality, create a menu item called ID_FILE_MRU_FILE1 and set its prompt to a string such as Recent File. This menu item is usually added to the File menu above the Exit or quit. The actual list of recent files is stored in an INI file that accompanies your application. To make this list available, you must call the LoadStdProfileSettings() method of the CWinApp class in your InitInstance() method. The syntax of this method is:

void LoadStdProfileSettings(UINT nMaxMRU = _AFX_MRU_COUNT);

By default, this allows the list to display up to four names of documents. This method takes one argument as the number of document names to be displayed in the list. If you do not want the default of 4, specify the nMaxMRU value to your liking.

Practical Learning: Improving the Application

  1. To provide new functionality to the application, in the Resource View, change the IDentifier of the Exit menu item to ID_APP_EXIT and set its Prompt to Quit the application
  2. Add the following menu item under File:
     

    Caption ID Prompt
    &New\tCtrl+N ID_FILE_NEW Create a new document
    &Open...\tCtrl+O ID_FILE_OPEN Open an existing document
    -    
    P&rint Setup... ID_FILE_PRINT_SETUP Change the printer and printing options
    -    
    Recent file ID_FILE_MRU_FILE1 Open this file
    -    
    E&xit ID_APP_EXIT Quit the application; prompt the save document
  3. To allow the application to treat documents, change the InitInstance() implementation as follows:
     
    BEGIN_MESSAGE_MAP(CExerciseApp, CWinApp)
    	ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) 
    	ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
    	ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
    END_MESSAGE_MAP()
    
    BOOL CExerciseApp::InitInstance()
    {
    #ifdef _AFXDLL
        Enable3dControls( );
    #else
        Enable3dControlsStatic();
    #endif
    	LoadStdProfileSettings(6);
    
    	CSingleDocTemplate* pDocTemplate;
    	pDocTemplate = new CSingleDocTemplate(
    		IDR_MAINFRAME,
    		RUNTIME_CLASS(CExerciseDoc),
    		RUNTIME_CLASS(CMainFrame), 
    		RUNTIME_CLASS(CExerciseView));
    	AddDocTemplate(pDocTemplate);
    
    	CCommandLineInfo cmdInfo;
    
    	// Dispatch commands specified on the command line
    	if (!ProcessShellCommand(cmdInfo))
    		return FALSE;
    
    	m_pMainWnd->ShowWindow(m_nCmdShow);
    	m_pMainWnd->UpdateWindow();
    
    	return TRUE;
    }
  4. Test the application and click the various menu items

SDI Improvements: The Document 

The document is actually the object that holds the contents of a file. Based on this role, it is its responsibility to validate the creation of a new file or to store a file that is being saved. To perform these tasks and others, the CDocument class provides various methods you can conveniently add to your application or add and customize their behavior.

Earlier, we saw that, to give the user a convenient means of creating a new document, you can add an ID_FILE_NEW menu identifier and connect it to your application class in the InitInstance() method. Clicking this menu item only allows to initiate the action, the document that is the base of file contents must be aware and validate this action. When a user decides to create a new document or when the application opens and is about to create a new document, you may want to make sure that there is no existing document or you may want to delete the existing one. To take care of this, the CDocument class provides the virtual OnNewDocument() method. Its syntax is:

virtual BOOL OnNewDocument();

When a new file is about to be created, this method is called to initiate it. If everything goes fine and the file is created, the OnNewDocument() method returns TRUE. If the file cannot be initialized, this method returns FALSE or 0. This method, which really behaves like an event, does not create a new file. It is launched when a new file is going to be created and allows you to make it happen or to prevent the creation of a new file.

When a new file has been created, it displays as empty. Such a document is referred to as "clean".

We also saw earlier that, to help the user open an existing document, you can create a menu item identified as ID_FILE_OPEN and associate it with the CWinApp::OnFileOpen() method in your InitInstance() method. This time also, the menu item only provides a convenient way to perform the action. It makes the document available to the application and not to the document. Once a user has initiated the action of opening an existing file, you may want to check that the document not only exists but also can be opened and make its contents available to the user. This job can be handled by the OnOpenDocument() virtual method of the CDocument class. Its syntax is:

virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);

This method usually results from the user clicking File -> Open... on the main menu, communicating a desire to open a file. When this action is initiated, the OnOpenDocument() method retrieves the path of the file as the lpszPathName argument. If the path is valid, that is, if the file exists, you can then check it or perform a last minute task before the file is opened. For example you can use this method to decide how the contents of the file will be displayed or dealt with by the document. You can also use to prevent the user from opening any file or to prevent the user from opening any file at all. You can also use this method to allow the user to open a type of file that your CDocument-derived class would not expect.

If the user has opened an existing file but has not (yet) changed anything in the document, the file is also called "clean". As we will learn eventually, some files can be changed and some do not allow modification. If a document allows the user to change it, he or she can manipulate it as necessary, including adding, deleting, or moving items. Once a user has changed anything on the document, the file is referred to as "dirty". You may want to keep track of such change(s) so you would know eventually if the document needs to be saved. To help you with this, the CDocument class provides the SetModifiedFlag() method. Its syntax is:

void SetModifiedFlag(BOOL bModified = TRUE);

To mark a document as clean or dirty, call the SetModifiedFlag() method. If you pass the bModified argument as TRUE, the document has been changed. Since the TRUE constant is its default value, you can also call the method simply as SetModifiedFlag(). To specify that the document is clean, pass the argument as FALSE. You can call this method whenever you judge necessary. For example, if the user saves the document while working on it but makes another change, you can mark it clean when it has just been saved and mark it dirty if the user changes anything again. At any time, you can check whether the document is dirty or clean using the CDocument::IsModified() method. Its syntax is:

BOOL IsModified();

This method simply checks the document to find out if it has been modified since the last time it was accessed. If the document has been modified, this method would return TRUE. If the document is clean, it returns FALSE.

Another action the user can perform on a document is to send it electronically to an email recipient. To allow the user to send the current document as an email attachment, first an email client (such as MS Outlook) must be installed on the user's computer. Therefore, add a menu item IDentified as ID_FILE_SEND_MAIL. Then, in the message table of your document implementation, add the following two macros:

BEGIN_MESSAGE_MAP(CTestDoc, CDocument)
    ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail)
    ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL, OnUpdateFileSendMail)
END_MESSAGE_MAP

Once a user has finished using a document, he or she would need to close it, which is done by either clicking the System Close button Close or using the main menu (File -> Exit). If the user clicks the System Close button Close, the application would need to close the frame. At this time, the document would call the OnCloseDocument() method. Its syntax is:

virtual void OnCloseDocument();

You can use this method (which really behave as an event) to decide what to do before a frame is closed.

When the user decides to close an application, the document class checks the file to know whether the file is "dirty". If the file is dirty, you may want to ask the user to save or not save the document. As we saw earlier with the ID_APP_EXIT pre-configured menu, the framework can check this setting for you. Also, while using a file, the user may want to save it. If the user is working on a document that was opened from a drive, the document may be saved immediately behind the scenes. If the user is working on a brand new document and decides to save it, you may want to check first if this is possible and what needs to be done in order to save the document.

To help the user save a document, you can create a menu item. Using the Document/View architecture, add a menu item with the identifier ID_FILE_SAVE in the IDR_MAINFRAME common resource name. It is that simple. This menu is usually positioned under File with a caption of &Save.

If the user wants to save a document with a different name and/or a different location, which is usually done by clicking File -> Save As... from the main menu, create a menu item with the ID_FILE_SAVE_AS identifier. This menu item is usually placed under Save in the File main menu. If the user is working on a new document (that has not been saved previously), or if the user working on a document that is marked as Read-Only, or if the user decides to close the application after working on a new document, and if the user decides to save the document, the action would initiate the File -> Save As action.

When the user has decided to save a document, if the document is dirty, a message box would display to the user for a decision. If the user decides to save the document, the CDocument class provides the OnSaveDocument() method to validate the action. The syntax of this method is:

virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);

To save a document, the user must specify where the document would reside. This is communicated as the lpszPathName argument). In reality, as its name suggests, this method is not used to save a document. It allows you to validate the action or desire to save a document. For example, if the user clicks File -> Save on the main menu or if the user is attempting to close a dirty document, you can use this method to check what is going or to deny saving the file.

Serialization is the ability to store data on a drive. Therefore, to actually save a file, the CObject class provides the Serialize() method to its children. To store data of the file, its information is held by a class called CArchive. When saving a file, a CArchive object is passed to the Serialize method as reference, which modifies its value.

CArchive is used to either save the current document or open an existing one. To take care of each, it uses two methods (ReadObject() and WriteObject()). These methods are actually implemented using the extraction operators (>> and <<). Whenever you need to perform serialization in an application, add a method called Serialize() to your document class and pass it a CArchive object reference. The syntax of this method is:

virtual void Serialize(CArchive& ar);

The implementation of this method may depend on the document.

Practical Learning: Improving the Document

  1. In the Resource View, add the following menu items under the File menu:
     

    Caption ID Prompt
    &New\tCtrl+N ID_FILE_NEW Create a new document
    &Open...\tCtrl+O ID_FILE_OPEN Open an existing document
    &Save\tCtrl+S ID_FILE_SAVE Save the current document
    Save &As... ID_FILE_SAVE_AS Save the current document with a new name or location
    -    
    P&rint Setup... ID_FILE_PRINT_SETUP Change the printer and printing options
    -    
    Recent file ID_FILE_MRU_FILE1 Open this file
    -    
    E&xit ID_APP_EXIT Quit the application; prompt the save document
  2. To use a CDocument method, in the header file, add the OnNewDocument() function to the document as follows:
     
    class CExerciseDoc : public CDocument
    {
    	DECLARE_DYNCREATE(CExerciseDoc)
    
    	virtual BOOL OnNewDocument();
    	DECLARE_MESSAGE_MAP()
    };
  3. In the source file, implement the method as follows:
     
    ...
              
    BOOL CExerciseDoc::OnNewDocument()
    {
    	return CDocument::OnNewDocument();
    }
    
    CExerciseApp theApp;
  4. Test the application. Change the content of the empty file and save it
  5. Close the application and return to MSVC.

SDI Improvements: The Frame 

The CWnd::OnCreate() method is used to create a window and it is usually meant to do this using its default configured features. Therefore, anything you want to display on the frame when the application displays, you can do so when creating the application. Therefore, the frame is typically used to create and display the toolbar(s), dialog bar(s), and status bar.

After the frame has been created, if you want to modified something on it, you can do so after it has been created but before it is displayed to the user. To do this, you can use the PreCreateWindow() method of the CWnd class. Its syntax is:

virtual void PreCreateWindow(CREATESTRUCT& cs);

This method takes a reference to CREATESTRUCT class, modifies and returns it with the new characteristics. For example, you can use this method to remove the Minimize and the Maximize system buttons on the title bar as follows:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	cs.style &= ~(WS_MAXIMIZEBOX | WS_MINIMIZEBOX);

	return CFrameWnd::PreCreateWindow(cs);
}

Practical Learning: Improving the Frame

  1. To use the CWnd::PreCreateWindow() virtual method, in the header file, declare it as follows:
     
    class CMainFrame : public CFrameWnd
    {
    	DECLARE_DYNCREATE(CMainFrame)
    	
    	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    	DECLARE_MESSAGE_MAP()
    };
  2. To modify the dimensions of the window at start up, implement the method as follows:
     
    BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
    {
    	// cs.style &= ~(WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
    	cs.cx = 450; // Change the width
    	cs.cy = 350; // Change the height
    
    	return CFrameWnd::PreCreateWindow(cs);
    }
  3. Test the application and return to MSVC

SDI Improvements: The View 

As mentioned already, the object that holds a file's data is the document which is an object of CDocument type. To make this document available to the user to view it, in your view derived class, you should declare a pointer to the document class. The syntax used is:

CDocument* GetDocument() const;

When implementing this method, simply ask the application to return a pointer to the document class used in your application. This can be done by casting the global m_pDocument variable to your document class:

CExerciseDoc* CExerciseView::GetDocument()
{
	return (CExerciseDoc*)m_pDocument;
}

You can use this GetDocument() method in your view class to access the document. For example, the view class can access the contents of the class

If you want to allow the user to print a document, you can add a menu item identified as ID_FILE_PRINT. Then, in the message table of the view class, use the ON_COMMAND macro to associate it to the view parent class of your derived class. You can use this same approach to allow the user to preview document. The identifier for this action is called ID_FILE_PRINT_PREVIEW.

 

Overview of the Multiple Document Interface (MDI)

An application is referred to as Multiple Document Interface, or MDI, if the user can open more than one document in the application without closing it. to provide this functionality, the application provides a parent frame that acts as the main frame of the computer program. Inside of this frame, the application allows creating views that each uses its own frame, making it distinct from the other. Here is Microsoft Word 97 displaying as a classic MDI application:

When using an MDI application, a user can create a document, open another while the first is still displaying, and even create new documents or open additional ones. The documents are stacked in a Z-order axis of a 3-D coordinate system. There are two ways to display the documents of an MDI. If one document is maximized, all documents are maximized. In this case, the main menu of the application would display the system buttons on its right, which creates two ranges of system buttons:

If no document is maximized, each document can assume one of two states: minimized or restored. While the child documents are confined to the borders of the main frame, if a document is or gets minimized, it would display its button inside the main frame. If a document is not minimized, it would display inside the main frame either with its original size or the size the user had given it:

There are two main ways the user can access the different documents of an MDI. If they are maximized (remember that if the user maximizes one document, all the others get maximized also), the menu of the main frame, which we always call the main menu in this book, has an item called Window. When the Window menu item is accessed, it would display a list of the currently opened documents. The user can then select from that list:

A list of windows of an MDI

The separation of the parent and the child frames allows the user to close one child document or all documents. This would still leave the main frame of the application but the user cannot use it as a document. Still, the main frame allows the user to either create a new document or open an existing one.

To manage the differentiation between the parent and its children, the application uses two different menus: one for the main frame and one for a (or each) child. Both menus use the same menu bar, meaning that the application cannot display two frame menus at one time. It displays either the parent menu or a (the) child menu. When at least one document is displaying, the menu applies to the document. If no document is displaying, the main frame is equipped with a simplified menu with limited but appropriate operations. The user can only create a new document, only open an existing document, or simply close the application.

Creating a Multiple Document Interface

We have mentioned that an MDI is an application made of a parent frame and at least one child. Therefore, to create an MDI, because it requires a frame of its own, you should first create a series of resource objects that share a common name as IDR_MAINFRAME. A basic application should have an icon and a menu. The icon can be designed any way you like and you are recommended to create one made of a 32x32 and a 16x16 versions. The menu, since it will be used only when no document is available, should provide functionality that does not process a document. It should allow the user to create a new document, to open an existing document, and to quit the application. Such a menu can have the following items:

Main Frame Menu of an MDI

As done since Lesson 3 (when we studied resources),  these two resources are sufficient to create an application since you can call the CFrameWnd::LoadFrame() method. To create a frame for a Multiple Document Interface, you must derive your frame class from CMDIFrameWnd:

BOOL CExercise1App::InitInstance()
{
	// Create a frame for the window
	CMainFrame* pMainFrame = new CMainFrame;
	if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
		return FALSE;
	m_pMainWnd = pMainFrame;

	CCommandLineInfo cmdInfo;

	// Dispatch commands specified on the command line
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;
	pMainFrame->ShowWindow(m_nCmdShow);
	pMainFrame->UpdateWindow();

	return TRUE;
}

The above code allows only creating a window frame for the parent window. To allow the user to interact with the computer through your MDI, you must provide a template for a document. The child document must have its own frame, distinct from that of the parent. To provide this frame, you have two main alternatives. You can directly use the CMDIChildWnd class or you can derive your own class from CMDIChildWnd:

class CChildFrame : public CMDIChildWnd
{
	DECLARE_DYNCREATE(CChildFrame)

	DECLARE_MESSAGE_MAP()
};

As opposed to a Single Document Interface application, to create a Multiple Document Interface (MDI) application, you use the CMultiDocTemplate class which, like the CSingleDocTemplate class, is derived from CDocTemplate. Therefore, you must declare a pointer variable to CMultiDocTemplate using the new operator, then use its constructor to initialize the template. The syntax of the CMultiDocTemplate constructor is:

CMultiDocTemplate(UINT nIDResource,
                  CRuntimeClass* pDocClass,
                  CRuntimeClass* pFrameClass,
                  CRuntimeClass* pViewClass); 

To make a document different from the parent, you must create an additional series of resources that share a common name other than IDR_MAINFRAME. The most basic resources you should create are a menu and an icon. There are two main types of MDI applications.

One kind of application may use only one particular category of documents such as only text-based. Because text-based documents can include ASCII text files, rich text documents (RTF), HTML files, script-based documents (JavaScript, VCScript, Perl, PHP, etc), etc, such an application may be configured to open only that type of document. To create such an MDI application, in the constructor of the CMultiDocTemplate object that you are using, specify a CView derived class (CEditView, CListView, etc) or your own class you derived from CView or one of its children. Here is an example:

BOOL CMultiEdit1App::InitInstance()
{
	CMultiDocTemplate* pDocEdit;
	pDocEdit = new CMultiDocTemplate(
		IDR_EDITTYPE,
		RUNTIME_CLASS(CMultiEdit1Doc),
		RUNTIME_CLASS(CChildFrame),
		RUNTIME_CLASS(CEditView));
	AddDocTemplate(pDocEdit);

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

	CCommandLineInfo cmdInfo;

	// Dispatch commands specified on the command line
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;
	pMainFrame->ShowWindow(m_nCmdShow);
	pMainFrame->UpdateWindow();

	return TRUE;
}

In this case, the user can create and/or open as many text files as the computer memory would allow.

Another type of MDI application you can create would allow the user to create or open more than one type of document. To provide this functionality, you must specify a template for each type. Again, there are various types of techniques you can use. You can ask the user to select the type of document he or she wants to create when the application starts:

New

To do this, you can create a template for each type of document using a CDocMultiDocTemplate constructor for each:

BOOL CMultiEdit1App::InitInstance()
{
	CMultiDocTemplate* pDocEdit;
	pDocEdit = new CMultiDocTemplate(
		IDR_EDITTYPE,
		RUNTIME_CLASS(CMultiEdit1Doc),
		RUNTIME_CLASS(CChildFrame),
		RUNTIME_CLASS(CEditView));
	AddDocTemplate(pDocEdit);

	CMultiDocTemplate* pDocForm;
	pDocForm = new CMultiDocTemplate(
		IDR_FORMTYPE,
		RUNTIME_CLASS(CMultiEdit1Doc),
		RUNTIME_CLASS(CChildFrame),
		RUNTIME_CLASS(CEmplRecords));
	AddDocTemplate(pDocForm);

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

	CCommandLineInfo cmdInfo;

	// Dispatch commands specified on the command line
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;
	pMainFrame->ShowWindow(m_nCmdShow);
	pMainFrame->UpdateWindow();

	return TRUE;
}

You can also display an empty main frame when the application starts and let the user create a document based on the available types from the main menu. Here is such a menu from Borland Image Editor:

Image Editor

As you can see, creating an MDI is not necessarily too difficult but it can involve a few more steps than an SDI. Therefore, whenever we need an MDI, unless specified otherwise, we  ill use the AppWizard.

The Visual C++ AppWizard

Microsoft Visual C++ provides various wizards to assist you with performing some tasks. Particularly, to create an application, it offers the AppWizard. AppWizard offers a convenient step-by-step series of choices to create various types of applications such as an SDI or an MDI (it offers many other choices of applications).

 

An SDI With AppWizard

To get help in creating a single document interface application, you can use the Visual C++' AppWizard. to do this, from the New Project dialog box (MSVC .Net) or the Projects property page of the New dialog box (MSVC), select MFC Application or MFC AppWizard (exe), specify the name and location, then click OK. On the next dialog box, you must specify the Application Type as Single Document. One of the decisions you must make is to specify the view of application you are creating, that is, you must select CView or one of its children as the base class and click Finish. MFC's AppWizard offers various options to create a starting application as complete as possible. We will view these when needed.

Practical Learning: Creating an SDI

  1. To create a new application, display the New or the New Project dialog box
  2. Select MFC Application or MFC AppWizard (exe)
  3. Specify the application Name as SDI1
     
    New Project
  4. Click OK
  5. Specify the application type as Single Document:
     
  6. Specify the Base Class as CEditView
     
  7. Click Finish
  8. Test the application
     
    SDI Application
  9. Close the window and return to MSVC
Author Note From now on, when you are asked to create an SDI, refer to this section.
 

An MDI With AppWizard

To reduce the amount of work involved with creating an MDI, you can use the MFC's AppWizard. To do this, in the MFC AppWizard, select the Multiple Document radio button.

If you want to create an application that would use a single type of document, select the desired base class.

If you want to create a Windows Explorer type of application, you should select a Single Document application type and select the project style as Windows Explorer. Then, in the Generated Classes section or the MFC AppWizard Step 6, specify a class for the left frame and another base class for the right frame. A classic example would have TreeView class for the left pane and a CListView class for the right side.

If you have created an SDI, you can change it into a multi-view application by adding a CView-derived class to the application. For example, in MSVC 6, if you had created an SDI based on a tree view and you want to create a second view as a form for the right frame, right-click the first node in Class View and click New Form.


Previous Copyright © 2003-2004 FunctionX, Inc. Next