您自己的最近文件列表






2.79/5 (6投票s)
处理最近文件的另一种方法,可以直接访问记事本编辑中包含的文件列表。
- 下载源码 - 104.4 KB
- 下载源码 - 33.4 KB (由 PPresedo(2001) 适配至 MSVS-2015)
- 下载演示 - 40.1 KB
引言
在 MFC 的早期版本中,可以直接访问 m_pRecentFileList
,有时这很有用。本文演示了一种如何在 MFC 应用程序记事本编辑中添加 **最近文件列表** 的替代方法,无需使用 LoadStdProfileSettings
和一些特殊条件,例如将 **主菜单** 的 ID 设置为 ID_MAINFRAME
。
背景
MFC 中的最近文件列表是一个很棒的功能。同时,MFC 内部使用一些特殊功能来执行特定操作,例如最近文件列表。尤其是在 MFC 的最新版本中,甚至 m_pRecentFileList
的直接访问也已被隐藏起来供内部使用。我已经在我的一些程序中使用了这个功能,并且尝试寻找一些替代方案。
我找到了一个很棒的 **CodeProject** 文章 为基于 MFC 对话框的应用程序添加最近文件列表,由 **PPresedo** 于 2001 年撰写。提供的代码表明,这项工作是由熟悉 MFC 内部结构的技术人员完成的。
上述文章的源代码是为 14 年前的早期 MFC 版本设计的。因此,它不能直接适用于最新的版本,例如 MSVS-2015。我已将此代码适配到 MSVS-2015,并在本文中附带了适配后的代码和我自己的代码。我相信它将作为 MFC 内部功能的完美使用示例,对程序员来说会很有趣。
尽管如此,我还是做了一些替代代码,无需深入了解 MFC 的内部机制,适用于任何菜单,并且可以直接访问文本文件中的最近文件列表,同时可以为不同的子菜单安排多个最近文件列表。有时这可能很有用,但经典的 MFC 最近文件列表只有一个。
Using the Code
我的项目 MultiRecentFile
的基础是使用标准 MFC 对话框应用程序过程初始化的。
我已将 ClassUseRescent.cpp, ClassUseRescent.h, GlobUseRescent.cpp
文件包含在项目中,这些文件包含用于多最近文件列表的过程和变量声明。如果您自己尝试使用此技术,只需通过菜单 Project->Add Existing Item.. 命令将这些文件添加到您的项目中。
最近列表初始化
在添加菜单时,只需键入弹出菜单名称并指定三字母扩展名(在我们提供的示例演示中,有“txt”、“doc”和“exe”;ID、项目数量和位置无关紧要),然后在该菜单中添加一个包含 ID ID_FILE_MRU_FILE_01
的条目,其字符串为“Recent File”。请注意,这并非 MFC 中默认的 ID_FILE_MRU_FILE1
。这个“Recent File”字符串的 ID 仅用于位置标识,将在初始化期间被替换。
在 ClassUseRescent.h 中,声明了 ClassUseRescent class
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
};
在 MultiRecentFile.h 中,声明了两个变量用于实现多最近列表技术
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
在 MultiRecentFileDlg.cpp 的 OnInitDialog 过程 中,执行最近文件列表的初始化。首先,创建(或如果存在则直接使用)具有此应用程序名称 MultiRecentFile
的默认 Windows 用户目录。
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);
逐一检查主菜单的所有项目,看其子菜单中是否存在“Recent File”字符串,并修改 ID 以进行标识,然后创建选项卡控件的按钮,其名称与主菜单中的名称相同。
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();
现在,读取现有的最近文件列表并将其输入到相应的子菜单中。
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
最近列表处理
该应用程序的技巧在于前两个过程 UpdateRecentArray(CString strS)
和 UpdateRecentMenu()
。UpdateRecentArray
的作用是在实现新的 string
strS
后,排列最近文件列表 m_recentFileArray
的顺序。
- 如果
strS
为空,只需将数组中的字符串数量安排在global var m_nRecent
以下或等于global var m_nRecent
,然后返回; - 找出数组
m_recentFileArray
中等于strS
的字符串的顺序号; - 如果存在此数字,则将该字符串替换为从该位置到数组开头的一个接一个的字符串。
- 如果未找到相等的字符串,并且数组大小超过了设定的限制,则将新字符串追加到数组;然后将该字符串替换为从末尾到数组开头的一个接一个的字符串。
- 用
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;
}
UpdateRecentMenu()
的作用是用新的 m_recentFileArray
性能更新弹出菜单。
- 找到弹出菜单中第一个最近文件的位置。
- 从菜单中删除所有最近文件字符串。
- 如果最近文件列表不为空,则将当前最近文件列表的字符串一个接一个地插入到找到的位置之前,并自行增加。
- 如果最近文件列表为空,则在找到的位置前插入灰色的字符串“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"));
}
用户自己需要创建 CMultiRecentFileDlg::OpenMyFile(CString strFile)
过程来处理文件打开。在我的应用程序中,仅为演示目的,文件以 Windows 浏览器触摸方式打开。
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);
}
请注意,命令对 UpdateRecentArray(strFile); UpdateRecentMenu()
等同于标准 MFC 过程中的 AfxGetApp()
->AddToRecentFileList(strFile)
。
为了在对话框完成后记住最近文件列表,最近列表的保存过程包含在应用程序退出时。
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();
}
菜单消息处理程序
菜单处理程序由类向导创建,并被覆盖以处理最近文件列表。
选择最近文件字符串之一,并带有 nID
,计算最近列表的数字顺序以及最近文件数组中文件名字符串的顺序号,并打开所选名称的文件。
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);
}
选择子菜单之一,并带有 nID
,计算最近列表的数字顺序,突出显示相关的选项卡控件按钮,并更新列表框控件。
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);
}
}
}
应用程序演示控件
对话框菜单和一些特殊控件排列,以演示如何使用您自己的最近文件列表。
- 菜单 文件->打开 - 将打开的文件添加到最近文件列表的开头,并以 Windows 浏览器触摸方式打开文件。
- 菜单 文件->任意最近文件 - 执行相同操作。
- 菜单 编辑最近->打开最近 - 加载现有的最近文件列表作为当前文件列表,并将打开的文件添加到最近文件列表的开头。
- 菜单 编辑最近->保存最近 - 将当前最近文件列表保存到指定的文本文件中,并将文件路径名添加到最近文件列表的开头。
- 菜单 编辑最近->清除所选 - 从最近文件列表中删除在列表框控件中选定的文件。
- 菜单 编辑最近->全部清除 - 从列表框控件和最近文件列表中删除所有文件。
- 菜单 编辑最近->恢复默认 - 加载最后保存的最近文件列表作为当前文件列表。
- 选项卡控件 - 在相关的最近文件列表之间切换,并指示当前文件列表。
- 列表框控件 - 显示当前文件列表的所有完整路径;鼠标双击 - 打开触及的单个文件。
- 复选框控件 - 用于选择列表框控件中的所有字符串。
- 编辑控件 - 用于指定当前最近文件列表中文件数量的限制。
- 确定按钮控件 - 从最近文件列表中打开列表框控件中选定的文件。
关注点
我相信,最近文件列表处理的替代方法应该会对一些程序员感兴趣。无论如何,它清晰明了,并且在所有阶段都保持您的控制。对于熟悉 MFC 内部技巧和窍门的人来说,标准的 MFC 最近文件列表性能很好。而且有时,这种标准的 MFC 性能会显得过于复杂,并且有太多的限制和条件,不如自己动手做一些事情。
还有一点:该项目是用 MSVS-2015 professional 创建的;但是,在属性中,选择了**MSVS-2010 的工具集**,以便保持此应用程序与 Windows-XP 的兼容性 - 有些人仍在继续使用它。然而,来自 ClassUseRescent.cpp 的过程集与任何旧版本的 MFC MSVS 兼容。
致谢
非常感谢 CodeProject 导师团队在我文章和源代码纠正方面的友好合作,特别是 Jochen Arndt。很高兴能与那些从内部了解所有问题的人接触。