MFC Document/View documentation and enhancements






4.84/5 (51 votes)
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
- Introduction
- Related articles
- MFC classes overview
- Enhancements to
CDocManager
andCDocTemplate
- Acknowledgements
- History
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 Plug-in architecture [^].
- Setting the default printer programically in an MFC application[^].
- Enhanced print preview for MFC[^].
- Print/preview for dialogs in MFC[^].
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:
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 CDcoument
s 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 yourCWinApp::InitInstance()
derived function, this call gets passed directly on to theCDocManager
object. The list ofCDocTemplate
objects is held by this class. The next two functions can be used to get a pointer to any of the registeredCDocTemplate
objects.virtual POSITION GetFirstDocTemplatePosition() const
Because the list of
CDocTemplate
objects is held by theCDocManager
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 registeredCDocTemplate
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 theCDocTemplate
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 yourCDocTemplate
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...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 theCDocTemplate
objects and calls theSaveAllModified()
function on each. These in turn iterate through all their own open documents and call theCDocument::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 allCDocTemplate
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 registeredCDocTemplate
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 ofCDocTemplate
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 callsOpenDocumentFile(NULL)
on the selected template to create a new empty document of the required type. By default, if you only have oneCDocTemplate
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 correctCDocTemplate
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 theGetOpenDocumentCount()
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:
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!