Multiple MRU lists (2)

In some applications, maintaining a separate MRU list for each document type is needed. This article describes how to do it and includes a sample project.

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 can not 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)
	// determine the message number and code (packed into nCode)
	UINT nMsg = 0;
	int nCod = nCode;

		nMsg = HIWORD(nCod);
		nCod = LOWORD(nCod);

	// for backward compatibility HIWORD(nCode)==0 is WM_COMMAND
	if (nMsg == 0)
		nMsg = WM_COMMAND;

	if ((nCod==CN_UPDATE_COMMAND_UI) && (nID>=m_nMenuId) && (nID<=m_nMenuId+MRU_RANGE))	
		BOOL bResult = TRUE; // default is ok
		ASSERT(pExtra != NULL);
		CCmdUI* pCmdUI = (CCmdUI*)pExtra;
		ASSERT(!pCmdUI->m_bContinueRouting);    // idle - not set
		bResult = !pCmdUI->m_bContinueRouting;
		pCmdUI->m_bContinueRouting = FALSE;     // go back to idle
		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,
#endif //_DEBUG

		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();
The menu items corresponding to the first MRU items 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:

  1. 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.
  2. 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".
  3. Include the files CDocTemplateEx.h and CDocTemplateEx.cpp in your project. Add #include directives for the header file in the document and application classes.
  4. Override the SetPathName function in the documents with separate MRU lists as described above. You can use the Class Wizard.
  5. Override the OnCmdMsg function in the application class as described above. You can use the Class Wizard.
  6. Modify the constructors of your document templates (by default in InitInstance) so you construct CDocTemplateEx objects instead of the default templates.
  7. 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.

Download Project Sample

Last updated: 19 March 1999


Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds