August 1999
Code for this article: Aug99CQA.exe (26KB)Paul DiLascia is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at askpd@pobox.com or http://pobox.com/~askpd.
|
Q I have a problem: my MFC application has two CDocument classes. The MRU file list shows all the document files in the same menu. How can I separate the file names into two groups? For example, one for projects and one for documents, like in Visual C++®.
Juan José Núñez
Mahendra Bhagat
A To make the recent file list appear in a menu, all you have to do is add the special command ID ID_FILE_MRU_ FILE1 to your menu. MFC then magically replaces this menu item with the names of the files the user most recently opened, as shown in Figure 1. MFC even stores the names of the files in your application profile (.INI file or system registry), so your app "remembers" them from one session to the next. This is wonderful, but how does it work? And how can you have more than one MRU list? To answer these questions, I have to take you spelunking. I'll start by asking Mahendra's question: where does MFC convert the menu item ID_ FILE_MRU_FILE1 into a list of recent files? |
ON_UPDATE_COMMAND_UI(ID_FILE_MRU_FILE1, OnUpdateRecentFileMenu)
|
void
CWinApp::OnUpdateRecentFileMenu(CCmdUI* pCmdUI)
{
if (m_pRecentFileList == NULL)
pCmdUI->Enable(FALSE);
else m_pRecentFileList->UpdateMenu(pCmdUI);
}
|
LoadStdProfileSettings();
|
This function loads the standard settings from your app's profile, which among other things includes the recent file list. LoadStdProfileSettings even takes an argument: the maximum number of file names. For example, my sample program's InitInstance function has the line |
LoadStdProfileSettings(NMAXMRU);
|
where NMAXMRU is 8. So my recent file list has up to eight entries. LoadStdProfileSettings is a CWinApp function that creates the recent file list and initializes it by reading from the registry. |
// in CWinApp::LoadStdProfileSettings(UINT nMaxMRU)
m_pRecentFileList = new
CRecentFileList(0, szFileSection,
szFileEntry, nMaxMRU);
m_pRecentFileList->ReadList();
|
szFileSection and szFileEntry describe where the recent file information is stored in the registry. Figure 3 explains it. |
void SetPathName(LPCTSTR lpszPathName,
BOOL bAddToMRU = TRUE);
|
The second arg is a flag that tells MFC whether to add the name to the MRU list. If it's TRUE, MFC adds the path name to the recent file list. |
// in CDocument::SetPathName
if (bAddToMRU)
AfxGetApp()-> AddToRecentFileList(m_strPathName);
|
// CWinApp::AddToRecentFileList
m_pRecentFileList->Add(lpszPathName);
|
This is pretty brainless: AddToRecentFileList calls CRecentFileList::Add. Now the next time the user invokes the menu, the new file name appears at the top of the list. |
// CWinApp
ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1,
ID_FILE_MRU_FILE16, OnOpenRecentFile)
|
When the user selects one of the MRU menu items, control passes to CWinApp::OnOpenRecentFile, which tries to open the file by calling OpenDocumentFile with the name of the document. |
// in CWinApp::OnOpenRecentFile(UINT nID)
int nIndex = nID - ID_FILE_MRU_FILE1;
if (OpenDocumentFile((*m_pRecentFileList)[nIndex]) ==
NULL)
m_pRecentFileList->Remove(nIndex);
|
class CMyApp : public CWinApp {
CMruFileManager m_mruFileMgr;
};
|
To create the recent file lists, CMyApp::InitInstance calls CMruFileManager::Add twice. Here's the first call (from MyEdit.cpp in Figure 5): |
m_mruFileMgr.Add(ID_MY_RECENT_CFILE1,
_T("Recent .cpp Files"),
_T("File%d"),
CFileFunc,
NMAXMRU);
|
This creates a recent file list containing up to NMAXMRU file names, using "Recent .cpp Files" as the registry key and "File%d" as the format (see Figure 6). ID_MY_RECENT_ CFILE1 is a resource ID; there are consecutive IDs, ID_ MY_RECENT_CFILE, ID_MY_RECENT_CFILE2, and so on, up to ID_MY_RECENT_CFILE8. CMruFileManager uses this range for the menu's command IDs. |
// in MyEdit.cpp
static BOOL CALLBACK CFileFunc(LPCTSTR lpszPathName)
{
return CompareFilenameSuffix(lpszPathName,
_T(".cpp"));
}
|
CompareFilenameSuffix tests whether the string lpszPathName ends in a particular suffix, such as .cpp. |
BOOL CMyApp::OnCmdMsg(...)
{
if (m_mruFileMgr.OnCmdMsg(...))
return TRUE;
return CWinApp::OnCmdMsg(...);
}
|
This joins the MRU menu manager to the MFC message routing infobahn. For more information about how this works, read my article, "Meandering Through the Maze of MFC Message and Command Routing" (MSJ, July 1995), which is still relevant after four years—amazing! Once you hook up the MRU file manager, it receives ON_COMMAND and ON_UPDATE_COMMAND_UI messages. This lets the CMruFileManager handle its own commands, so your app doesn't have to. |
void CMyApp::AddToRecentFileList(LPCTSTR lpszPathName)
{
if (m_mruFileMgr.AddToRecentFileList(lpszPathName))
return;
CWinApp::AddToRecentFileList(lpszPathName);
}
|
Remember from my earlier discussion that CWinApp:: AddToRecentFileList is the function that adds a file name to the recent file list. But now that there are several lists, how do you tell the file manager which one to use? This is where the filter function comes in. CMruFileManager::AddToRecentFileList loops through all its lists, calling a new function, CRecentFileList2::IsMyKindOfFile, to see if the file name belongs in that list. |
BOOL CMruFileManager::AddToRecentFileList(
LPCTSTR lpszPathName)
{
for (prfl = /*each
CRecentFileList2*/) {
if (prfl-> IsMyKindOfFile(lpszPathName)) {
prfl->Add(szTemp);
return TRUE;
}
}
return FALSE;
}
|
ON_COMMAND_EX_RANGE(0, 0xFFFF, OnOpenRecentFile)
|
Thus CMruFileManager::OnOpenRecentFile gets control for any command in the range 0 to 0xFFFF, which is to say all commands. |
void CMruFileManager::OnUpdateRecentFileMenu(
CCmdUI* pCmdUI)
{
CRecentFileList2* prfl = FindRFL(pCmdUI->m_nID);
if (prfl) {
pCmdUI->Enable(prfl->GetSize()>0);
prfl->UpdateMenu(pCmdUI);
} else {
pCmdUI->ContinueRouting();
}
}
|
UpdateMenu is the same MFC function I showed you before; ContinueRouting is a special CCmdUI trick you may or may not know about. It tells MFC to keep routing the CN_UPDATE_COMMAND_UI event. ContinueRouting is required because you don't want CMruFileManager::OnUpdateRecentFileMenu to gobble all IDs, only the ones destined for one of its recent file lists. |
Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com |
From the August 1999 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe. |