代码统计工具1.1版本技术文档
说明:主要记录自己在做这个项目的过程中用到的方法和相关技术
1. 首先面临的问题就是怎样选择一个目录,网上搜索了一下,下面是解决方案(用到目录对话框)
(1) 从默认的磁盘总目录下开始选择:
TCHARszPath[MAX_PATH];
BROWSEINFObr;
ITEMIDLIST*pItem;
br.hwndOwner=this->GetSafeHwnd();
br.pidlRoot=0;
br.pszDisplayName=0;
br.lpszTitle=" 选择路径 ";
br.ulFlags=BIF_STATUSTEXT;
br.lpfn=0;
br.iImage=0;
br.lParam=0;
pItem=SHBrowseForFolder(&br);
if(pItem!=NULL)
{
if(SHGetPathFromIDList(pItem,szPath)==TRUE)
{
// 这就是我们得到的目录名称
CStringstrDir=szPath;
}
}
(2) 自己 设定需要目录对话框默认选择的目录
第一步:(和第一种不同的是需要为这个目录对话框设定自定义回调函数)
TCHARszDefaultDir[MAX_PATH];
CStringstrDef(_T("d://C++//"));// 需要设定的默认的目录
memcpy(szDefaultDir,strDef.GetBuffer(strDef.GetLength()),strDef.GetLength());
strDef.ReleaseBuffer();
TCHARszPath[MAX_PATH];
BROWSEINFObr;
ITEMIDLIST*pItem;
br.hwndOwner=this->GetSafeHwnd();
br.pidlRoot=0;
br.pszDisplayName=0;
br.lpszTitle=" 选择路径 ";
br.ulFlags=BIF_STATUSTEXT;
// 设置 CALLBACK 函数
br.lpfn=FA_BrowseCallbackProc;
br.iImage=0;
// 设置默认路径
br.lParam=long(&szDefaultDir);
/* 说明:在 Unicode 环境下,编译测试,此处的默认路径无法起作用
/* 需要手动转换成 TChar/WChar
/*TCharstrBuffer[MAX_PATH];
/*wcscpy(strBuffer,szDefaultDir);*/
pItem=SHBrowseForFolder(&br);
if(pItem!=NULL)
{
if(SHGetPathFromIDList(pItem,szPath)==TRUE)
{
// 这就是我们得到的目录名称
m_strDirPath=szPath;
}
}
第二步:设计回调函数
intCALLBACKFA_BrowseCallbackProc(HWNDhwnd,UINTuMsg,LPARAMlParam,LPARAMlpData)
{
switch(uMsg)
{
caseBFFM_INITIALIZED:// 初始化消息
// 传递默认打开路径(方法一)
//::SendMessage(hwnd,BFFM_SETSELECTION,TRUE,(LPARAM)"C://ProgramFiles");
// 传递默认打开路径(方法二,前提是 lpData 提前设置好)
::SendMessage(hwnd,BFFM_SETSELECTION,TRUE,lpData);
break;
caseBFFM_SELCHANGED:// 选择路径变化,
{
charcurr[MAX_PATH];
SHGetPathFromIDList((LPCITEMIDLIST)lParam,curr);
::SendMessage(hwnd,BFFM_SETSTATUSTEXT,0,(LPARAM)curr);
}
break;
default:
break;
}
return0;
}
(3) 用到 的数据结构( MSDN 查看相应介绍):
typedefstruct_browseinfo{
HWNDhwndOwner;
LPCITEMIDLISTpidlRoot;
LPTSTRpszDisplayName;
LPCTSTRlpszTitle;
UINTulFlags;
BFFCALLBACKlpfn;
LPARAMlParam;
intiImage;
}BROWSEINFO,*PBROWSEINFO,*LPBROWSEINFO;
typedefstruct_ITEMIDLIST{
SHITEMIDmkid;
}ITEMIDLIST,*LPITEMIDLIST;
typedefconstITEMIDLIST*LPCITEMIDLIST;
2. 遍历一个目录(需要递归遍历下面所有的文件)并保存源代码文件的文件名(后 缀名为 .c,cpp,.h,.java)
/********************************************************************
* 函数名 :
FA_ReadDirectory(CStringstrDirPath)
* 函数功能 :
读取一个目录下的所有文件
* 输入参数 :
strDirPath: 目录的完整路径
* 输出参数 :
* 返回值 :
* 用到的全局变量和结构 :
* 其他说明 :
********************************************************************/
voidCFA_CodeAnalysisView::FA_ReadDirectory(CStringstrDirPath)
{
WIN32_FIND_DATAtFind={0};
CStringstrTemp;
CStringstrDirTemp;
CStringstrSuffix;
strDirPath.Format("%s//*",strDirPath);
HANDLEhSearch=::FindFirstFile(strDirPath,&tFind);
if(hSearch==INVALID_HANDLE_VALUE)
{
return;
}
// 过滤掉 . 和 .. 文件目录
::FindNextFile(hSearch,&tFind);
while(::FindNextFile(hSearch,&tFind))
{
strDirTemp=strDirPath;
strTemp.Format("%s",tFind.cFileName);
// 去掉最后那一个 * 通配符
strDirTemp=strDirTemp.Left(strDirTemp.GetLength()-1);
strDirTemp+=strTemp;
if((tFind.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)!=0)
{
FA_ReadDirectory(strDirTemp);
}
strSuffix=strTemp.Right(strTemp.GetLength()-strTemp.Find('.'));
if(!strSuffix.CompareNoCase(".h")||!strSuffix.CompareNoCase(".cpp")||
!strSuffix.CompareNoCase(".c")||!strSuffix.CompareNoCase(".java"))
{
m_strFileName[m_iFileCount]=strDirTemp;
m_iFileCount++;
}
}
::FindClose(hSearch);
}
知识点: FindFirstFile 和 FindNextFile 函数以及下面这个结构体 .
typedefstruct_WIN32_FIND_DATA{
DWORD dwFileAttributes ;
FILETIME ftCreationTime ;
FILETIME f tLastAccessTime ;
FILETIME ftLastWriteTime ;
DWORD nFileSizeHigh ;
DWORD nFileSizeLow ;
DWORD dwOID ;
TCHAR cFileName [MAX_PATH] ;
}WIN32_FIND_DATA;
3. 计算
/********************************************************************
* 函数名 :
FA_CalculateLines()
* 函数功能 :
计算各个文件的代码行数,注释函数以及空白函数
* 输入参数 :
* 输出参数 :
* 返回值 :
* 用到的全局变量和结构 :
* 其他说明 :
********************************************************************/
voidCFA_CodeAnalysisView::FA_CalculateLines()
{
m_lBlankTotalLines=0;
m_lNoteTotalLines=0;
m_lCodeTotalLines=0;
CStringstrFileName;
CStringstrRecvData;
CStdioFilefile;
BOOLbNoteEnd=FALSE;
intblankLines=0;
intnoteLines=0;
intcodeLines=0;
intblankTotalLines=0;
intnoteTotalLines=0;
intcodeTotalLines=0;
CRectrt;
GetClientRect(rt);
intiAverHigh=rt.Height()/32;
/************************************************************************/
/* 以下代码段计算每个文件的有效代码行数,注释行数以及空行数
/* 计算所有文件的总的相对应的行数
/* 用的是一个从 CFile 继承的类 CStdioFile ,因为它有一个方法可以直接读一行文件
/* 内容到一个 CString 中
/************************************************************************/
for(inti=0;i<m_iFileCount;i++)
{
strFileName=m_strFileName[i];
// 以只读模式打开文件
file.Open(strFileName,CFile::modeRead);
// 读入一行带字符串中
while(file.ReadString(strRecvData))
{
// 判断是否是多行注释的开头
if(!strRecvData.Left(2).Compare("/*")&&!bNoteEnd)
{
// 判断多行注释是否在当前行的结束
if(strRecvData.Right(2).Compare("*/"))
{
bNoteEnd=TRUE;
}
noteLines++;
}
// 判断是不是多行注释的结束
elseif(!strRecvData.Right(2).Compare("*/")&&bNoteEnd)
{
noteLines++;
bNoteEnd=FALSE;
}
// 判断当前行在多行注释中间部分
elseif(bNoteEnd)
{
noteLines++;
}
// 判断是否是空行
elseif(strRecvData.TrimLeft("/t"),strRecvData.IsEmpty())
{
blankLines++;
}
// 判断是否是单行注释
elseif(!strRecvData.Left(2).Compare("//"))
{
noteLines++;
}
// 否则是有效代码行
else
{
codeLines++;
}
}
// 注意用完一个文件后关闭
file.Close();
m_iBlankLines[i]=blankLines;
m_iNoteLines[i]=noteLines;
m_iCodeLines[i]=codeLines;
m_lBlankTotalLines+=blankLines;
m_lNoteTotalLines+=noteLines;
m_lCodeTotalLines+=codeLines;
blankLines=0;
noteLines=0;
codeLines=0;
}
// 根据计算结果计算视图总共的高度
if(m_iFileCount>7)
{
CSizesizeTotal;
sizeTotal.cx=600;
sizeTotal.cy=m_iFileCount*iAverHigh*4;
SetScrollSizes(MM_TEXT,sizeTotal);
}
}
知识点: CStdioFile 类的使用以及它的函数 ReadStirng 读入文件的一行到一个字符串
4. 输出计算结果:
/********************************************************************
* 函数名 :
DrawFileText(CDC*pDC)
* 函数功能 :
输出文件名,及各个计算结果
* 输入参数 :
pDC: 用于输出文字的 CDC 指针
* 输出参数 :
* 返回值 :
* 用到的全局变量和结构 :
* 其他说明 :
********************************************************************/
voidCFA_CodeAnalysisView::FA_DrawFileText(CDC*pDC)
{
CStringstrFileName;
pDC->SetBkMode(TRANSPARENT);
CRectrt;
GetClientRect(rt);
intiAverHigh=rt.Height()/32;
pDC->SetTextColor(RGB(0,0,0));
strFileName.Format(" 此目录下各个行数的总数如下 ( 总共有 %d 个文件 ) : ",m_iFileCount);
pDC->DrawText(strFileName,CRect(0,0,rt.Width(),20),DT_LEFT);
pDC->SetTextColor(RGB(255,0,0));
strFileName.Format(" 总代码有 %d 行 ",m_lCodeTotalLines);
pDC->DrawText(strFileName,CRect(50,1*iAverHigh,rt.Width(),1*iAverHigh+20),DT_LEFT);
pDC->SetTextColor(RGB(0,255,0));
strFileName.Format(" 总注释有 %d 行 ",m_lNoteTotalLines);
pDC->DrawText(strFileName,CRect(50,2*iAverHigh,rt.Width(),2*iAverHigh+20),DT_LEFT);
pDC->SetTextColor(RGB(0,0,255));
strFileName.Format(" 总空行有 %d 行 ",m_lBlankTotalLines);
pDC->DrawText(strFileName,CRect(50,3*iAverHigh,rt.Width(),3*iAverHigh+20),DT_LEFT);
for(inti=0;i<m_iFileCount;i++)
{
strFileName=m_strFileName[i];
pDC->SetTextColor(RGB(0,0,0));
pDC->DrawText(strFileName.Right(strFileName.GetLength()-m_strDirPath.GetLength()-1),
CRect(0,(i+1)*4*iAverHigh,rt.Width(),(i+1)*4*iAverHigh+20),DT_LEFT);
pDC->SetTextColor(RGB(255,0,0));
strFileName.Format(" 代码有 %d 行 ",m_iCodeLines[i]);
pDC->DrawText(strFileName,CRect(50,((i+1)*4+1)*iAverHigh,rt.Width(),
((i+1)*4+1)*iAverHigh+20),DT_LEFT);
pDC->SetTextColor(RGB(0,255,0));
strFileName.Format(" 注释有 %d 行 ",m_iNoteLines[i]);
pDC->DrawText(strFileName,CRect(50,((i+1)*4+2)*iAverHigh,rt.Width(),
((i+1)*4+2)*iAverHigh+20),DT_LEFT);
pDC->SetTextColor(RGB(0,0,255));
strFileName.Format(" 空行有 %d 行 ",m_iBlankLines[i]);
pDC->DrawText(strFileName,CRect(50,((i+1)*4+3)*iAverHigh,rt.Width(),
((i+1)*4+3)*iAverHigh+20),DT_LEFT);
}
}
5. 运行效果
6. 总结
此项目虽然很小,但是比较实用,我们可以简单的计算一个目录下或是一个工程有多少代码行,注释行以及空白行。对于自己编程多少的检验,以及一个团队内每个成员的编程多少做统计。