代码统计工具1.1版本技术文档
说明:主要记录自己在做这个项目的过程中用到的方法和相关技术
1.首先面临的问题就是怎样选择一个目录,网上搜索了一下,下面是解决方案(用到目录对话框)
(1)从默认的磁盘总目录下开始选择:
TCHAR szPath[MAX_PATH];
BROWSEINFO br;
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)
{
//这就是我们得到的目录名称
CString strDir = szPath;
}
}
(2)自己设定需要目录对话框默认选择的目录
第一步:(和第一种不同的是需要为这个目录对话框设定自定义回调函数)
TCHAR szDefaultDir[MAX_PATH];
CString strDef(_T("d://C++//"));//需要设定的默认的目录
memcpy(szDefaultDir, strDef.GetBuffer(strDef.GetLength()), strDef.GetLength());
strDef.ReleaseBuffer();
TCHAR szPath[MAX_PATH];
BROWSEINFO br;
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
/* TChar strBuffer[MAX_PATH];
/* wcscpy(strBuffer, szDefaultDir);*/
pItem = SHBrowseForFolder(&br);
if (pItem != NULL)
{
if (SHGetPathFromIDList(pItem,szPath) == TRUE)
{
//这就是我们得到的目录名称
m_strDirPath = szPath;
}
}
第二步:设计回调函数
int CALLBACK FA_BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
switch(uMsg)
{
case BFFM_INITIALIZED: //初始化消息
//传递默认打开路径 (方法一)
//::SendMessage(hwnd, BFFM_SETSELECTION,TRUE,(LPARAM)"C://Program Files");
//传递默认打开路径 (方法二,前提是lpData提前设置好)
::SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
break;
case BFFM_SELCHANGED: //选择路径变化,
{
char curr[MAX_PATH];
SHGetPathFromIDList((LPCITEMIDLIST)lParam,curr);
::SendMessage(hwnd,BFFM_SETSTATUSTEXT,0,(LPARAM)curr);
}
break;
default:
break;
}
return 0;
}
(3)用到的数据结构(MSDN查看相应介绍):
typedef struct _browseinfo {
HWND hwndOwner;
LPCITEMIDLIST pidlRoot;
LPTSTR pszDisplayName;
LPCTSTR lpszTitle;
UINT ulFlags;
BFFCALLBACK lpfn;
LPARAM lParam;
int iImage;
} BROWSEINFO, *PBROWSEINFO, *LPBROWSEINFO;
typedef struct _ITEMIDLIST {
SHITEMID mkid;
} ITEMIDLIST, * LPITEMIDLIST;
typedef const ITEMIDLIST * LPCITEMIDLIST;
2.遍历一个目录(需要递归遍历下面所有的文件)并保存源代码文件的文件名(后缀名为.c, cpp, .h,. java)
/********************************************************************
* 函数名:
FA_ReadDirectory(CString strDirPath)
* 函数功能:
读取一个目录下的所有文件
* 输入参数:
strDirPath:目录的完整路径
* 输出参数:
* 返回值:
* 用到的全局变量和结构:
* 其他说明:
********************************************************************/
void CFA_CodeAnalysisView::FA_ReadDirectory(CString strDirPath)
{
WIN32_FIND_DATA tFind = {0};
CString strTemp;
CString strDirTemp;
CString strSuffix;
strDirPath.Format("%s//*", strDirPath);
HANDLE hSearch = ::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 函数以及下面这个结构体.
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwOID;
TCHAR cFileName[MAX_PATH];
} WIN32_FIND_DATA;
3.计算
/********************************************************************
* 函数名:
FA_CalculateLines()
* 函数功能:
计算各个文件的代码行数,注释函数以及空白函数
* 输入参数:
* 输出参数:
* 返回值:
* 用到的全局变量和结构:
* 其他说明:
********************************************************************/
void CFA_CodeAnalysisView::FA_CalculateLines()
{
m_lBlankTotalLines = 0;
m_lNoteTotalLines = 0;
m_lCodeTotalLines = 0;
CString strFileName;
CString strRecvData;
CStdioFile file;
BOOL bNoteEnd = FALSE;
int blankLines = 0;
int noteLines = 0;
int codeLines = 0;
int blankTotalLines = 0;
int noteTotalLines = 0;
int codeTotalLines = 0;
CRect rt;
GetClientRect(rt);
int iAverHigh = rt.Height() / 32;
/************************************************************************/
/* 以下代码段计算每个文件的有效代码行数,注释行数以及空行数
/*计算所有文件的总的相对应的行数
/*用的是一个从CFile继承的类CStdioFile,因为它有一个方法可以直接读一行文件
/*内容到一个CString中
/************************************************************************/
for (int i=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++;
}
//判断是不是多行注释的结束
else if (!strRecvData.Right(2).Compare("*/") && bNoteEnd)
{
noteLines++;
bNoteEnd = FALSE;
}
//判断当前行在多行注释中间部分
else if (bNoteEnd)
{
noteLines++;
}
//判断是否是空行
else if (strRecvData.TrimLeft("/t "), strRecvData.IsEmpty())
{
blankLines++;
}
//判断是否是单行注释
else if (!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)
{
CSize sizeTotal;
sizeTotal.cx = 600;
sizeTotal.cy = m_iFileCount * iAverHigh * 4;
SetScrollSizes(MM_TEXT, sizeTotal);
}
}
知识点:CStdioFile类的使用以及它的函数ReadStirng读入文件的一行到一个字符串
4.输出计算结果:
/********************************************************************
* 函数名:
DrawFileText(CDC *pDC)
* 函数功能:
输出文件名,及各个计算结果
* 输入参数:
pDC:用于输出文字的CDC指针
* 输出参数:
* 返回值:
* 用到的全局变量和结构:
* 其他说明:
********************************************************************/
void CFA_CodeAnalysisView::FA_DrawFileText(CDC *pDC)
{
CString strFileName;
pDC->SetBkMode(TRANSPARENT);
CRect rt;
GetClientRect(rt);
int iAverHigh = 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 (int i=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.总结
此项目虽然很小,但是比较实用,我们可以简单的计算一个目录下或是一个工程有多少代码行,注释行以及空白行。对于自己编程多少的检验,以及一个团队内每个成员的编程多少做统计。