一、目的:熟悉在树控件上的相关操作。在用户指定加载某个目录或者磁盘后,可以通过层级结构显示该目录或磁盘下的所有文件,以及文件对应的系统图标,在用户双击摸个文件或者文件夹后可以调用对应的程序打开文件、文件夹;在界面上的Edit Control 上键入关键字可以在用户加载的目录中查找所有对应项,并显示出来。程序的完整代码点击打开链接。
二、总体效果图:
三、界面总体布局:建立一个基于对话框的程序。两个Edit Control在界面的左上角,其中一个用来显示用户选择加载的目录,另一个用来接受用户键入的关键字;两个Button在界面的右上角,一个按钮实现用户加载目录的功能,另一个实现查找事件;然后界面其余部分平均分布两个控件:Tree Control 通过层次结构现实用户选择的目录中的文件和文件夹;Edit Control显示用户关键字查找的结果。
四、步骤:
1、 添加所有需要的控件以及为控件添加成员变量.
1) 添加显示用户所选目录或磁盘的Edit Control控件,拉伸到合适的大小,将ID更改为IDC_EDIT_CHOOSE,为其添加一个Value类型的成员变量m_Path.在下文中管该控件为“用户选择框”
2) 添加实现加载用户所选目录的Button,将其ID更改为:IDC_BUTTON_ONLOARD,caption更改为:浏览;在下文中管该按钮为“浏览按钮”。
3) 添加供用户键入关键字的Edit Control,拉伸到合适的大小,将ID更改为:IDC_EDIT_KEY,这个控件不添加成员变量.在下文中管该控件为“搜索框”
4) 添加实现搜索事件Button,将其ID更改为:IDC_BUTTON_SEARCH,将Caption更改为搜索,在下文中管该按钮为“搜索按钮”。
5) 添加TreeControl,ID更改为IDC_TREE,更改相关属性:Has Line-True, Has Buttons –True,添加Control类型的成员变量m_FileTree。
6) 添加现实搜索结果的EditControl控件,ID 更改为IDC_EDIT_RESULT:,更改相关属性:Multiline-True,Vertical Scroll-True,Horizontal Scroll-True,Line at root - True;添加Value类型的成员变量m_result.在下文中管该控件为“搜索结果视图”
2、实现“浏览按钮”的事件.该按钮需要能够打开一个查找对话框,供用户选择路径(就像我们安装软件时候选择路径一样),其次需要将用户的选择显示到“用户选择框”控件上,最后需要通过树控件将用户的选择的目录,通过层次结构显示出来。因此该按钮点击一次需要实现三个功能。
1)打开一个查找对话框。这主要是通过封装API实现的。手动添加一个函数,封装某些API。
在头文件中声明该函是为:CStringSelectDirectory(HWND hwnd);
在cpp文件中实现该函数:
CStringCMyManagerDlg::SelectDirectory(HWND hwnd)
{
CStringstrPath;
charszBuf[1024] = {0};
memset(szBuf,0,sizeof(szBuf));
BROWSEINFOAbInfo;
bInfo.hwndOwner= hwnd;
bInfo.pidlRoot= NULL;
bInfo.pszDisplayName= szBuf;
bInfo.lpszTitle= "选择位置";
bInfo.ulFlags= BIF_EDITBOX;
bInfo.lpfn= NULL;
bInfo.iImage= 0;
LPITEMIDLISTlp = SHBrowseForFolderA(&bInfo);
if(lp&&SHGetPathFromIDListA(lp,szBuf))
strPath= szBuf;
else
strPath= _T("");
returnstrPath;
}
2)将用户的选择显示到“用户选择框”上。这个非常简单,将strPath赋值给“用户选择框”的成员变量,UpdateData(FALSE)即可。
3)通过树控件加载用户目录。这里需要将文件夹加载到分支结点,将文件加载到叶子结点;同时为了判断在该分支是否还有未处理的文件以及判断当前路径是文件还是文件夹;为了获得该文件的属性从而获得对应的系统图标;还需要使用CFileFind类的API。
在当前类中声明一个保存图标的链表对象;一个表示文件属性的对象,一个表示当前图标索引的对象:
CImageListm_imageLIst; //保存图标的链表对象
SHFILEINFOfileinfo; //保存文件属性的对象
intindex; //表示当前图标索引的对象
注意上述变量需要在OnInitDialog函数中做初始化:
m_imageLIst.Create(32,32,ILC_COLOR32|ILC_MASK,0,0);
//关联树控件以及图标列表
m_FileTree.SetImageList(&m_imageLIst,TVSIL_NORMAL);
通过一个函数实现属性控件的初始化。
函数声明:voidCreateTreemanager(HTREEITEM Root,CString path);两个参数分别代表根节点的句柄以及路径。
函数定义如下:
voidCMyManagerDlg::CreateTreemanager(HTREEITEM Root,CString path)
{
CFileFindfinder;
HTREEITEMhFatherItem,hSonItem;
BOOLexit = finder.FindFile(path+_T("\\*.*")); //判断该路径下是否有文件或者文件夹
while(exit) //有文件或文件夹的话
{
exit= finder.FindNextFile(); //finder保存下一个文件或文件夹的句柄
if(finder.IsDirectory()&&!finder.IsDots()) //假如这是一个文件夹的话
{
CStringmDir = finder.GetFileTitle(); //获取文件夹名
//获取该文件夹对应的系统图标
SHGetFileInfo(finder.GetFilePath(),0,&fileinfo,sizeof(fileinfo),SHGFI_ICON);
//将该文件夹的系统图标保存到图标列表中,index保存其在列表中的位置
index= m_imageLIst.Add(fileinfo.hIcon);
//将该文件以及对应的图标插入树控件的分支结点
hFatherItem= m_FileTree.InsertItem(mDir,index,index,Root,TVI_LAST);
mDir= path+_T("\\")+mDir; //获取该文件的路径
CreateTreemanager(hFatherItem,mDir);//递归处理该文件下的文件
}
elseif(!finder.IsDirectory()&& !finder.IsDots()) //假如当前的是一个文件,则直接插入业则结点
{
CStringmDir = finder.GetFileName();
SHGetFileInfo(mDir,0,&fileinfo,sizeof(fileinfo)
, SHGFI_USEFILEATTRIBUTES|SHGFI_SYSICONINDEX|SHGFI_ICON);
index= m_imageLIst.Add(fileinfo.hIcon);
hSonItem= m_FileTree.InsertItem(mDir,index,index,Root,TVI_LAST);
}
}
finder.Close();
}
将上述三个功能实现好之后,可以实现“浏览按钮”的事件了。双击该按钮,添加代码如下:
voidCMyManagerDlg::OnBnClickedButtonOnloard()
{
HTREEITEMhRoot;
m_FileTree.DeleteAllItems();//首先清空一下该树控件,防止多次点击出现bug;
m_Path.Empty(); //当前保存用户路径控件的成员变量为空
//下面这三行代码实现的是2.2)
CStringpath = SelectDirectory(m_hWnd); //获取用户选择的路径
m_Path= path; //将该路径保存到用户路径控件的成员变量中
UpdateData(FALSE); //将用户的选择显示到“用户选择框”上
//下面这四行代码是给树控件建立根节点
SHGetFileInfo(m_Path,0,&fileinfo,sizeof(fileinfo),SHGFI_SYSICONINDEX|SHGFI_ICON);
index= m_imageLIst.Add(fileinfo.hIcon);
path.Remove('\\');
hRoot= m_FileTree.InsertItem(path,index,index,TVI_ROOT);
//CreateTreemanager递归处理更目录下的所有文件或文件夹
CreateTreemanager(hRoot,m_Path);
UpdateData(FALSE);
}
至此,效果图如下:
2、 实现搜索的功能,并将搜索结果显示在“搜索结果视图”中。
这里需要做的工作包括:获取用户键入的关键字;在当前树控件中查找与关键字相关的叶子结点;将结果显示在“搜索结果视图”中。
1) 获取用户键入的关键字:这非常简单,换一种不实用成员变量的方法
CString name;//保存用户输入的关键字
GetDlgItem(IDC_EDIT_KEY)->GetWindowText(name); //将“搜索框”中文本保存到name.
2)查找:在树控件中递归查找,我们使用两个函数实现。主要的思路是:如果当前是分支结点就递归查找其子节点,否则直接判断。
首先声明两个函数:
voidFindFile(CString name); //该函数调用下面的函数进行查找操作
voidTestSearch(HTREEITEM ,CString,int&); //进行查找的函数
函数实现如下:
voidCMyManagerDlg::FindFile(CString name)
{
int count = 0; //计数器
name.Remove('*'); //将用户使用的通配符去掉
TestSearch(m_FileTree.GetRootItem(),name,count);
if(count ==0)
m_result = _T("未找到相关项目");
UpdateData(FALSE); //将m_result 中的数据显示在相应的控件上
}
这段代码与我上传的源码有点不同,请注意啊,原来的代码将导致一个bug。
//idex:树节点的某个项的句柄
//name: 满足我一条件的带查找文件名
//count:一个计数器
void CMyManagerDlg::TestSearch(HTREEITEM idex,CString name,int& count)
{
if(idex == NULL)
return;
//判断是否是个分支节点
if (m_FileTree.ItemHasChildren(idex))
{
//递归遍历其子节点
HTREEITEM child = m_FileTree.GetChildItem(idex);
TestSearch(child,name,count);
child = m_FileTree.GetNextItem(child,TVGN_NEXT);
}
else
{
//判断该叶子节点是否满足查找的条件,不满足返回-1
if (m_FileTree.GetItemText(idex).Find(name)!=-1)
{
CString mypath = _T("");
HTREEITEM temp = idex;
//获取满足条件的叶子节点的完整路径
while((temp = m_FileTree.GetParentItem(temp))!=NULL)
mypath = m_FileTree.GetItemText(temp)+_T("\\")+mypath;
m_result+= mypath+m_FileTree.GetItemText(idex);
m_result+="\r\n";
count++;
}
}
//查找下一个节点的相关内容
TestSearch(m_FileTree.GetNextSiblingItem(idex),name,count);
}
上述步骤完成后可以实现我的搜索按钮的功能了,双击该按钮,代码如下
voidCMyManagerDlg::OnBnClickedButtonSearch()
{
// TODO: 在此添加控件通知处理程序代码
CString name;//保存用户输入的关键字
GetDlgItem(IDC_EDIT_KEY)->GetWindowText(name); //将“搜索框”中的文本保存到name中
m_result = _T("");
UpdateData(FALSE);
FindFile(name);
}
至此,效果如下:
3、 实现可以双击打开对应文件的功能。
这里需要做的工作是:获取鼠标左键所处位置,以及该位置上对应的项路径;调用对应程序打开文件。
为树控件添加一个鼠标左键事件,代码如下://鼠标左键双击打开文件或文件夹事件的实现
void CMyManagerDlg::OnNMDblclkTree(NMHDR *pNMHDR, LRESULT *pResult)
{
// TODO: 在此添加控件通知处理程序代码
TVHITTESTINFO hinfo;
CPoint pct;
CRect rc;
DWORD dw = GetMessagePos();
CPoint pt(LOWORD(dw),HIWORD(dw));
m_FileTree.GetWindowRect(&rc);
pct.x = pt.x-rc.left;
pct.y = pt.y-rc.top;
hinfo.pt = pct;
m_FileTree.HitTest(&hinfo);
m_FileTree.SelectItem(hinfo.hItem);
CString path = getPath()+_T("\\")+m_FileTree.GetItemText(hinfo.hItem);//当前文件的路径
ShellExecute(NULL,_T("open"),path,0,0,SW_SHOWNORMAL);
*pResult = 0;
}
//获取文件路径的函数
CString CMyManagerDlg::getPath()
{
HTREEITEM m_current;
m_current = m_FileTree.GetSelectedItem();
CString s1 = m_FileTree.GetItemText(m_current);
CString s2 = _T("");
while ((m_current = m_FileTree.GetParentItem(m_current))!=NULL)
{
s2 = m_FileTree.GetItemText(m_current)+_T("\\")+s2;
}
return s2;
}
4、 实现最大化时控件自动放大功能:这部分代码点击MFC 控件随窗口同步变大的实现。
五、总结
没什么好总结的,我觉得比较简单,没多复杂:主要使用到了递归;搞懂自己想干什么;搞懂需要哪些技术;搞懂如何将这些技术有机组合在一起;最后debug之即可。
六、注意事项,因为该程序使用了递归的方式,所以假如您的磁盘文件非常大而且是使用了笔记本的话,运行起来会慢,建议不要加载整个磁盘,而是加载磁盘下面的某个文件夹。
七、有不足指出请各位指出,谢谢!!!