Click here to Skip to main content
15,861,340 members
Articles / Desktop Programming / MFC
Article

MFC Document/View documentation and enhancements

Rate me:
Please Sign up or sign in to vote.
4.84/5 (53 votes)
15 Oct 2003CPOL11 min read 197.1K   1   160   20
An extension to the MSDN documentation and a list of tips and tricks you can do with the MFC Doc/View architecture to enhance your applications.

Table of content

This article is a work in progress. I wanted to get it out initially as it will make me do some more work on it. Note to editors - Please leave this article unedited.

Introduction

The MFC document/view classes are a powerful way for creating applications. I see many questions in the forums about how these classes work and ways to modify/extend their standard behaviour. Here I would like to present some of the knowledge I have of this architecture and ways it can be modified and extended to do what you want it to do.

Some of the MFC classes and the way they work are very well documented in the MSDN, but others are completely missing. The only way to learn about these is to dig directly into the MFC source code and see how they do their job. I will also be presenting some additional documentation on these classes here.

Related articles

A list of related articles which may be of help.

MFC classes overview

The first thing we need to understand about the MFC doc/view architecture is the way that the classes used to control it relate to each other. Below is a basic diagram of how these are arranged:

Image 1

This is a simplistic view of the framework as it does not take into account the different base class view types etc. which the framework supports.

In general it can be seen that we have a single CWinApp class object. This holds a CDocManager object which is used by the MFC to handle all the CDocTemplate objects that you registered with the framework. The CWinApp object also creates a CMainFrame object which is the main window of your application. Every time you open/create a document in your application, a CDocument object of the right type will be created. A pointer to this object will be stored in a list under the corresponding CDocTemplate object which was used to register that document type. When you open a document, the CMainFrame creates a CChildFrame object (an MDI window) which will be used to display the documents view. A CView object will be created as child inside the CChildFrame window and a pointer to the view will also be stored in the view list for the CDocument object just opened.

If you create additional views for your CDocument, additional CChildFrame objects will be created with the correct view type residing within. A pointer to this new view will also be added to the relevant CDcouments objects view list.

The CDocManager class

The CDocManager class is one of MFC's undocumented helper classes. The MFC uses it to manage the list of CDocTemplate/CMultiDocTemplate objects in your application. There is only ever one of these objects in your MFC application. It can be found in CWinApp and is accessed through the m_pDocManager member variable. It is accessed through a pointer to allow you to substitute your own CDocManager derived class for the MFC one if you need to, but to do that, you have to create and set the m_pDocManager variable before MFC creates its own one. This is done in the following function:

void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)
{
    if (m_pDocManager == NULL)
        m_pDocManager = new CDocManager;
    m_pDocManager->AddDocTemplate(pTemplate);
}

So as long as you create and assign your own CDocManager derived object before the first call to AddDocTemplate() in your CWinApp::InitInstance() derived function, yours will be used automatically by the MFC code. Note that the CDocManager object is deleted for you by the MFC code in the CWinApp destructor:

CWinApp::~CWinApp()
{
    // free doc manager
    if (m_pDocManager != NULL)
        delete m_pDocManager;
...

One of the reasons that CDocManager is undocumented is that it is an MFC helper class not usually intended to be changed by the average user. It could be removed or changed any time in the future as MFC is developed further.

So let's take a look at the functions available in the CDocManager class (from AFXWIN.H):

class CDocManager : public CObject
{
    DECLARE_DYNAMIC(CDocManager)
public:

// Constructor
    CDocManager();

    //Document functions
    virtual void AddDocTemplate(CDocTemplate* pTemplate);
    virtual POSITION GetFirstDocTemplatePosition() const;
    virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const;
    virtual void RegisterShellFileTypes(BOOL bCompat);
    void UnregisterShellFileTypes();
    // open named file
    virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName); 
    virtual BOOL SaveAllModified(); // save before exit
    virtual void CloseAllDocuments(BOOL bEndSession);
    virtual int GetOpenDocumentCount();

    // helper for standard commdlg dialogs
    virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle,
            DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);

