Introduction
Basically, what I did was to transfer all the MRU functionality from the application class to the document template. The files DocTemplateEx.h and DocTemplateEx.cpp contain the declaration and implementation of a CDocTemplate
descendant class with added MRU functionality.
This approach brought two problems. First, how to assign the menu ID for the first file in the list to the document template. In MFC, where you have only one MRU list there are no problems, the statically defined constant ID_FILE_MRU_FILE1
is used. When each document template has its own MRU list, the menu identifier is different for each template instance, so it should be a class member initialized somewhere (we do it in the constructor). The consequence of this is that MFC message maps cannot be used (MFC message maps need global identifiers). This was solved with a hack in the CDocTemplate::OnCmdMsg
member function, which is responsible for command message routing:
BOOL CDocTemplateEx::OnCmdMsg(UINT nID, int nCode,
void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
UINT nMsg = 0;
int nCod = nCode;
if (nCod != CN_UPDATE_COMMAND_UI)
{
nMsg = HIWORD(nCod);
nCod = LOWORD(nCod);
}
if (nMsg == 0)
nMsg = WM_COMMAND;
if ((nCod==CN_UPDATE_COMMAND_UI) && (nID>=m_nMenuId)
&& (nID<=m_nMenuId+MRU_RANGE))
{
BOOL bResult = TRUE;
ASSERT(pExtra != NULL);
CCmdUI* pCmdUI = (CCmdUI*)pExtra;
ASSERT(!pCmdUI->m_bContinueRouting);
OnUpdateRecentFileMenu(pCmdUI);
bResult = !pCmdUI->m_bContinueRouting;
pCmdUI->m_bContinueRouting = FALSE;
return bResult;
}
if ((nMsg==WM_COMMAND) && (nCod==CN_COMMAND) &&
(nID>=m_nMenuId) && (nID<=m_nMenuId+MRU_RANGE))
{
#ifdef _DEBUG
if (afxTraceFlags & traceCmdRouting)
{
TRACE2("SENDING command id 0x%04X to %hs target.\n",
nID, GetRuntimeClass()->m_lpszClassName);
}
#endif
return OnOpenRecentFile(nID);
}
return CMultiDocTemplate::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
The second problem was that when there are no documents opened, the default message routing mechanism does not route messages to the document templates. Once again, this was solved with a hack of the OnCmdMsg
function, this time on the application class.
BOOL CMyApp::OnCmdMsg(UINT nID, int nCode,
void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
BOOL bResult = CWinApp::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
if (bResult)
return TRUE;
POSITION pos = m_pDocManager->GetFirstDocTemplatePosition();
while (pos != NULL)
{
CDocTemplate* pTemplate = m_pDocManager->GetNextDocTemplate(pos);
ASSERT_KINDOF(CDocTemplate, pTemplate);
if (pTemplate->IsKindOf(RUNTIME_CLASS(CDocTemplateEx)))
{
if ((nID>=((CDocTemplateEx *)pTemplate)->GetMenuID()) &&
(nID<=((CDocTemplateEx *)pTemplate)->GetMenuID()+MRU_RANGE))
bResult |= pTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
}
return bResult;
}
The functionality added to the document template includes loading and saving the MRU list in the registry. The MRU list is saved under a key named after the document name, obtained with the CDocTemplate::GetDocString
function. This occurs transparently, no further action is required from the programmer.
When documents of a certain type have a separate MRU list, then in the constructor of the document template for this type of documents (commonly on InitInstance
), 2 new parameters should be included: the menu identifier for the first item in the MRU list and the maximum number of files that will exist on the list (must be less than 16). If there is no need of a separate MRU list, just ignore these parameters.
The document class should be slightly modified to avoid default MRU mechanism. Override the document SetPathName
function as follows:
void CMyDoc::SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU)
{
CDocument::SetPathName(lpszPathName, FALSE);
CDocTemplateEx* pDocTemplate = (CDocTemplateEx *)GetDocTemplate();
pDocTemplate->AddToRecentFileList(lpszPathName);
}
The menu items corresponding to the first MRU item of each document type can be placed at any place in the menu, including submenus. If an MRU item is the first item in a submenu, then the submenu will be inactivated when the MRU list is empty. The menu items for subsequent files in the MRU list will take IDs following the first one. For example, if the ID for the first item is ID_FILE_DOC1_MRU1
, the ID of the second will be ID_FILE_DOC1_MRU1
+1. To guarantee that all MRU items will show their action text in the status bar, a string table should be added to the resources, with the MRU menu item IDs and text such as "Open this file".
You would probably want to disable default CWinApp
MRU processing. To do that, call CWinApp::LoadStdProfileSettings
with 0 as parameter in InitInstance
. Also, remove all default MRU menu items from the resource menus.
The following is a step by step list to implement separate MRU lists for each document type:
- Edit your menus in the resource editor. Create the first menu items for each MRU list. Don't forget that the values following the ID ranging from ID to ID+15 are reserved. If needed, delete default MFC MRU items.
- Create strings in the string table with IDs corresponding to the IDs of all menu items in the MRU list (not just the first!) and values such as "Open this file".
- Include the files CDocTemplateEx.h and CDocTemplateEx.cpp in your project. Add
#include
directives for the header file in the document and application classes.
- Override the
SetPathName
function in the documents with separate MRU lists as described above. You can use the Class Wizard.
- Override the
OnCmdMsg
function in the application class as described above. You can use the Class Wizard.
- Modify the constructors of your document templates (by default in
InitInstance
) so you construct CDocTemplateEx
objects instead of the default templates.
- Add to the constructor, 2 parameters at the end: the menu ID of the first item in the corresponding MRU list and the maximum number of items maintained in the list (if you omit the last,
_AFX_MRU_COUNT
is taken by default).
The provided sample code is for a MDI application. In a SDI application, you should change all references to CMultiDocTemplate
in the DocTemplateEx.h and DocTemplateEx.cpp files to CDocTemplate
. The rest remains the same.
One last word about the sample project. This project implements 2 MRU popups for 2 different document types. To check that the list works for the documents of type Doc2, you need to create new documents of that type and save it. The open dialog opens Doc1 type documents only.