Introduction
In the former versions of MFC, there was direct access to the m_pRecentFileList
, and sometimes, it proved useful. This article demonstrates an alternative way of how to add a recent file list to MFC application notepad edit including without LoadStdProfileSettings
and some special conditions like IDR_MAINFRAME
id given to the main menu.
Background
The recent file list in MFC is the fine option. And in the meantime, the MFC uses some special features internally to do certain actions such as the recent file list. Especially in the latest versions of MFC, even direct access to the m_pRecentFileList
has been hidden into internally use. I've used this option in some of my programs, and I've tried to find out some alternatives.
And I've found out the fine CodeProject article Adding a Recent File List to an MFC dialog based application by PPresedo of 2001. The code provided shows that the job has been done by the producer familiar with the internal arrangement of MFC.
The source code of the article above has been designed for the earlier versions of MFC of 14 years ago. Therefore, it is not applicable directly to the latest versions as MSVS-2015. I've adopted this code to MSVS-2015 and I've enclosed the adopted code together with my code in the present article. I believe it will be interesting for the programmers as a sample of perfect use of the internal features of MFC.
Nevertheless, I've done some alternative code without the use of comprehensive knowledge of the internal features of the MFC applicable to any menu and with direct access to recent file lists in the text files and the possibility to arrange simultaneously several recent file lists to different submenus. Sometimes, it may be useful, but classic MFC recent file list is one and only.
Using the Code
The basis of my project MultiRecentFile
has been initialized with the standard MFC procedure for dialog based application.
I've enclosed with the project the ClassUseRescent.cpp, ClassUseRescent.h, GlobUseRescent.cpp files containing the procedures and the variables declaration for multiple recent file lists. If you yourself should try to use this technology, just enclose these files to your project with the menu Project->Add Existing Item... command.
Recent Lists Initialization
While adding your menu, you just type popup name with three letters extension specified (in our sample demo provided herewith, there are "txt", "doc" and "exe" ; ID, number of items and position doesn't matter) and add an entry to that menu which contains the id ID_FILE_MRU_FILE_01
with the string
"Recent File". Please note that it is not default ID_FILE_MRU_FILE1
from MFC. This ID of "Recent File" string
is just for position identification and should be replaced during initialization.
In the ClassUseRescent.h, the ClassUseRescent class
declared:
class ClassUseRecent
{
public:
class ClassUseRecent
{
public:
ClassUseRecent(CMenu * pMenu, int nOrder, int nLimit, int nResent);
~ClassUseRecent(void void UpdateRecentArray(const CString& strS);
void UpdateRecentArray(const CString& strS);
void UpdateRecentMenu(void);
void ReadRecentFiles(const CString& strFile);
void SaveRecentFiles(const CString& strFile);
CMenu * m_pMenu; int m_nOrder; int m_nLimit; int m_nRecent; BOOL m_bChange; CString m_ext; CStringArray m_recentFileArray; CString m_mruListFile; int *m_MRU; };
In the MultiRecentFile.h, two variables are declared for multi recent lists technology implementation:
ClassUseRecent * m_pCurClassUseRecent; CObList m_classUseRecent;
In the MultiRecentFileDlg.cpp in the OnInitDialog procedure, the initialization of the recent file lists is performed. First, the default Windows' user directory with this application name MultiRecentFile
is created (or just picked up if it exists).
CString tDir = CreateTempDir(AfxGetApp()->m_pszExeName); if (!tDir.GetLength()) AfxMessageBox(_T("Failed to Create TempDir for ") + (CString)AfxGetApp()->m_pszExeName);
All the items of the main menu are checked one by one if in its' Submenu "Recent File" string
exists and modify ID for identification and create the buttons of the Tab Control with the names that are the same as in the main menu:
CTabCtrl * pTab = (CTabCtrl *)GetDlgItem(IDC_TAB1); int nOrder = 0; CMenu * pMenu = GetMenu(); for (int i = 0; i < pMenu->GetMenuItemCount(); i++)
{
CMenu * pM = pMenu->GetSubMenu(i);
if(pM->GetMenuItemCount()) if(pM->ModifyMenu(ID_FILE_MRU_FILE_01, MF_BYCOMMAND, (UINT)(0xF000 + 0x10 * nOrder)))
{
ClassUseRecent * pCl = new ClassUseRecent(pM, nOrder, 16, 10); m_classUseRecent.AddTail((CObject *)pCl); CString tString;
pMenu->GetMenuString(i, tString, MF_BYPOSITION); pCl->m_ext = tString.Right(3); pCl->m_mruListFile = tDir + _T("\\") + (CString)AfxGetApp()->m_pszExeName
+ _T("_") + pCl->m_ext + _T(".txt");
pTab->InsertItem(nOrder++, tString); //insert current menu name in Tab Control
}
if (m_classUseRecent.GetCount()) //To appoint first item as current in use:
m_pCurClassUseRecent = (ClassUseRecent *)m_classUseRecent.GetHead();
Now, read the existing recent file lists and input them in corresponding submenus:
for (POSITION pos = m_classUseRecent.GetHeadPosition(); pos != NULL;)
{
ClassUseRecent * ptr = (ClassUseRecent *)m_classUseRecent.GetNext(pos);
ptr->ReadRecentFiles(ptr->m_mruListFile); ptr->UpdateRecentMenu(); }
UpdateListBox(); if(m_pCurClassUseRecent != NULL)
m_num_recent = m_pCurClassUseRecent->m_nRecent; UpdateData(FALSE);
Recent Lists Handling
The gimmick of the application are the first two procedures UpdateRecentArray(CString strS)
and UpdateRecentMenu()
. The job of the UpdateRecentArray
is to arrange the recent file list m_recentFileArray
order after implementation of the new string
strS
:
- If
strS
is empty, just arrange the number of string
s in the array less or equal to global var m_nRecent
and return;
- Find out the order number of the
string
in m_recentFileArray
which is equal to strS
;
- If number exists, then replace the
string
with the previous one by one from this place upto the beginning of the array
- If no equal
string
is found and the size of array is later than the limit installed, then append a new string
to the array; replace the string
with the previous one by one from the end upto the beginning of the array
- Replace the first
string
with the strS
void ClassUseRecent::UpdateRecentArray(const CString& strS)
{
if (!strS.GetLength()) {
int remLen = m_recentFileArray.GetSize();
while (m_recentFileArray.GetSize() > m_nRecent) m_recentFileArray.RemoveAt(m_recentFileArray.GetUpperBound()); m_bChange = remLen != m_recentFileArray.GetSize();
return;
}
BOOL bFind = FALSE;
for (int i = 0; i < m_recentFileArray.GetSize(); i++) { if (m_recentFileArray[i] == strS)
{
if (!i)
{
}
bFind = TRUE; for (int j = i; j > 0; j--) m_recentFileArray.SetAt(j, m_recentFileArray.GetAt(j - 1));
break;
}
}
if (!bFind) {
if (m_recentFileArray.GetSize() < m_nRecent) m_recentFileArray.Add(strS); for (int i = (int)m_recentFileArray.GetUpperBound(); i > 0; i--) m_recentFileArray.SetAt(i, m_recentFileArray.GetAt(i - 1)); } if (m_recentFileArray.GetSize())
m_recentFileArray.SetAt(0, strS); m_bChange = TRUE;
}
The job of the UpdateRecentMenu()
is to update the popup menu with the new m_recentFileArray
performance:
- Find the position of the first recent file in the popup menu
- Delete all recent file
string
s from menu
- If the recent file list is not empty, insert the
string
s of the current recent file list one by one before the position found self increased
- If the recent file list is empty, insert before the position found the gray
string
"Recent File"
void ClassUseRecent::UpdateRecentMenu(void)
{
UINT pos = 0;
for (int i = 0; i < m_pMenu->GetMenuItemCount(); i++)
if (m_pMenu->GetMenuItemID(i) == m_MRU[0])
{
pos = i; break;
}
for (int i = 0; i < m_nLimit; i++)
m_pMenu->DeleteMenu(m_MRU[i], MF_BYCOMMAND); if (m_recentFileArray.GetSize()) for (int i = 0; i < m_recentFileArray.GetSize(); i++)
{
CString dString = m_recentFileArray.GetAt(i);
dString = MyFolderString(dString, 15);
CString nString; nString.Format(_T("%2d "), i + 1);
dString = nString + dString; m_pMenu->InsertMenu(pos++, MF_STRING | MF_BYPOSITION |
MF_ENABLED, m_MRU[i], dString); }
else m_pMenu->InsertMenu(pos, MF_STRING | MF_BYPOSITION |
MF_GRAYED, m_MRU[0], _T("Recent File"));
}
Procedure CMultiRecentFileDlg::OpenMyFile(CString strFile)
is to be created by the user himself to open file handling. In my application, just for demonstration purposes, the file is opened as by the Windows Browser touch:
void CMultiRecentFileDlg::OpenMyFile(CString strFile)
{
if (m_pCurClassUseRecent != NULL)
{
m_pCurClassUseRecent->UpdateRecentArray(strFile);
m_pCurClassUseRecent->UpdateRecentMenu();
UpdateListBox();
}
int nn = strFile.ReverseFind(_T('\\'));
CString strP;
CString strF = strFile;
if (nn > 0)
{
strP = strFile.Left(nn);
strF = strFile.Mid(nn + 1);
}
ShellExecute(NULL, NULL, strF, NULL, strP, SW_SHOWDEFAULT);
}
Please note that the pair of commands UpdateRecentArray(strFile); UpdateRecentMenu()
are the equivalent to AfxGetApp()
->AddToRecentFileList(strFile)
in standard MFC procedures.
In order to remember the recent file list after the dialog completed, the procedure of the Recent Lists saving is enclosed with the application exit:
void CMultiRecentFileDlg::OnCancel()
{
for (POSITION pos = m_classUseRecent.GetHeadPosition(); pos != NULL;)
{
ClassUseRecent * ptr = (ClassUseRecent *)m_classUseRecent.GetNext(pos);
ptr->SaveRecentFiles(ptr->m_mruListFile);
}
CDialogEx::OnCancel();
}
Menu Message Handlers
Menu handlers are created by Class Wizard and overwritten for recent file lists handling.
Selection of one of the recent file string
s with nID
calculates the number order of the recent list and order number of the file name string
in the recent file array and opens the file with the name selected:
BOOL CMultiRecentFileDlg::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (nCode == 0xffffffff)
if ((nID >= 0xF000) && (nID < 0xF100))
{
int nn = (nID - 0xF000) / 0x10;
int nf = (nID - 0xF000) % 0x10;
POSITION pos = m_classUseRecent.FindIndex(nn);
ClassUseRecent * pCl = (ClassUseRecent *)m_classUseRecent.GetAt(pos);
if (pCl != NULL)
m_pCurClassUseRecent = pCl;
OpenMyFile(pCl->m_recentFileArray[nf]);
}
return CDialogEx::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
Selection of the one of the Submenus with nID
calculates the number order of the recent list and highlighted Tab Control Button concerned and updates the ListBox
Control:
void CMultiRecentFileDlg::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
{
CDialogEx::OnMenuSelect(nItemID, nFlags, hSysMenu);
CMenu * pMenu = GetMenu();
if(nFlags != 0xffff)
if (nFlags & MF_POPUP)
{
CMenu * pM = pMenu->GetSubMenu(nItemID);
if (pM != NULL)
if(pM != m_pCurClassUseRecent->m_pMenu)
{
for (POSITION pos = m_classUseRecent.GetHeadPosition(); pos != NULL;)
{
ClassUseRecent * ptr =
(ClassUseRecent *)m_classUseRecent.GetNext(pos);
if (ptr->m_pMenu == pM)
{
m_pCurClassUseRecent = ptr;
UpdateListBox();
break;
}
}
CString mString;
pM->GetMenuString(ID_FILE_RESTOREDEFAULT, mString, MF_BYCOMMAND);
pM->ModifyMenu(ID_FILE_RESTOREDEFAULT, MF_STRING | MF_BYCOMMAND
|m_pCurClassUseRecent->m_bChange ? MF_ENABLED : MF_GRAYED,
ID_FILE_RESTOREDEFAULT, mString);
}
}
}
Application Demo Controls
Dialog box menu and some special controls arranged in order to demonstrate the work with your own recent file list:
- Menu File->Open - Adding the file opened to the head of recent file list and the file to be opened as by the Windows Browser touch
- Menu File->Any Recent File - Doing the same
- Menu Edit Recent->Open Recent - Loading the existing recent file list as current file list and adding the file opened to the head of recent file list
- Menu Edit Recent->Save Recent - Saving the current recent file list into the text file specified and adding the file pathname to the head of recent file list
- Menu Edit Recent->Clear Selected - Removing the files selected in the list box control from the recent file list
- Menu Edit Recent->Clear All - Removing all the files in the list box control and from the recent file list
- Menu Edit Recent->Restore Default - Loading the last saved recent file list as current file list corresponding
- Tab Control - Switch back and forth recent file lists concerned and indicating the current file list
- List Box Control - To show all the pathways complete of the current file list; mouse double click - the single file touched opened
- Check Box Control - To select all the
string
s in the list box control
- Edit Control - To specify the limit of the number of files in the current recent file list
- Ok Button Control - To open the files selected in the list box control from the recent file list
Points of Interest
I believe that the alternative way of the recent file list handling should be interesting for some programming people. Anyway, it is clear and keep your control in all the stages. The standard MFC recent file list performance is fine for people familiar with the MFC gimmicks and tricks from the internal side. And some time, this standard MFC performance occurred too complicated and has too many restrictions and conditions as it is worth doing something yourself.
And one remark more: the project has been created with MSVS-2015 pro; nevertheless, in the Properties the Instruments for MSVS-2010 selected in order to keep this application compatible with Windows-XP - some people are still using it. However, the set of procedures from ClassUseRescent.cpp is compatible with any former versions of MFC MSVS.
Acknowledgements
Many thanks to the CodeProject Mentor Team for their kind cooperation in my article and source correction, especially to Jochen Arndt. It is a great pleasure to be in touch with people who know all the problems from inside.