//Commands
    // Advanced: process async DDE request
    virtual BOOL OnDDECommand(LPTSTR lpszCommand);
    virtual void OnFileNew();
    virtual void OnFileOpen();

// Implementation
protected:
    CPtrList m_templateList;
    int GetDocumentCount(); // helper to count number of total documents

public:
    static CPtrList* pStaticList;       // for static CDocTemplate objects
    static BOOL bStaticInit;            // TRUE during static initialization
    static CDocManager* pStaticDocManager;  // for static CDocTemplate objects

public:
    virtual ~CDocManager();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif
};

So what do each of these functions do? Well, if you look at the code, you can see that in general they implement the actual functionality of the CWinApp class' function calls with the same name/prototype, so going to the standard CWinApp help for the same function name can give you a good idea of what the function actually does. I have also documented these to the best of my knowledge/experience below:

  • CDocManager()

    The constructor for the class. This does not do anything special as all the member variables know how to construct themselves.

  • virtual void AddDocTemplate(CDocTemplate* pTemplate)

    A function you should at least be partially familiar with. When you call CWinAPP::AddDocTemplate in your CWinApp::InitInstance() derived function, this call gets passed directly on to the CDocManager object. The list of CDocTemplate objects is held by this class. The next two functions can be used to get a pointer to any of the registered CDocTemplate objects.

  • virtual POSITION GetFirstDocTemplatePosition() const

    Because the list of CDocTemplate objects is held by the CDocManager object, some way of iterating this list needs to be available, so this function and the next allow you to iterate through the list of registered CDocTemplate objects in your application.

  • virtual CDocTemplate* GetNextDocTemplate(POSITION& pos) const

    Allows you to iterate the CDocTemplate list.

    // typical iteration code is:
    POSITION pos = m_pDocManager->GetFirstDocTemplatePosition();
    while (pos)
    {
        CDocTemplate *pTemplate = m_pDocManager->GetNextDocTemplate(pos);
        ASSERT(pTemplate);
        ASSERT_VALID(pTemplate);
        // do something with the pDocTemplate object
    }
  • virtual void RegisterShellFileTypes(BOOL bCompat)

    This is the actual implementation of the CWinApp::RegisterShellFileTypes() function. The MSDN gives a good description of what this function actually does.

  • void UnregisterShellFileTypes()

    It will undo the results of the above function. No help is available in MSDN about this function for CWinApp.

  • virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName)

    This function will iterate through the list of CDocTemplate objects in your application and see whether the file extension of the file matches those for the CDocTemplate object. If a match is found and the file is not already open in your application, then a new document of the right type is created and the file opened. The file extension used to match document types is defined in your applications string table. If you have registered your CDocTemplate object as follows:

    pDocTemplate = new CMultiDocTemplate(
        IDR_CONTEXTTYPE,
        RUNTIME_CLASS(CContextHelpDoc),
        RUNTIME_CLASS(CChildFrame), // custom MDI child frame
        RUNTIME_CLASS(CContextHelpView));
    AddDocTemplate(pDocTemplate);

    Then the string table entry under IDR_CONTEXTTYPE will have the default file extension contained in it in one of the fields. You usually get to define the first document types file extension during the AppWizard project creation phase...

    Image 2

  • virtual BOOL SaveAllModified()

    This function is again the actual implementation of the CWinApp::SaveAllModified() function. Normally called when the user wants to terminate the current application session, it iterates through all the CDocTemplate objects and calls the SaveAllModified() function on each. These in turn iterate through all their own open documents and call the CDocument::SaveModified() function. If any of these functions return non-zero (usually because the user cancelled the operation through a message box), then the application will not close.

  • virtual void CloseAllDocuments(BOOL bEndSession)

    This function, which is very similar to SaveAllModified() causes all open documents to be closed. Usually used by the frame window when the application is being closed, it could still be used to close all open documents.

  • virtual int GetOpenDocumentCount()

    This function implements the CWinApp::GetOpenDocumentCount() function. It returns the total number of all documents open, for all CDocTemplate objects in your application.

  • virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate)

    The default implementation of CWinApp::DoPromptFileName(), it prompts the user to enter a filename for a new document that is being saved for the first time. It uses the standard filter supplied for each registered CDocTemplate object and also adds the default *.* filter. This is a good one to override if you want to change the basic behaviour of your application, for example, if you were writing a Graphics application that needed to be able to save a picture in multiple file formats, here is where you could get the correct file filter list setup.

  • virtual BOOL OnDDECommand(LPTSTR lpszCommand)

    This function implements the DDE commands to a mainframe usually by Windows Explorer. This is for commands such as to open/print an existing file.

  • virtual void OnFileNew()

    The actual implementation of the CWinApp::OnFileNew(), this function checks the number of CDocTemplate objects registered with the framework. If more than one, a dialog box will be displayed with a ListBox containing the names of the document types (from the string table). Once the user has selected a template type, the framework calls OpenDocumentFile(NULL) on the selected template to create a new empty document of the required type. By default, if you only have one CDocTemplate object registered with the system, then it will automatically create a document of that type.

    If you need to create new empty documents of a specific type without the framework displaying the selection dialog box, you are going to have to call CDocTemplate::OpenDocumentFile(NULL) on the correct CDocTemplate object yourself.

  • virtual void OnFileOpen()
  • int GetDocumentCount()

    This is a protected member function, so you will not be able to call it directly. It seems to be an exact copy of the GetOpenDocumentCount() function, so you don't need to use it anyway.

