一、成果展示
二、实现功能
2.1 显示树形目录结构
- 根节点是“我的电脑”
- “我的电脑”下有几个盘符(C、D、E等)就有几个子节点,递归显 示文件系统下的所有文件信息(分支可以是目录也可以是文件,叶 子节点都是文件)
2.2 文件操作
能够创建目录、创建文件、删除目录、删除文件、复制文件、粘贴文件
三、需求分析
- 首先要求我们能够获取电脑本地的所有盘符;
- 然后利用递归将本地的所有文件夹以及文件都获取到;
- 接着将获取到的所有内容以树形目录结构的形式在UI界面显示,并且任一文件夹中的内容(文件夹、文件)可以以列表的形式在UI界面显示出来;
- 最后在写出的可视化界面上要能够对文件夹或者文件进行创建、删除、复制、粘贴等操作,这些操作应以可视化的形式显示并且要足够简单,让用户操作使用起来足够方便。
总体要求做出一个功能类似Windows 10操作系统中的“此电脑”的可视化应用程序,故可以仿照“此电脑”的UI设计及相关功能的操作方法,如下图所示。
四、系统设计
4.1 数据结构和存储结构的设计
因为这个文件系统的程序需要实时读取当前磁盘空间的存储状态及存储内容并且在C++中具有读取文件或文件夹状态以及路径的API函数,所以在程序中对于文件或文件夹的操作并不需要其他什么特殊的数据结构和存储结构,只要在使用树控件的时候利用现成的API获取相应的信息并将这些信息在树形控件中显示出来即可。
若想存储文件或文件夹的信息,可以采用树结构来存储,一个结点的孩子结点是这个结点所代表的文件夹中的所有文件或者其他文件夹,其中文件一定处在叶子结点上;其次,为了查询的方便、快速以及IO开销尽可能小,可以采用B-树或者B+树的结构存储,可以减小IO开销,提高查询效率。
其他的一些数据结构,例如栈、堆、队列、数组等,用法、设计与其余所有C++程序相同。(比如调用函数,利用了堆栈等)
4.2 算法设计
4.2.1 获取与删除目录: 递归算法
4.2.1.1 获取所有盘符的下一级目录
// 获取目录下所有子项
void CFileViewSystemDlg::GetDriveDir(HTREEITEM hParent)
{
HTREEITEM hChild = m_tree.GetChildItem(hParent);
while (hChild)
{
CString strText = m_tree.GetItemText(hChild); // 检索列表中项目文字
if (strText.Right(1) != "\\") // 从右边1开始获取从右向左nCount个字符
strText += _T("\\");
strText += "*.*";
// 将当前目录下文件枚举并InsertItem树状显示
CFileFind file; // 定义本地文件查找
BOOL bContinue = file.FindFile(strText); // 查找包含字符串的文件
while (bContinue)
{
bContinue = file.FindNextFile(); // 查找下一个文件
if (file.IsDirectory() && !file.IsDots()) // 找到文件为内容且不为点"."
m_tree.InsertItem(file.GetFileName(), hChild); // 添加盘符路径下树状文件夹
}
GetDriveDir(hChild); // 递归调用
hChild = m_tree.GetNextItem(hChild, TVGN_NEXT); // 获取树形控件TVGN_NEXT下兄弟项
}
}
4.2.1.2 删除目录及目录中的所有内容
需要删除一个目录首先要删除该目录下的所有文件和文件夹,然后再移除空文件夹,同样删除该文件夹下的文件夹也需要先删除其中的文件和文件夹,就形成了递归。
// 删除文件夹
bool CFileViewSystemDlg::DeleteFolder(LPCTSTR pstrFolder)
{
if ((NULL == pstrFolder))
{
return FALSE;
}
// 检查输入目录是否是合法目录
if (!IsDirectory(pstrFolder))
{
return FALSE;
}
// 创建源目录中查找文件的通配符
CString strWildcard(pstrFolder);
if (strWildcard.Right(1) != _T('\\'))
{
strWildcard += _T("\\");
}
strWildcard += _T("*.*");
// 打开文件查找,查看源目录中是否存在匹配的文件
// 调用FindFile后,必须调用FindNextFile才能获得查找文件的信息
CFileFind finder;
BOOL bWorking = finder.FindFile(strWildcard);
while (bWorking)
{
// 查找下一个文件
bWorking = finder.FindNextFile();
// 跳过当前目录“.”和上一级目录“..”
if (finder.IsDots())
{
continue;
}
// 得到当前目录的子文件的路径
CString strSubFile = finder.GetFilePath();
// 判断当前文件是否是目录,
// 如果是目录,递归调用删除目录,
// 否则,直接删除文件
if (finder.IsDirectory())
{
if (!DeleteFolder(strSubFile))
{
finder.Close();
return FALSE;
}
}
else
{
if (!DeleteFile(strSubFile))
{
finder.Close();
return FALSE;
}
}
}
// 关闭文件查找
finder.Close();
// 删除当前空目录
return RemoveDirectory(pstrFolder);
}
4.2.2 判断文件是否存在:计数器累加法
每一次需要创建文件或者文件夹,都从 1 开始若文件或文件夹已存在计数器就加 1,形成新的文件夹或文件的名称,直到没有重复时就以该名称创建文件或文件夹。
文件夹:
str += _T("新建文件夹1");
while (PathIsDirectory(str))
{
str.Delete(str.GetLength() - 1, 1);
CString chg;
chg.Format(_T("%d"), f_cnt);
str += chg;
f_cnt++;
}
文本文档:
str += _T("新建文本文档1.txt");
while (PathFileExists(str))
{
CString chg;
chg.Format(_T("%d"), t_cnt);
str.SetAt(str.Find(_T(".")) - 1, chg.GetAt(0));
t_cnt++;
}
CFile file(str, CFile::modeCreate);
file.Close();
DOCX 文档:
str += _T("新建DOCX文档1.docx");
while (PathFileExists(str))
{
CString chg;
chg.Format(_T("%d"), d_cnt);
str.SetAt(str.Find(_T(".")) - 1, chg.GetAt(0));
d_cnt++;
}
CFile file(str, CFile::modeCreate);
file.Close();
4.2.3 读取一个文件夹下的所有文件或文件夹:文件搜索算法
运用 MFC 中的 CFileFind 类中的 FindFile(str)获取一个文件夹下面的所有文件和文件夹。
CFileFind file;
BOOL bContinue = file.FindFile(str);
while (bContinue) {
bContinue = file.FindNextFileW();
if (!file.IsDots()) {
//文件和文件夹
SHFILEINFO info = {
0 };
CString temp = str;
int index = temp.Find(_T("*.*"));
temp.Delete(index, 3);
SHGetFileInfo(temp + file.GetFileName(), 0, &info, sizeof(&info), SHGFI_DISPLAYNAME | SHGFI_ICON);
int i = m_ImageList.Add(info.hIcon);
m_list.SetImageList(&m_ImageList, LVSIL_SMALL); //设置图标
m_list.InsertItem(i, info.szDisplayName, i); //在列表插入一项
}
}
4.3 模块设计
4.3.1 树形控件显示模块
1、获取所有盘符并在树形控件中的根节点“我的电脑”的孩子结点上插入:
void FileViewSystemDlg::GetLogicalDrive(HTREEITEM hParent)
2、获取所有盘符下的所有文件夹并在相应盘符结点的孩子结点上插入(仅仅是盘符的下一级):
void FileViewSystemDlg::GetDriveDir(HTREEITEM hParent)
注:盘符的下一级单独处理的原因是盘符与普通文件夹不同需要单独处理。
3、返回某一结点的绝对路径(从结点开始向根节点回溯):
CString FileViewSystemDlg::GetFullPath(HTREEITEM hCurrent)
4、获取某一文件夹下的所有文件夹并在代表该文件夹的结点的孩子结点上插入子文件夹:
void FileViewSystemDlg::AddSubDir(HTREEITEM hParent)
5、展开树形控件某一结点时触发的事件(显示该节点下的所有文件夹并载入再下一层的文件夹):
void FileViewSystemDlg::OnItemexpandedTree(NMHDR* pNMHDR, LRESULT* pResult)
4.3.2 列表控件显示模块
1、选择树形控件的某一节点时触发的事件(将该结点代表的文件下的所有文件夹以及文件显示在列表控件中):
void FileViewSystemDlg::OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)
2、鼠标左键双击列表控件时触发的事件(文件夹:打开该文件夹在列表控件中显示;文件:调用外部应用程序打开文件)(未选中任何一项无动作):
void FileViewSystemDlg::OnDblclkList(NMHDR* pNMHDR, LRESULT* pResult)
3、鼠标右键单击列表控件时触发的事件(选中了某一项时显示操作菜单:打开、复制、删除;未选中某一项时显示操作菜单:刷新、新建、粘贴):
void FileViewSystemDlg::OnRclickList(NMHDR* pNMHDR, LRESULT* pResult)
4.3.3 返回、转到与显示地址模块
1、单击“返回”按钮时触发的事件(返回上一级目录):
void FileViewSystemDlg::OnClickedBack()
2、单击“转到”按钮时触发的事件(进入下拉列表框中输入的路径所表示的文件夹内):
void FileViewSystemDlg::OnClickedEnter()
3、下拉列表框中所选项发生变化时所触发的事件(进入下拉列表框所选项表示的文件夹内):
void FileViewSystemDlg::OnSelchangeDirpath()
4.3.4 创建模块
注:文件、文件夹的命名均通过计数器来避免名称冲突而创建失败。
1、鼠标右键单击列表空白处后弹出菜单,再鼠标左键单击菜单的“新建-文件夹”时触发的事件(在操作的目录下新建一个文件夹):
void FileViewSystemDlg::OnNewfile()
2、鼠标右键单击列表空白处后弹出菜单,再鼠标左键单击菜单的“新建-文本文档”时触发的事件(在操作的目录下新建一个文本文档):
void FileViewSystemDlg::OnTxt()
3、鼠标右键单击列表空白处后弹出菜单,再鼠标左键单击菜单的“新建-DOCX 文档”时触发的事件(在操作的目录下新建一个 DOCX 文档):
void FileViewSystemDlg::OnDocx(