Windows浏览窗口就是你在安装程序的时候经常叫你选择安装在哪个目录里面的对话框,我们一般使用这个对话框来获得用户选择的目录。
(一)主要牵扯到的函数是:
PIDLIST_ABSOLUTE SHBrowseForFolder(lpbi)
此函数会调用Windows Explorer(浏览)对话窗口,如果用户点击确定的话,会返回一个PIDLIST_ABSOLUTE(绝对路径标识序列)。
1:首先讲解返回值:
PIDLIST_ABSOLUTE是PITEMIDLIST的别称,之所以别称主要是起字面说明作用,因为还有PIDLIST_RELATIVE也是PITEMIDLIST。PIDLIST_ABSOLUTE得到的是绝对路径的标识序列的内存指针。是标识序列而不是路径名称。
其中PITEMIDLIST是结构体ITEMIDLIST的指针,而ITEMIDLIST里面只有一个结构体成员变量SHITEMID,所以也是一个别名。而SHITEMID的结构体如下:
typedef struct _SHITEMID {
USHORT cb;
BYTE abID[1];
} SHITEMID, * LPSHITEMID;
SHITEMID的名称中,SH是Shell的意思,ITEMID是条目ID,或者对象ID。
因为Shell中使用对象ID(ITEMID)来表示Namespaces Hierarchy(名称空间层次)中的对象。而Namespace Hierarchy是一个名称空间层次,就想桌面下的电脑和我的文档在一个名称空间,而C:/下的windows和program files在一个名称空间,同一个名称空间对象的名称不能重复,就是为了更好了处理电脑上同名文件,才引入了名称空间。
名称空间对象有两种,一种是文件夹(大多数情况),即节点。一种是文件,即叶子。
一个节点不光可以是文件夹,也可以是虚拟对象,如打印机连接符号(可以连接局域网的打印机),桌面的回收站 (里面有文件,但它不是一般的文件夹目录)等。
Namespaces Hierarchy中并不使用路径来表示这些对象,因为他对一些虚拟对象是不适用的,用路径可以表示某个磁盘上的文件,方式就是把从根目录开始,到此文件的途经过的所有名称空间层次中的对象名称连接起来,每个对象名称用'/'间隔开来。但对这样情况,如用户的某个文件夹连接指向的是一个远程服务器中的一个文件夹,这是无法用路径来表示的,它因该是个URL或者其他的方式。总之Namespace Hierarchy提供另一种方式来标识对象,那就是用ItemID来表征,我们无法从abID这个数组中得到任何可理解的信息。MSDN告诫我们只需把Item ID看做是特定对象的标记就可以。我们经常用PIDLIST来指向一组SHITEMID,每个SHITEMID代表一个对象,每个SHITEMID里面有一个abID数组,那就相当于我们对象的标识符,就想名称一样。所以一组SHITEMID就相当于我们的路径了,其中每一个SHITEMID就代表一个相应的文件夹或者最后的文件,但是SHITEMID里面的数据对我们来说是不相干的,意思就是不是字符串。你无法从表面得知它的含义。所以这里我们只需知道PIDLIST相当于路径标识符,我们可以通过PIDLIST_ABSOLUTE可以得到一些我们想要的东西。
记住PIDLIST_ABSOLUTE是一个对象ID链表指针,指向的ID链表是由API函数内部申请的,但其销毁工作有用户完成。所以用户要手动的调用CoTaskMemFree(pidl);
2:讲解lpbi
lpbi是BROWSEINFO结构体,其中包含了Windows Explorer对话框的很多有用的信息,一般设置如下。
bi.hwndOwner=hWnd;
bi.pidlRoot=NULL;
bi.pszDisplayName=szDisplayName;
bi.lpszTitle=TEXT("选择你要进行清理的目录");
bi.ulFlags=BIF_BROWSEFORCOMPUTER|BIF_RETURNONLYFSDIRS;
bi.lpfn=NULL;
bi.lParam=NULL;
bi.iImage=0;
注意pszDisplayName就是节点名字,也就是文件夹名称。ulFlags有很多含义,具体参照MSDN。
其实对我们最重要的是函数的返回值,就想我上面体的一样,有了条目ID列表,我们可以通过API函数得到路径字符串。
(二)要得到路径标识序列对应的路径,我们需要下面这个函数:
BOOL SHGetPathFromIDList(pidl,szPath)
pidl就是你通过SHBrowseForFolder获得的对象ID链表,szPath是一个字符数组,用于保存函数返回的路径;
注意:MSDN中强调我们在用SHBrowseForFolder的时候必须要先调用CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);
用完后调用CoUninitializeEx();
现在我们讲解遍历文件夹及其子目录的函数:
hFind FindFirstFile(szFileName,fd);
hFind是文件查找句柄,通过它可以使用FindNextFile来得到下一个文件信息。
szFileName是文件的完整路径名,允许使用通配符(?、*)。
fd是WIN32_FIND_DATA结构体。里面包含了文件的许多基本信息。
BOOL FindNextFile(hFind,fd);
一看原型估计就知道什么意思了。
typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[MAX_PATH]; TCHAR cAlternateFileName[14]; } WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;
其中对我们最重要的是dwFileAttribute和cFileName。
dwFileAttribute是告诉我们文件的属性,是归档还是隐藏,还是只读,还是加密了什么的。
cFileName是文件的名字。
假设我们要遍历一个文件夹的文件及其子目录的话,例如E:/visual stdio 2008
那么我们一般将我们要遍历的文件夹路径设置为:E:/visual stdio 2008/*
将此字符串传给FindFirstFind的第一个参数,第二个参数则在成功是保存文件信息。
一般我们找到的第一个对象,我说的是对象,因为我们用*通配符,所以将查找所有的子目录和文件。所以我们第一次得到的是对象。第一次查找到的是一个叫做当前目录文件夹,也就是cFileName=TEXT(".");的这个文件夹,这是个系统隐藏文件夹。下一个一般是cFileName=TEXT("..")的文件夹,这是上级目录文件夹,也是系统隐藏的,除了是在根目录下,因为根目录没有上一级了。在这以后才是我们一般文件夹里面看到的文件。
我们既然说了要遍历所有的子目录来完成我们的任务,那么势必要使用递归递归,那就是遇到一个子目录,就将这个目录全名再次传给递归函数。一遍完成整个目录子树的搜索。
递归函数本身负责文件的处理,和文件夹的递归处理。
下面是一个遍历并删除某个文件夹下所有vs项目工程里的一些非必要文件,以减小磁盘存储大小。
BOOL Travel(TCHAR pszPath[]) { static TCHAR* pszFilter[]=//欲删除文件扩展名 { TEXT(".ncb"), TEXT(".pch"), TEXT(".pdb"), TEXT(".htm"), TEXT(".obj"), TEXT(".idb"), TEXT(".dep"), TEXT(".ilk"), TEXT(".manifest"), TEXT(".res"), TEXT(".suo"), TEXT(".APS") }; TCHAR szPath[PATH_LENGTH];//路径,一般带有通配符 TCHAR szFileNamePrePath[PATH_LENGTH];//路径,不带通配符 TCHAR szFileName[PATH_LENGTH];//完整文件名 if(FAILED(StringCchCopy(szPath,PATH_LENGTH,pszPath))||//保存路径通配符 FAILED(StringCchCopy(szFileNamePrePath,PATH_LENGTH,pszPath))) { MessageBox(NULL,TEXT("Copy Failed!"),szTitle,MB_OK|MB_ICONERROR); return FALSE; } szFileNamePrePath[lstrlen(szFileNamePrePath)-1]=TEXT('/0');//去掉星号 HANDLE hFind=NULL; WIN32_FIND_DATA fd; hFind=FindFirstFile(szPath,&fd); if(INVALID_HANDLE_VALUE==hFind) return FALSE; do { //找到新的目录 if(fd.dwFileAttributes==FILE_ATTRIBUTE_DIRECTORY&& 0!=lstrcmp(fd.cFileName,TEXT("."))&&//不是当前目录 0!=lstrcmp(fd.cFileName,TEXT("..")))//不是上一级目录 { szPath[lstrlen(szPath)-1]=TEXT('/0'); //将路径设为新的路径名称,采用追加的方式 if(FAILED(StringCchCat(szPath,PATH_LENGTH,fd.cFileName))|| FAILED(StringCchCat(szPath,PATH_LENGTH,TEXT("//*")))) { MessageBox(NULL,TEXT("Cat Failed!"),szTitle,MB_OK|MB_ICONERROR); return FALSE; } if(!Travel(szPath)) return FALSE; if(FAILED(StringCchCopy(szPath,PATH_LENGTH,pszPath)))//将路径值更改回来 { MessageBox(NULL,TEXT("Copy Failed!"),szTitle,MB_OK|MB_ICONERROR); return FALSE; } } else if(0!=lstrcmp(fd.cFileName,TEXT("."))&&0!=lstrcmp(fd.cFileName,TEXT("..")))//找到文件 { TCHAR szExtend[FILEEXTEND];//文件扩展名字符串,FILEEXTEND=20(自定义) int iLength=lstrlen(fd.cFileName); int iIndex=iLength-1; //获得扩展名 while(*(fd.cFileName+iIndex)!=TEXT('.')) iIndex--; if(FAILED(StringCchCopy(szExtend,FILEEXTEND,fd.cFileName+iIndex))) { MessageBox(NULL,TEXT("Copy Failed!"),szTitle,MB_OK|MB_ICONERROR); return FALSE; } ////////////////////////////////////////////////////////////////////////// for(int i=0;i<FILETYPE_NUM;i++)//查找是否找到相关可删除文件 if(0==lstrcmp(szExtend,pszFilter[i])) { if(FAILED(StringCchCopy(szFileName,PATH_LENGTH,szFileNamePrePath))|| FAILED(StringCchCat(szFileName,PATH_LENGTH,fd.cFileName)))//得到文件名,第一个函数特别采用copy,防止无限追加产生字符串越界 { MessageBox(NULL,TEXT("Cat Failed In Delete"),szTitle,MB_OK|MB_ICONERROR); break; } if(!DeleteFile(szFileName))//删除不成功 { TCHAR szDeleteFailed[PATH_LENGTH+50]; TCHAR szDeleteFormat[PATH_LENGTH]; LoadString(hInst,IDS_DELETE,szDeleteFormat,PATH_LENGTH); wsprintf(szDeleteFailed,szDeleteFormat,szFileName); MessageBox(NULL,szDeleteFailed,szTitle,MB_OK|MB_ICONERROR); } } } } while (FindNextFile(hFind,&fd)); FindClose(hFind);//关闭句柄 return TRUE; }
具体的细节不在陈述,如果错误,敬请指出。