The CDocTemplate class

The CDocTemplate class handles the list of open documents and the classes that will be used to implement its functionality. It also lets an MDI/SDI interface know the default menu and accelerator keys used by the mainframe when the selected document type is active. First let's see how a CDocTemplate object is registered in the InitInstance() function:

pDocTemplate = new CMultiDocTemplate(
    IDR_YOURDOCTYPE,
    RUNTIME_CLASS(CYourDoc),
    RUNTIME_CLASS(CChildFrame), // custom MDI child frame
    RUNTIME_CLASS(CYourView));
AddDocTemplate(pDocTemplate);

Let's take a look at these parameters:

  • IDR_YOURDOCTYPE

    This parameter is used to identify the documents type menu, accelerator and file extensions. You will find various resources using the same ID, but of different types:

    • Menu: The menu that will be used when your document is active.
    • Accelerator: The accelerator keys for your menu commands/short cuts.
    • String table: This string table entry is used to identify your documents file extension.

The string table entry

The string table entry is a very important part of your application. It is several strings in a set order delimited by the '\n' character. The order of these entries is controlled by the enum:

enum DocStringIndex
{
    windowTitle,        // default window title
    docName,            // user visible name for default document
    fileNewName,        // user visible name for FileNew
    // for file based documents:
    filterName,         // user visible name for FileOpen
    filterExt,          // user visible extension for FileOpen
    // for file based documents with Shell open support:
    regFileTypeId,      // REGEDIT visible registered file type identifier
    regFileTypeName,    // Shell visible registered file type name
};

So, what do each of these example actually do:

    • windowTitle - This entry is only used by the MFC when you register/de-register your document type with the shell.
    • docName - The default name that will be given to any new documents of this type created, appended with a number.
    • fileNewName - If you have more than one document template registered with your application, and a user selects New..., by default the MFC displays a Select document type dialog. This is the name listed by the dialog for this document type in the dialog box.
    • filterName - The file filter that appears in the file Open/Save dialog box, for example My document type (*.mdt)
    • filterExt - The default file extension for this document type. By default your documents can only handle a single file extension for your document type, I'll show you later how to associate several file extensions with a single document template.
    • RegFileTypeID - Used by the shell for your document type.
    • regFileTypeName - Used by the shell to give a description of what the document type is to the user in application such as the Explorer.
  • RUNTIME_CLASS(CYourDoc)

    The document object type that will be created by MFC when a new or existing document is opened.

  • RUNTIME_CLASS(CChildFrame)

    The child frame controlling class in which your view will be displayed. This is the actual MDI window.

  • RUNTIME_CLASS(CYourView)

    The view type that will be displayed in the MDI frame.

    Both the document class and the view class(es) can reference each other through the relevant access functions.

