之前在使用SourceInsight查看一些开源项目的源代码时发现了一个很重要的问题,就是有些开源的工程的头文件现在都没有.h的后缀名了。编译器可以包含这些没有扩展名的头文件,但是使用SourceInsight打开这些工程时却没办法包含这些头文件,或者说即使包含了这些头文件在SourceInsight中查看源码时要找到某个类的声明更本找不到,不知道这算不算SourceInsight的一个bug。
我使用SouceInsight主要的原因就是开发时使用的开源库文档很少,很多时候要查看某个函数的用法都只能去查看源代码,然后在整个工程中查看这些函数是如何使用的。如果SourceInsight不能包含并且找到不带.h后缀的头文件,那基本就没有使用SourceInsight的必要了。
基于以上原因,做了一个对文件后缀名批量更改的工具,之前在网上也查找对文件进行批量更改的工具,功能很强大,但是用起来都不是很顺手,或者说不太符合我的要求吧,我只需要对符合条件的文件更改文件的后缀。
下面是软件的运行截图:
制作这个工具主要有两个问题:
- 类似于资源管理器这个界面的实现
- 文件名更改的规则如何制定
第一个问题其实可以使用一个简单的选择对话框指定路径来实现就可以,但是使用那种方法不能很直观地看到需要修改的文件,而且我要做的功能也不是很多,界面上完全有足够的空间来放置需要修改的目录和文件,所以选择了做一个资源管理器,这样看起来就方便多了。Visual Studio 2008 sp1就有实现这个资源管理功能的类了。分别是CMFCShellTreeCtrl和CMFCShellListCtrl,组合使用这两个类就可以实现出资源管理器的功能,特别要注意编译器需要时Visual Studio 2008 sp1,不能是Visual Studio 2008,后者没有这两个类。如果编译器是Visual Studio 2008,安装Visual Studio 2008 sp1的补丁就可以了,这个补丁可以在这里
下载。
至于第二个问题才是关键,就是如何定义修改的规则,这个规则可以很简单也可以很复杂。因为我做这个工具的目的是修改头文件中不带后缀名的文件,所以直接使用了CString来操作。源文件名主要包括下面这些测试用例:
- *:匹配所有没有后缀名的文件(注意*这个通配符的含义本身是能匹配所有文件的)
- *.*:匹配带有后缀的文件,其中文件名可以是任意的,后缀也可以是任意的
- *.h:类似的这种表达式可以匹配所有后缀名是.h的文件
我主要更改了*这个通配符的含义,正常情况下它是能匹配所有文件的,这样我无法区分出那些不带后缀名的文件。
上图中“扩展文件名”一栏列出的就是修改后文件的扩展名了。
“递归遍历当前文件夹”如果选中可以一直递归遍历选中文件夹下面的文件,这个也很使用,如果只想更改当前文件夹下符合条件的文件那就不勾选,如果当前文件夹下的文件夹也需要进行修改,那就把这项勾上。
上面就是基本的说明,具体的代码实现如下:
1.首先使用Visual Studio 2008 sp1 建立一个基于对话框的MFC应用程序,如果使用的是sp1 ,那么那么建立的工程应该是继承CWinAppEx而不是继承自CWinApp,这点很关键,不然后面很多操作都执行不了。
2.在对话框中放置两个控件List Control 和Tree Control,然后在这两个控件上添加成员变量,分别命名为m_list和m_tree,然后在头文件CFileRenameDlg.h中修改它们的类型为CMFCShellListCtrl,CMFCShellTreeCtrl。设置List Control的属性,在面板里面设置View为Report 一定要设置这个,不然会出错,其余的属性可以设置Always Show为True,Alignment为Top;设置Tree Control的属性,Has Buttons 为True,Has Lines为True,Lines At Root为True
3.在CFileRenameApp::InitInstance()函数中添加下面一行代码InitShellManager();
4.最后在CFileRenameDlg::OnInitDialog()中添加将Tree Control和List Control关联到一起的代码:
m_tree.SetFlags((SHCONTF)(SHCONTF_FOLDERS)); //显示文件夹
m_tree.Expand(m_tree.GetRootItem(), TVE_EXPAND);
m_tree.SetRelatedList(&m_list);
基本上上面的操作完成后就可以看到资源管理器的界面了,此时已经解决了第一个问题,接下来就是解决第二个问题,获取文件名,并根据修改文件的规则来对文件进行修改。
此时主要涉及到CMFCShellListCtrl这个类的一些操作,包括获取到这个List Control的当前目录,以及CFileFind这个类,这个类可以用来遍历目录和文件。
在“批量命名”的响应函数中添加如下代码:
void CFileRenameDlg::OnBnClickedOk()
{
UpdateData(TRUE);
CString str;
//获取是否进行递归的按钮
isChecked =((CButton*)GetDlgItem(IDC_CHECK1))->GetCheck();
int flag=AfxMessageBox("确定批量更改文件名?",MB_OKCANCEL);
if (flag==IDCANCEL )
{
return;
}
//获取当前List的目录
m_list.GetCurrentFolder(str);
//文件重命名
renameFiles(str);
//刷新List Control的内容,这样修改完毕后就可以在界面上看到效果
m_list.Refresh();
//OnOK();
}
下面是最重要的函数renameFiles函数,根据上面的更改规则进行编写,如果上面的文件名修改规则改变了,这个函数也要改变。
整个工程的源代码下载: 下载页面
bool CFileRenameDlg::renameFiles(CString basePath)
{
CFileFind finder;
CString szPath=CString(basePath) + "\\"+m_sourcePath;
bool bPrefix=m_sourcePath.Find('.')==-1?false:true;//判断文件中是否指定'.',如果没有指定,表示要修改那么没有后缀名的文件
CString desExt=m_desPath.Right(m_desPath.GetLength()-m_desPath.ReverseFind('.')-1);//获取.后缀
bool bDes=m_desPath.Find('.')==-1?false:true;//判断修改后的文件中是否有后缀
BOOL bFind=finder.FindFile(szPath);
while(bFind)
{
bFind=finder.FindNextFile();
//如果不是目录
if (!(finder.IsDirectory()||finder.IsDots()))
{
//获取当前文件的文件名和后缀
CString oldName,oldTitle;
oldName=finder.GetFileName();
oldTitle=finder.GetFileTitle();
bool bOld=oldName.Find('.')==-1?false:true;//判断当前遍历的文件是否有后缀
//1.如果源文件没有指定后缀并且当前遍历的文件也有没有后缀,那么进行更改
//2.源文件有后缀名并且匹配
if (!bPrefix&&!bOld||bPrefix)
{
try
{
//如果修改后的文件有后缀
if(bDes)
CFile::Rename(basePath+"\\"+oldName,basePath+"\\"+oldTitle+"."+desExt);
else//如果修改后的文件没有后缀
CFile::Rename(basePath+"\\"+oldName,basePath+"\\"+oldTitle);
}
catch (CFileException* e)
{
}
}
}
}
//递归遍历文件夹并修改文件夹内符合条件的文件
szPath=CString(basePath) + "\\*.*";
bFind=finder.FindFile(szPath);
while(bFind)
{
bFind = finder.FindNextFile();
if (finder.IsDirectory()&&!finder.IsDots()&&isChecked)//注意该句需要排除“.”“..”
{
renameFiles(basePath+"\\"+finder.GetFileTitle()+"\\");//注意这里不能是直接使用GetFileUrl函数
}
}
finder.Close();
return true;
}
整个工程的源代码下载: 下载页面
可执行程序的下载:
下载页面
下面是准备测试用的文件夹:
将没有后缀名的文件添加.txt后缀:
结果:
特别要注意,不要随便在任何目录下运行这个工具!!!
参考文章: