Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Multiple MRU lists

0.00/5 (No votes)
28 Dec 1999 1  
This article describes how to maintain the separate MRU list for each document type that is needed in some applications.

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)
{
   // determine the message number and code (packed into nCode)

   UINT nMsg = 0;
   int nCod = nCode;

   if (nCod != CN_UPDATE_COMMAND_UI)
   {
      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

      OnUpdateRecentFileMenu(pCmdUI);
      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, GetRuntimeClass()->m_lpszClassName);
      }
#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();
   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:

  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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here