Enhancements to CDocManager and CDocTemplate

Here are a list of things you can do with the CDocManager and CDocTemplate classes to enhance and change their functionality to better support you application.

Opening a new specific document type when you have more than one CDocTemplate registered

If you have more than one CDocTemplate object registered with the framework, when the user clicks ID_FILE_NEW, by default the framework displays a "Select document type" dialog box:

Image 3

In cases such as these, you need to get the correct CDocTemplate object pointer and call OpenDocumentFile(NULL> on it. A good way of doing this would be to define an enum which lists all the document types in the order they are registered and add a function to the CDocManager class, by inheriting your own from it:

enum mdt_MyDocumentTypes
{
    mdt_FirstDocumentType = 0,
    mdt_SecondDocumentType,
    mdt_ThirdDocumentType                // ... add extras as required
};

class CDocManagerEx : public CDocManager
{
    DECLARE_DYNAMIC(CDocManagerEx)
public:
    CDocManagerEx();
    virtual    ~CDocManagerEx();
    CDocument*    CreateNewDocument(int doc_index, CString filename = "");
};

CDocument* CDocManagerEx::CreateNewDocument(int doc_index, CString filename)
{
    // Find the correct document template and create a document from it
    CDocument    *pDoc = NULL;
    if (m_templateList.GetCount() >= doc_index)
    {
        POSITION    pos = m_templateList.GetHeadPosition();
        CDocTemplate *pTemplate = NULL;
        // iterate through the list looking for the required document type
        if (doc_index == 0)
        {
            pTemplate = (CDocTemplate*)m_templateList.GetNext(pos);
        }
        while (pos != NULL && doc_index > 0)
        {
            pTemplate = (CDocTemplate*)m_templateList.GetNext(pos);
            ASSERT_KINDOF(CDocTemplate, pTemplate);
        }
        if (pTemplate != NULL)
        {
            // create the document
            if (filename == "")
            {
                pDoc = pTemplate->OpenDocumentFile(NULL);
            }
            else
            {
                pDoc = pTemplate->OpenDocumentFile(filename);
            }
        }
    }
    // return the document pointer or NULL if failed
    return pDoc;
}

You will also have to substitute your own CDocManager object for that created by default by MFC in CWinApp::InitInstance.

m_pDocManager = new CDocManagerEx; // we replace the default doc manager
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
    IDR_DPASTYPE,
    RUNTIME_CLASS(CSomeDoc),
    RUNTIME_CLASS(CChildFrame), // custom MDI child frame
    RUNTIME_CLASS(CSomeView));
AddDocTemplate(pDocTemplate);

To make use of the new function, you would make a call to the function like this:

(static_cast<CDocManagerEx*>(AfxGetApp()->m_pDocManager))->CreateNewDocument(
                                                          mdt_FirstDocumentType);

with an optional filename as the 2nd parameter if you want to open a specific document.

Associating more than one file extension with a document template

If you need multiple file extensions to be recognized by a document type, you need to extend the CMultiDocTemplate class to recognize these extra extensions. There is one virtual function you need to change.

class CMultiDocTemplateEx : public CMultiDocTemplate
{
    DECLARE_DYNAMIC(CMultiDocTemplateEx)
public:
    CMultiDocTemplateEx(UINT nIDResource, 
            CRuntimeClass* pDocClass, 
            CRuntimeClass* pFrameClass, 
            CRuntimeClass* pViewClass );
    virtual ~CMultiDocTemplateEx();
    virtual Confidence MatchDocType(LPCTSTR lpszPathName, 
                CDocument*& rpDocMatch);
};

