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

Your Own Recent File List(s)

0.00/5 (No votes)
5 Apr 2016 1  
The alternative way of recent files handling with the direct access to the list of the files notepad edit including.

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; //Submenu with unicum Recent File List 
    int m_nOrder;  //Order number of this set in CObList m_classUseRecent in the main dialog
    int m_nLimit;  //Max limit of file numbers in this  set
    int m_nRecent; //limit of file's number to be shown in the recent file list
    BOOL m_bChange; //Flag of original recent file list changed
    CString m_ext;  //default file extension 
    CStringArray m_recentFileArray;    //Array of strings with the complete pathway of the file(s)
    CString m_mruListFile;//Default filename in the directory GetTempPath 
    			//procedure specified  with the latest recent file list
    int *m_MRU;  //Array with the ID of the order numbers of the files in the recent file list
};

In the MultiRecentFile.h, two variables are declared for multi recent lists technology implementation:

ClassUseRecent *  m_pCurClassUseRecent; //Current set of variables for the current Submenu
CObList  m_classUseRecent; // Object list of the all sets of variables corresponding to all Submenus
                              // with the item "Recent File" originally installed

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); //In the default Window user directory
    if (!tDir.GetLength())                                   // with this application name created
        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); //reference to Tab Control
int nOrder = 0; //Order number of the set in CObList m_classUseRecent in the main dialog
CMenu * pMenu = GetMenu(); //Get Main Menu
for (int i = 0; i < pMenu->GetMenuItemCount(); i++)
{
    CMenu * pM = pMenu->GetSubMenu(i);
    if(pM->GetMenuItemCount()) //find if "Recent File" string in submenu exists and modify ID:
        if(pM->ModifyMenu(ID_FILE_MRU_FILE_01, MF_BYCOMMAND, (UINT)(0xF000 + 0x10 * nOrder)))
        {
            ClassUseRecent * pCl = new ClassUseRecent(pM, nOrder, 16, 10);// new ClassUseRecent
                            //item created
            m_classUseRecent.AddTail((CObject *)pCl);   //and appended to the
                            //Object List m_classUseRecent.
            CString tString;
            pMenu->GetMenuString(i, tString, MF_BYPOSITION); //Get the name of the Menu item
            pCl->m_ext = tString.Right(3);             //and read extension specified
            //Default file name in the Windows special directory with the latest recent file list:
            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);//Read m_recentFileArray of the item specified
     ptr->UpdateRecentMenu();     //Update menu with  m_recentFileArray
}
 UpdateListBox();                    //Input m_recentFileArray into list box
 if(m_pCurClassUseRecent != NULL)
 m_num_recent = m_pCurClassUseRecent->m_nRecent;   //set current number of recent files as default
 UpdateData(FALSE);                    //show this number in control edit window IDC_EDIT_NUM_RECENT

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 strings 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())   	//if strS is empty just arrange the number of strings 
				//in array less or equal to global var m_nRecent
    {
        int remLen = m_recentFileArray.GetSize();
        while (m_recentFileArray.GetSize() > m_nRecent)	//if the number of strings in array 
							//greater than global var m_nRecent
            m_recentFileArray.RemoveAt(m_recentFileArray.GetUpperBound());//remove the last string        
            m_bChange = remLen != m_recentFileArray.GetSize();
        return;
    }
 BOOL bFind = FALSE;
    for (int i = 0; i < m_recentFileArray.GetSize(); i++)//find out the order number of the string 
							//in m_recentFileArray 
    {                                                   // which is equal to strS;        
        if (m_recentFileArray[i] == strS)
        {
            if (!i)
            {
                //m_bChange = FALSE;
                //return;
            }
bFind = TRUE; //if the number exist then replace the string with the previous one by one    
            for (int j = i; j > 0; j--)  //from this place upto the beginning of the array. 
                m_recentFileArray.SetAt(j, m_recentFileArray.GetAt(j - 1));
            break;
        }
    }
    if (!bFind)                                         //if no equal string found
    {
        if (m_recentFileArray.GetSize() < m_nRecent) 	//and the size of array is later than 
							//limit installed
            m_recentFileArray.Add(strS);             	//append a new string to the array;     
        for (int i = (int)m_recentFileArray.GetUpperBound(); i > 0; i--) //replace the string 
									//with the previous one by one
            m_recentFileArray.SetAt(i, m_recentFileArray.GetAt(i - 1)); //from the end 
									//upto the beginning of the array
    }//if (!bFind)
    if (m_recentFileArray.GetSize())
        m_recentFileArray.SetAt(0, strS); //replace the first string with the 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 strings from menu
  • If the recent file list is not empty, insert the strings 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;   //Find the position of the first recent file in the popup menu
            break;
        }
    for (int i = 0; i < m_nLimit; i++)
        m_pMenu->DeleteMenu(m_MRU[i], MF_BYCOMMAND); //delete all recent file strings from menu
       if (m_recentFileArray.GetSize())               // if the  recent file list is not empty
        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; //insert the strings of the current recent file list one by one
            m_pMenu->InsertMenu(pos++, MF_STRING | MF_BYPOSITION | 
            MF_ENABLED, m_MRU[i], dString);//before the position found
        }
    else  // if the recent file list is empty insert 
          // before the position found gray string "Recent File"
        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)
    {
        //equivalent to AfxGetApp()->AddToRecentFileList(LPCTSTR lpszPathName)
        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 strings 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 strings 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.

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