CMultiDocTemplateEx::CMultiDocTemplateEx(
        UINT nIDResource, 
        CRuntimeClass* pDocClass, 
        CRuntimeClass* pFrameClass, 
        CRuntimeClass* pViewClass )
    : CMultiDocTemplate(nIDResource, pDocClass, 
                          pFrameClass, pViewClass)
{
}

CMultiDocTemplateEx::~CMultiDocTemplateEx()
{
}

// one draw-back of using our own MatchDocType function is that 
// we never return the "yesAttemptForiegn" confidence
// type anymore. Although I beleive this is a good thing as we 
// should not be trying to open file types we do not
// recognise.
CDocTemplate::Confidence CMultiDocTemplateEx::MatchDocType(
        LPCTSTR lpszPathName, 
        CDocument*& rpDocMatch)
{
    Confidence    con = noAttempt;

    // make sure we have the correct resource string
    m_strDocStrings.LoadString(m_nIDResource) ;

    // code copied from the base class and now also checks
    // for multiple file extensions
    ASSERT(lpszPathName != NULL);
    rpDocMatch = NULL;

    // go through all documents
    POSITION pos = GetFirstDocPosition();
    while (pos != NULL)
    {
        CDocument* pDoc = GetNextDoc(pos);
        if (AfxComparePath(pDoc->GetPathName(), lpszPathName))
        {
            // already open
            rpDocMatch = pDoc;
            con = yesAlreadyOpen;
            break;
        }
    }

    if (con != yesAlreadyOpen)
    {
        // see if it matches our default suffix
        CString strFilterExt;
        if (GetDocString(strFilterExt, CDocTemplate::filterExt) && 
            !strFilterExt.IsEmpty())
        {
            CString extensions(strFilterExt);
            extensions.MakeLower();
            // see if extension matches
            ASSERT(strFilterExt[0] == '.');
            // break of the file extension. if its blank "", set it to ".!!!" 
            // so that an empty file extension can be recognised.
            char ext[_MAX_EXT];
            _splitpath(lpszPathName, NULL, NULL, NULL, ext);
            if (_tcslen(ext) == 0)
            {
                // no file extension, set to ".!!!" 
                // which is an invalid file 
                // extension that should never occur
                // in real life. We onyl use it here to recognise 
                // files without extensions.
                
                // goto a special file extension
                _tcscpy(ext, ".!!!");            
            }
            CString copy(ext);
            copy.MakeLower();
            if (extensions.Find(copy) >= 0)
            {
                // extension matches, looks like ours
                con = yesAttemptNative; 
            }
        }
    }

    return con;
}

Once you have this object type being used in your InitInstance() function, you will be able to update the filterExt field in your string table resources to add in the extra file extensions needed:

\nMyDocType
\nMyDocType
\nMyDocType files (*.ex1;*.ex2;*.ex3;*.)
\n.ex1.ex2.ex3.!!!
\nMyDocType.Document
\nMyDocType Document

Note the special file extension in use of .!!! which is used to recognize empty file extensions.

Acknowledgements

  • Michael Dunn - Who's HTML layouts I blatantly ripped because I did not know how to do them myself (I suck at HTML).

History

  • March 2003 - Initial version.

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Sirius Analytical Instruments
United Kingdom United Kingdom
A research and development programmer working for a pharmaceutical instrument company for the past 17 years.

I am one of those lucky people who enjoys his work and spends more time than he should either doing work or reseaching new stuff. I can also be found on playing DDO on the Cannith server (Send a tell to "Maetrim" who is my current main)

I am also a keep fit fanatic, doing cross country running and am seriously into [url]http://www.ryushinkan.co.uk/[/url] Karate at this time of my life, training from 4-6 times a week and recently achieved my 1st Dan after 6 years.

Comments and Discussions

 
GeneralMy vote of 5 Pin
sebasurfer20-Dec-10 20:49
sebasurfer20-Dec-10 20:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.