Wednesday, March 09, 2005

 

Some notes on Windows Shell programming - 1

I.
Windows外壳名字空间的浏览

作者:姜伟华
出处:不详

Windows95/98对Dos/Win3.x作了许多重大改进,在文件系统方面,它除了采用长文件名替代Dos中的8.3文件名以外,引入外壳名字空间(Shell Name Space)来代Dos文件系统是其又一大突破.本文将简要地介绍如何在Windows 95/98或Windows NT4.0以上版本。

概述
简介
  在Dos/Win3.x中,每个逻辑分区构成一棵目录树,文件系统由这一统一的根,而且每个目录或文件必须一一对应于文件系统中客观存在的项。但Windows引入了“外壳名字空间”( Shell Name Space)的概念之后,这一切就都变了。

 外壳名字空间是Windows下的标准文件系统,它大大扩展了Dos文件系统,形成了以“桌面”(Desktop)为根的单一的文件系统树,原有的C盘、D盘等目录树变成了“我的电脑”这一外壳名字空间子树的下一级子树,而像“控制面板”、“回收站”、“网上邻居”等应用程序及“打印机”等设备也被虚拟成了外壳名字空间中的节点。另外,与DOS中物理存储只能和文件系统项一一对应这一点不同的是,一个实际目录在外壳名字空间中可以表现为不同的项。例如“我的文档”与“C;\My Documents”其实都指向“C;\My Documents”目录,但它们在外壳名字空间中是不同的项。如果我们运行Windows 自带的“Windows资源管理器”看一下的话,那么在它的左部树型视图中我们就可以清楚的看到整个外壳名字空间替代DOS文件系统,Windows在文件系统的组织与管理上终于有了质的飞跃。

  为了区别于DOS中“目录”的概念,Windows引入了“文件夹”(Folder)的概念。“文件夹”一般是指外壳名字空间树中的非叶节点,既可以是DOS下的目录,也可是“控制面板”、“回收站”这类虚拟的目录。但外壳名字空间中有些项本身并不是文件夹(即不具有文件夹属性),但却含有子文件夹,比如“网上邻居”等。以下为讲座方便,我们也认为它们是文件夹。

  在下面的讲座过程中我们将用“文件系统”一词来指代DOS文件系统,而用“外壳名字空间”一词来指代Windows中的外壳名字空间:另外用“文件”一词来指代外壳名字空间这棵树中的叶节点(虽然它们不都是物理存储上的文件)。

  在Windows中,Win3.x的文件操作函数,如FindFirstFile、FindNextFile、SetCurrentDirectory等,虽然仍可使用,但用它们只能浏览文件系统,却无法浏览与操纵整个外壳名字空间。要浏览Windows中的外壳名字空间,就必须使用一套全新的、基于COM(组件对象模型)基础上的方法。

新的“路径”PIDL
 在讨论基于 COM的方法之前,我们先来介绍一下外壳名字空间中“路径”的表示问题。DOS的字符串路径只能表示文件系统,而无法表示整个外壳名字空间,所以外壳名字空间提供了一种“路径”的替代物椩乇晔斗斜恚虺莆?/FONT>PIDL)。

  PIDL是一个元素类型为ITEMIDLIST结构的数组,数组中元素的个数是未知的,但紧接着数组末尾的必是一个双字节的零。每个数组元素代表了外壳名字空间树中的一层(即一个文件夹或文件),数组中的前一元素代表的是后一元素的父文件夹。由此可见,PIDL实际上就是指向一块由若干个顺序排列的ITEMIDLIST结构组成、并在最后有一个双字节零的空间的指针。所以PIDL的类型就被Windows定义为ITEMIDLIST结构的指针。

  PIDL亦有“绝对路径”与“相对路径”的概念。表示“相对路径”的PIDL(本文简称为“相对PIDL”)只有一个ITEMIDLIST结构的元素,用于标识相对于父文件夹的“路径”;表示“绝对路径”的PIDL(简称为“绝对PIDL”)有若干个ITEMIDLIST结构的元素,第一个元素表示外壳名字空间根文件夹(“桌面”)下的某一子文件夹A,第二个元素则表示文件夹A下的某一子文件夹B,其余依此类推。这样绝对PIDL就通过保存一条从“桌面”下的直接子文件夹或文件的绝对PIDL与相对PIDL是相同的,而其他的文件夹或文件的相对PIDL就只是其绝对PIDL的最后一部分了。

  但现在就出现了一个问题:即“桌面”的表示问题。外壳名字空间中其他各项都可以用从“桌面”开始的绝对PIDL加以表示,但“桌面”的PIDL数组显然一个元素都没有。这样就只剩下PIDL数组最后的那个双字节的零了。所以,“桌面”的PIDL就是一个16位的零。注意:“桌面”内容是一个双字节的零。另外,虽然“桌面”表示的是“C:\ Windows \Desktop”文件夹(这里假定Windows的系统目录为“C:\ Windows”)的内容,但“桌面”与“C:\ Windows \Desktop”文件夹的PIDL是完全不同的。这一点同样适用于“我的文档”与“C:\ My Documents”等文件夹。

  DOS中的路径是一个字符串,但PIDL是一种二进制结构,所以我们不能直接从PIDL中获知它所代表的到底是哪个文件夹或文件,而必须调用相应的函数把它转换成代表路径的字符串。如果某绝对PIDL是文件系统的一部分,则调用SHGetPathFromIDList函数即可;但如不是,就无法获得路径字符串了,因为DOS中根本就不存在这种路径。但很可惜的是,Windows并没有提供一个函数来让我们方便地把文件系统的路径字符串转换成PIDL。不过我们可用一个我们自己实现的函数ParsePidlFromPath()来达目的(具体函数的实现见下文)。

  PIDL的创建与释放一般并不使用C++的new和delete操作或C语言的malloc和free函数,而必须使用专门的方法进行.首先调用SHGEetMallocI函数得到Malloc接口(COM接口的一种,关于COM接口下面将详述)的指针,再调用该接口的Alloc方法为PIDL分配空间,或调用该接口的Free方法释放某个PIDL占用的空间。最后调用该接口的Release方法释放该接口。

 除了下面将要介绍的IShellFolder、IEnumIDList等COM接口可以操作PIDL外,还有很多以SH开头的Windows API函数也可操作PIDL,不过一般这些函数都要求使用绝对PIDL作参数。例如SHGetFileInfo函数可得到某一PIDL所指对象的各种信息,包括名字、图标、属性等;SHFileOperation函数可对外壳名字空间中的项进行拷贝、移动、改名、删除等操作;SHBrowseForFolder可以显示一个让用户选择外壳名字空间中某一文件夹的浏览对话框.

基于COM的方法
  讨论清楚了PIDL的概念之后,我们回过头来讨论基于COM之上的浏览外壳名字空间的方法。如果说PIDL是外壳的名字空间中的“路径”的话,那么下面所说的两个COM接口IshellFolder与IEnumIDList就起着与Win 3.x中的FindFirstFile、FindNextFile等函数类似的功能。

  在Windows中,每个文件夹都由操作系统实现了一个派生自Iunknown接口(COM接口的最基本类)的接口IshellFolder。通过调用某个文件夹的该接口,即可实现对该文件夹的浏览,得到该文件夹中子项(子文件夹或文件)的各种相关信息。

  我们可以调用SHGetDesktopFolder函数来获得外壳名字空间的根文件夹(即“桌面”)的IshellFolder接口。对于某个文件夹A,以它的子文件夹B的相对PIDL为参数,调用它的IshellFolder接口的BindToObject方法即可得到子文件夹B的IshellFolder接口。如要枚举某个文件夹下的子项,则只需调用它的IshellFolder接口的EnumObjects方法即可获得一个IEnumIDList接口。通过调用该IEnumIDList接口的Next方法我们即可枚举出该文件夹的所有子项(包括文件夹和文件等对象),获得它们的相对PIDL。使用父文件夹的IshellFolder接口和这些相对PIDL,我们即可获得这些子项的各种相关信息,包括显示名称、图标、属性等,甚至还可以获得它的右键菜单。例如,调用该接口的GetDisplayNameOf方法可获得该文件夹下子项的显示名称;调用ParseDisplayName方法可把某个子项的用Unicode内码表示的字符串路径翻译成对应的PIDL。这样通过PIDL和这两个接口,我们就可以遍历和操纵整个外壳名字空间了。

  除了IshellFolder和IEnumIDList接口以外,Windows 外壳名字空间还提供了很多其他COM接口,例如IshellBrowser、IshellLink、IshellIcom、IshellView等。通过这些接口,应用于程序就可以更好的与外壳名字空间交互。由于本文篇幅有限,这些接口就不详细介绍了,有兴趣的读者可参阅相关资料。

  值得注意的是,COM中的接口虽然在使用上与C++中的类是非常相似(事实上COM接口在C ++中就是以类的形式声明的),但维护其正确的引用计数机制是非常重要的。每增加一个对该接口的引用,就要调用一次它的AddRef( )方法;而在使用完后必须调用它的Release( )方法释放该接口。关于COM及COM接口的细节请参见相关资料,这里不再赘述。

  可惜的是,虽然我们可依照上文给出的方法实现外壳名字空间的逐层展开,但外壳名字空间却并没有提供一种让我们自由跳转到某一文件夹的方法,也没有提供返回到上一级文件夹的方法,因为我们无法方便地获得父文件夹的IshellFolder接口。如果要返回,就必须由应用程序自己想方法获得父文件夹的IshellFolder接口。一种可行的方法是在展开外壳名字空间时保存每个文件夹的IShellFolder接口指针和它的绝对PIDL,这样就可以相对容易地实现自由跳转了。

  但无论如何,外壳名字空间提供的浏览和操作的方法比起DOS/ Windows 3.x的函数来还是有着巨大的飞跃的。只要我们理解清楚了这种方法的优点与不足,我们就可以扬长避短,开发出各种各样的使用外壳名字空间的程序来。

相关接口、函数和数据结构
  对于本文所涉及的一些比较复杂的接口、函数和数据结构,以下仅列举出作者在Visual C++6.0查到的声明与定义,并配上相应的注释.一些较简单的则从略,未列出的请参见相关资料。

数据结构
typedf IshellFolder*LPSHELLFOLDER;

//IshellFolder接口指针的声明

typedef struct _ITEMIDLIST{//ITEMIDLIST结构的定义

SHITEMID mkid;

}ITEMIDLIST, * LPITEMIDLIST;

typedef struct _SHITEMID{//ITEMIDLIST结构中元素的定义

USHORT cb;//本结构的长度(以字节计)

BYTE abID[1];//可变长的元素标识符

}SHITEMID, *LPSHITEMID;

typedef struct _SHFILEINFO{//SFFILEINFO结构的定义

HICON hicon;//文件图标的句柄

Int ilcon;//图标在系统图像列表中的序号

DWORD dwAttributes;//文件的属性

Char szDisplayName [MAX_PATH];//显示名称或路径

Char szTypeName[80];//表示文件类型的字符串

} SHFILEINFO;

2.相关接口

2.1 IshellFolder接口的方法

BindToObject
格式:HRESULT BindToObject( LPCITEMIDLLIST pidl, LPBC pbcreserved, REFIID riid, LPVOID *ppvOut);

作用:得到本文件夹中某一子文件夹的IShellFolder接口。

参数:Riid应为IID_IshellFolder, pbcReserved应为NNUL,pidl为表示该子文件夹的“相对路径”的PIDL,从ppvOut中返回要求的IshellFolder接口的指针。

EnumObjects
格式:HRSULT EnumObjects( HWND hwndOwner, DWORD grfFlags, LPENUMIDLIST*ppenumIDList);

作用:枚举本文件夹的成员。

参数:hwndOwmer为父窗口句柄,grfFlags决定枚举世闻名的内容,可为SHCONTF_FOLDERS、SHCONTF_NONFOLDERS、SHCONTF_INCLUDEHIDDEN的组合,从ppenumIDList返回IEnumIDList接口的指针。

(3)GetDisplayNameOf

格式:HRESULT GetDisplayNameOf (LPCITEMIDLIST pidl, DWORD uFlags, LPSTRRERT lpName);

作用:得到本文件夹中某一对象的显示名称。

参数:pidl为表示该子文件夹的“相对路径”的PIDL, uFlags为 SHGDN_NORMAL、SHGDN_INFOLDER、SHGFI_SYSICONINDEX、SHGFI_EXETYPE、SHGFI_ATTRIBUTES、SHGFI_PIDL、SHGFI_DISPLAYNAME、SHGFI_LARGEICON等。

返回值:如uFlags包含SHGFI-EXETYPE标志,则返回值为该可执行文件夹类型;如uFlags包含SHGFI_SYSICONINDEX标志,则返回值为系统图像列表的句柄。否则,如本函数调用成功则返回非零值,失败则返回零。

应用举例
几个非常有用的函数的实现
1.1ParsePidlFromPath

描述:将文件系统路径翻译成对应的PIDL。LPITEMIDLIST ParsePidlFromPath(LPCSTR path)

{

//存放以Unicode内码表示的路径字符串的缓冲区

OLECHAR szOleChar[MAX_PATH];

//“桌面“的IshellFolder接口指针

LPSHELLFOLDER IpsfDeskTop;

//返回的PIDL

LPITEMIDLIST Ipifq;

ULONG ulEaten, ulAttribs;

HRESULT hres;

//得到“桌面”的IshellFolderr 接口指针

SHGetDesktopFolder(&lpsfDeskTop);

//将Ansi字符集的路径字符串转换成Unicode字符串,

存入szOleChar

MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,

Path,-1,szOleChar,sizeof(szOleChar));

//将szOleChar,中的路径径字符串翻译成相应的PIDL,存入lpifq

hres=lpsfDeskTop->Release( );

//如果翻译失败,则返回NULL

if(FAILED(hres))return NULL;

return lpifq;

1.2 GetItemIcon

描述:返回lpi这个绝对PIDL所指项的图标在系统图像列表中的序号,uFlags为要求的图标类型。

Int Getltemlcon(LPITEMIDLIST lpi, UINT uFlags)

{

//存放文件信息的结构

SHFILEINFO sfi;

//给uFlags增加一些公共标志(lpi为PIDL、要求返回系统图像列表、要求小图标)

uFlags|=SHGFI-PIDL |SHGFI_SYSICONINDEX |SHGFI_SMALLICON;

获得图标

SHGetFileinfo( (LPCSTR) lpi, 0, & sfi , sizeof(SHFILEINFO),uFlags);

//返回图标在系统图像列表中的序号

return sfi,ilcon;

}

1.3 GetName

描述:lpio lpsf所指的IshellFolder接口代表的文件夹下的相对PIDL,本函数获得lpi所指项的显示名称,dwFlags表明欲得到的显示名称类型,lpFriendlyName为存放显示名称的缓冲区。

BOOL GetName(LPSHELLFOLDER lpsf,LPITEMIDLIST lpi,DWORD dwFlags,LPSTR lpFriendlyName)

{

STRRET str;

//得到显示名称

if(NOERROR!=lpsf->GetDisplayNameOf()lpi,dwFlags,&str))

return FALSE;

//根据返回值进行转换

switch(str uType)

{

//如为Unicode字符串,则转成Ansi字符集的字符串case STRRET_WSTR:

WideCharToMultiByte(CP_ACP,0,str.pOleStr,-1,ipFriendlyName,sizeof(lpFriendlyName),NULL,NULL);

Break;

//如为偏移量,则去除偏移量

case STRRET_OFFSET:

lstrcpy(lpFriendlyName,(LPSTR)lpi+str.uOffset);

break;

如为Ansi字符串,则直接拷贝

case STRRET_CSTR:

Lstrcpy(lpFriendlyName,(LPSTR)str.cStr);

Break;

//非法情况

default:

return FALSE;

}

return TRUE;

一个实例
以下我们将用Visual C++6.0制作一个例子来演示外壳名了空间的浏览。具体为使用Ctreer View,展开外壳名字空间中的“桌面”文件夹,枚举出该文件夹下的所有子文件夹。

在这个项目中,CtreeView 的图像列表我们使用Windows 的系统图像列表,而不是自己创建一个。

首先,用AppWizard新建一个项目,类型为MFC AppWizard(exe),项目名为Test;在第一步中选择Single document;在第六步中将CtestView的基类改为CtreeView。其它均使用默认设置。

其次,在CtestView中加一个私有成员变量m_ImageList,类型为CimageList,用于保存系统列表。(Windows中所有的图标都保存在系统图像列表中,我们可以在程序中得到这个图像列表)。

第三步,将上文提到的GetName 和GetItemIcon 这两个函数的实现拷贝到CtestView.cpp的较开头的位置。

第四步,在CtestView的OnInitialUpdate( )函数中加入以下代码:

//系统图像列表的句柄

HIMAGELIST himlSmall;

//存放文件信息的结构

SHFILEINFO sfi;

//存放树型控件中的节点的信息

TV_INEM tvi;

//向树型控件中插入节点时使用的结构

TV_INSERTSTRUCT tvis;

//欲插入节点的前一节点的句柄

HTREEITEM hParent=TVI_FIRST;

//欲节点的父节点的句柄

HTREEITEM hParent=TV_ROOT;

//某一文件夹的IshellFolder接口指针

LPSHELLFOLDER lpsf=0;

//IenumiDList接口的指针

LPENUMIDLIST lpe=0;

//lpi为一PIDL

LPITEMIDLIST lpi=0;

//IMalloc接口的指针

LPMALLOC lpMalloc=0;

//枚举的个数

ULONG ulFetched;

//存放显示名称的缓冲区

char szBuff[MAX_PATH];

//获得系统图像列表,并把它赋给CtestView的CtreeCtrl控件

himlsmall=(HIMAGELIST)SHGetFileinfo(“C:\\”,0,&

sfi, sizeof(SHFIEINFO), SHGFI_SYSICONINDEX|SHGFI_SMALLICON);

m_lmageList.Attach(himlsmall);

GetTreeCtrl().SetlmageList(&m_imageList,TVSIL_NORMAL);

//获得Imalloc接口的指针

SHGetMalloc(&lpMalloc);

//获得“桌面”文件夹的IshellFolder接口指针

SHGetDesktopFloder(&lpsf);

//创建一个“桌面”的绝对PIDL

lpi=(LPITEMIDLIST)lpMalloc->Alloc(sizeof(USHORT0));

*((USHORT*)lpi)=0

//设置要插入的树节点信息

tvi,mask=TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE|

TVIF_CHILDREN;

tvi.cchTextMax=MAX_PATH;

//设置显示名称

tvi.pszText=_T(“桌面”);

//获得标准图标和展开时的图标

tvi.ilmage=Tetltemlcon(lpi,NULL);

tvi.iSelectedlmage=Getltemlcon(lpi,SHGFI_OPENICON);

//设置插入位置

tvis.item=tvi;

tvis.hlnsertAfter=TVI_FIRST;

tvis.hParent=TVI_ROOT;

//插入根节点

hpParent=GetTreeCtrl().Instrtltem(& tvis);

//释放lpi所占的空间

lpMalloc->Free(lpi);

//获得“桌面”文件夹的IenumiDList接口指针lpe

lpsf->EnumObjects(m_hWnd, SHCONTF_FOLDERS |

SHCONTF_NONFOLDERS,& lpe);

//枚举“桌面”下的各个子文件夹

while(S_OK= =lpe-> Next(1,&lpi,&ulFetched))

{

//获得lpi表示的子文件夹的显示名称

GetName(lpsf,lpi,SHGDN_NORMAL,szBuff);

tvi.pszText=szBuff;

// 获得该项的图标

//由于是“桌面”下的直接子项,所以它的相对PIDL与绝对PIDL是一致的

tvi.ilmage=Getltemlcon(lpi,NULL);

tvi.iSelectedimage=Getltemlcon(lpi,SHGFI_OPENICON);

//设置插入位置

tvis.item=tvi;

tvis.hinsertAfter=hPrev;

tvis.hParent=hParent;

//插入节点

hPrev=GetTreeCtrl(). insertltem(& tvis);

//释放lpi所占的空间

ipMalloc->Free(lpi);

}

//释放Imalloc和IsshellFolder接口

lpMalloc->Release();

lpsf->Release();

//对生成的节点进行排序

GetTreeCtrl( ).SortChildren(hParent);

//将CtestView中的“桌面”节点展开

GetTreeCtrl( ).Selectltem(hParent);

GetTreeCtrl( ).Expand(hParent,TVE_EXPAND);

最后,响应CtestView的WM_DESTROY消息,加入以下代码:

//由于使用了系统图像列表,退出时必须释放对它的所有权

//否则,退出后Windows将一个图标没有

m_imageList.Detach( );

这个演示程序的效果如下图所示:

后记
  由于篇幅的关系,本文所举的例子只能非常简单的演示一下外壳名字空间的浏览,很多较复杂的编程方法都没有表现出来。最后,希望本文能够起到抛砖引玉的作用,让更多的开发者认识与使用外壳名字空间,开发出更好的程序来。

参考文献
MicrosoftCorporation. Microsoft Windows95程序员指南,清华大学出版社,1996
StefamoMaruzzi.Windows95开发者必读,电子工业出版社,1997

II.
ITEMIDLIST和Path的互相转化

小case而已,仅为回答某人问题

反向
SHGetPathFromIDList(LPCITEMIDLIST pidl,LPSTR pszPath)

正向,jiangsheng提供

方法一:
HRESULT ItemIdListFromPath( LPITEMIDLIST& pidl,LPCSTR szPath)
//szPath is assumpted MAX_PATH size
{
LPSHELLFOLDER pDesktopFolder;
OLECHAR olePath[MAX_PATH];
ULONG chEaten;
ULONG dwAttributes;
HRESULT hr;

//
// Get a pointer to the Desktop's IShellFolder interface.
//
if (FAILED(SHGetDesktopFolder(&pDesktopFolder)))
{
return -1;
}

//
// IShellFolder::ParseDisplayName requires the file name be in
// Unicode.

MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1, olePath, MAX_PATH);

// Convert the path to an ITEMIDLIST.
hr = pDesktopFolder->ParseDisplayName(NULL,NULL,olePath,&chEaten,
&pidl,&dwAttributes);
if (FAILED(hr))
{
// Handle error.
::AfxMessageBox (IDS_FAIL_PARSING_PATH);
}

// pidl now contains a pointer to an ITEMIDLIST for .\readme.txt.
// This ITEMIDLIST needs to be freed using the IMalloc allocator
// returned from SHGetMalloc().
//release the desktop folder object

pDesktopFolder->Release();
return 0;
}


另外jiangsheng谈到SHParseDisplayName Function的用法如下

Translates a Shell namespace object's display name into an item identifier list and returns the attributes of the object. This function is the preferred method to convert a string to a pointer to an item identifier list (PIDL).

Syntax

HRESULT SHParseDisplayName(

PCWSTR pszName,

IBindCtx *pbc,

LPITEMIDLIST *ppidl,

SFGAOF sfgaoIn,

SFGAOF *psfgaoOut

);

Parameters
pszName

[in] Pointer to a zero-terminated wide string that contains the display name to parse.

pbc
[in] Optional bind context that controls the parsing operation. This parameter is normally set to NULL.

ppidl
[out] Address of a pointer to a variable of type ITEMIDLIST that receives the item identifier list for the object. If an error occurs, then this parameter is set to NULL.

sfgaoIn
[in] ULONG value that specifies the attributes to query. To query for one or more attributes, initialize this parameter with the flags that represent the attributes of interest. For a list of available SFGAO flags, see IShellFolder::GetAttributesOf.

psfgaoOut
[out] Pointer to a ULONG. On return, those attributes that are true for the object and were requested in sfgaoIn will be set. An object's attribute flags may be zero or a combination of SFGAO flags. For a list of available SFGAO flags, see IShellFolder::GetAttributesOf.

Return Value
Returns S_OK if successful, or an error value otherwise.
Function Information

Stock Implementation shell32.dll version 6.0 or later
Custom Implementation No
Header shlobj.h
Import library shell32.lib
Minimum operating systems Windows XP (按:看到了!!)

方法二:
// Given a VARIANT, pull out the PIDL using brute force

LPITEMIDLIST PidlFromVARIANT(LPVARIANT pvarLoc)
{
if (pvarLoc)
{
if (V_VT(pvarLoc) == (VT_ARRAY|VT_UI1))
{
LPITEMIDLIST pidl = (LPITEMIDLIST)pvarLoc->parray->pvData;
return pidl;
}
}
return NULL;
}

// Pack a PIDL into a VARIANT

HRESULT InitVARIANTFromPidl(LPVARIANT pvar, LPITEMIDLIST pidl)
{
if (!pidl || !pvar)
{
return E_POINTER;
}

// Get the size of the pidl and allocate a SAFEARRAY of
// equivalent size

UINT cb = ILGetSize(pidl);
LPSAFEARRAY psa = MakeSafeArrayFromData((LPBYTE)pidl, cb);

if (!psa)
{
VariantInit(pvar);
return E_OUTOFMEMORY;
}

V_VT(pvar) = VT_ARRAY|VT_UI1;
V_ARRAY(pvar) = psa;
return NOERROR;
}



// Allocate a SAFEARRAY of cbData size and pack pData into it

LPSAFEARRAY MakeSafeArrayFromData(LPBYTE pData, DWORD cbData)
{
LPSAFEARRAY psa;

if (!pData || 0 == cbData)
{
return NULL; // nothing to do
}

// create a one-dimensional safe array of BYTEs
psa = SafeArrayCreateVector(VT_UI1, 0, cbData);

if (psa)
{
// copy data into the area in safe array reserved for data
// Note we party directly on the pointer instead of using locking/
// unlocking functions. Since we just created this and no one
// else could possibly know about it or be using it, this is okay.

memcpy(psa->pvData,pData,cbData);
}
return psa;
}

补充:那么在C#中呢?请看代码

public class Directory
{
public static bool Exists( string ObjectDirectory )
{
return System.IO.Directory.Exists( ObjectDirectory );
}

public static void Create( string ObjectDirectory )
{
Win32API.CreateDirectoryEx( @OS.Path.WindowsDirectory , ObjectDirectory , 0 );
}

public static void Remove( string ObjectDirectory )
{
Win32API.RemoveDirectory( ObjectDirectory );
}

public static string Browse( System.Windows.Forms.Form ObjectForm , string DialogTitle )
{
System.Text.StringBuilder _BrowsePath = new System.Text.StringBuilder( 255 );
Win32API.BrowseInfo _BrowseInfo = new Win32API.BrowseInfo();

BrowseInfo.hWndOwner = ObjectForm.Handle.ToInt32();
_BrowseInfo.lpszTitle = DialogTitle;
_BrowseInfo.ulFlags = Win32API.BIF_RETURNONLYFSDIRS;

Win32API.SHGetPathFromIDList( Win32API.SHBrowseForFolder( ref _BrowseInfo ) , _BrowsePath );

return _BrowsePath.ToString();
}
}

哇哈哈,原来还有这么类似的方法
3/21/2004修改

III.
CSIDL对应的常用特殊文件夹

作者:Drate
出处:不详

CSIDL 表示的是windows shell中一个特殊文件夹的标识值。这里说的特殊文件夹指的是由windows shell所定义的文件夹,例如:"我的文档","回收站","Inernet Cache文件夹"就是一些由SHELL指定的特殊文件夹.这些特殊文件夹可以是一个磁盘上的物理路径,也可以是一个虚拟文件夹(Virtual Folder)!
注:Virtual Folder
虚拟文件夹是一个由windows shell定义过的文件夹,但这个文件夹并没有与你的电脑上的任何物理 路径相对应.例如:“我的电脑 ”就是一个包含了所有驱动器的虚拟文件夹,还有如“控制面板”也是一个虚拟文件夹,等等。虚拟文件夹很少应用于非shell函数。
在windows2000/NT中,许多csidl所涉及的文件夹都是针对各个用户的。但类似于CSIDL_COMMON_* 可以供所有用户使用。

在许多应用中,一个CSIDL可以与下面的标识之一进行组合使用:

CSIDL_FLAG_CREATE($8000)
windows 2000:如果这个CSIDL所引用的文件夹不存在,则创建这个文件夹!.
CSIDL_FLAG_DONT_VERIFY($4000)
WINDOWS 2000: 无论这个CSIDL所引用的文件夹是否存在都使用它!

CSIDL_ADMINTOOLS($30)
表示当前用户的“管理工具”系统文件夹
CSIDL_ALTSTARTUP($h1D)
不详
CSIDL_APPDATA($1A)
表示当前用户的特定的应用程序数据存储文件夹,例如:C:\Documents and Settings\username\Application Data
CSIDL_BITBUCKET($A)
表示回收站
CSIDL_COMMON_ADMINTOOLS($2F)
表示所有用户的“管理工具”系统文件夹
CSIDL_COMMON_ALTSTARTUP($1D)
表示所有用户的 (只支持WINDOWS NT 系统)
CSIDL_COMMON_APPDATA($23)
表示所有用户的特定的应用程序数据存储文件夹,例如:C:\Documents and Settings\All Users\Application Data
CSIDL_COMMON_DESKTOPDIRECTORY($19)
表示所有用户的“桌面”文件夹,例如:C:\Documents and Settings\All Users\Desktop。
CSIDL_COMMON_DOCUMENTS($2E)
表示所有用户的“我的文档”文件夹,例如:C:\Documents and Settings\All Users\Documents
CSIDL_COMMON_FAVORITES($1F)
表示所有用户的“我的收藏夹”文件夹。
CSIDL_COMMON_PROGRAMS($17)
表示所有用户的“程序”文件夹,例如:C:\Documents and Settings\All Users\Start Menu\Programs
CSIDL_COMMON_STARTMENU($16)
表示所有用户的“开始菜单”文件夹,例如:C:\Documents and Settings\All Users\Start Menu
CSIDL_COMMON_STARTUP($18)
表示所有用户的“启动”文件夹,例如:C:\Documents and Settings\All Users\Start Menu\Programs\Startup
CSIDL_COMMON_TEMPLATES($2D)
表示所有用户的“模块”文件夹,例如:C:\Documents and Settings\All Users\Templates
CSIDL_CONTROLS($3)
表示“控制面板”文件夹
CSIDL_COOKIES($21)
表示当前用户Internet Explorer的cookie 文件夹,例如:C:\Documents and Settings\username\Cookies
CSIDL_DESKTOP($0)
表示“桌面”虚拟文件夹,包含了计算机中的所有内容
CSIDL_DESKTOPDIRECTORY($10)
表示当前用户的“桌面”文件夹,例如:C:\Documents and Settings\username\Desktop
CSIDL_DRIVES($11)
表示“我的电脑”虚拟文件夹,包含了计算机中所有的驱动器
CSIDL_FAVORITES($6)
表示当前用户的“收藏夹”文件夹,例如:C:\Documents and Settings\username\Favorites
CSIDL_FONTS($14)
表示“系统字体”文件夹,例如:C:\WINNT\Fonts
CSIDL_HISTORY($22)
表示Inernet Explorer的“历史记录”文件夹
CSIDL_INTERNET($1)
表示Internet的这个虚拟文件夹
CSIDL_INTERNET_CACHE($20)
表示当前用户的Internet Explorer的"Cache"文件夹,例如:C:\Documents and Settings\username\Temporary Internet Files
CSIDL_LOCAL_APPDATA($1C)
表示当前用户的应用程序数据文件夹,例如:C:\Documents and Settings\username\Local Settings\Application Data
CSIDL_MYMUSIC
表示当前用户存储音乐文件的文件夹,例如:C:\Documents and Settings\User\My Documents\My Music
CSIDL_MYPICTURES($27)
表示当前用户存储图像文件的文件夹,例如:C:\Documents and Settings\username\My Documents\My Pictures
CSIDL_NETHOOD($13)
表示当前用户存在的网络连接的文件夹,例如:C:\Documents and Settings\username\NetHood
CSIDL_NETWORK($12)
表示“网上邻居”这个虚拟文件夹
CSIDL_PERSONAL($5)
表示当前用户的“我的文档”文件夹,例如:C:\Documents and Settings\username\My Documents
CSIDL_PRINTERS($4)
指向“打印机”这个虚拟文件夹
CSIDL_PRINTHOOD($1B)
表示当前用户存在的网络打印机的虚拟文件夹,例如:C:\Documents and Settings\username\PrintHood
CSIDL_PROFILE($28)
表示当前用用户配置文件的文件夹
CSIDL_PROGRAM_FILES($26)
表示程序文件的文件夹,例如:C:\Program Files
CSIDL_PROGRAM_FILES_COMMON($2B)
表示系统程序共用组件文件夹,例如:C:\Program Files\Common
CSIDL_PROGRAMS($2)
表示当前用户的“程序”菜单文件夹,例如:C:\Documents and Settings\username\Start Menu\Programs
CSIDL_RECENT($8)
表示当前用户的“文档”菜单文件夹,例如:C:\Documents and Settings\username\Start Menu\Programs
CSIDL_SENDTO($9)
表示当前用户的“发送到”文件夹,例如:C:\Documents and Settings\username\SendTo
CSIDL_STARTMENU($B)
表示当前用户的“开始”菜单文件夹,例如:C:\Documents and Settings\username\Start Menu
CSIDL_STARTUP($7)
表示当前用户的“启动”菜单文件夹,例如:C:\Documents and Settings\username\Start Menu\Programs\Startup
CSIDL_SYSTEM($25)
表示WINDOWS系统的系统文件夹,例如:C:\WINNT\SYSTEM32
CSIDL_TEMPLATES($15)
表示的是系统中存储文档模块的文件夹
CSIDL_WINDOWS($24)
表示的是系统中Windows目录的文件珍,例如:C:\WINNT

CSIDL可以在以下API函数中进行操作使用,至于函数的说明,我们可以参照MSDN中的API说明:
SHGetFolderLocation, SHGetFolderPath, SHGetSpecialFolderLocation, SHGetSpecialFolderPath。

IV.
使用虚列表和自画实现文件夹的缩略图显示

作者:jiangsheng
出处:http://www.csdn.net/develop/article/22/22243.shtm

本示例演示了列表控件的虚列表和自画功能,也演示了一些系统外壳的函数和接口的使用方法。

单击 这里 下载本文的代码。

预备性阅读
在阅读本文之前,建议先对列表视图控件和系统外壳有一个基本的了解。建议阅读以下SDK文章

Shell FAQ
List-View Controls Overview
Using List-View Controls
Customizing a Control's Appearance Using Custom Draw
创建应用程序
使用MFC应用程序向导创建一个SDI应用程序,在最后一步选择视图的基类为CListView。创建完成之后,在资源中去掉保存、编辑和打印等功能的菜单和工具栏按钮(因为这些功能没有实现)。

虚列表的创建
本文采用虚列表技术,使得显示信息是在第一次显示的时候才被获取。为了创建虚列表,在创建之前需要指定列表的风格

BOOL CPicViewView::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style&=~LVS_TYPEMASK;
cs.style|=LVS_ICON|LVS_OWNERDATA;
return CListView::PreCreateWindow(cs);
}
同时,因为列表项的Overlay图标也是被动态获取的,所以需要设置动态Overlay图标

void CPicViewView::OnInitialUpdate()
{
CListView::OnInitialUpdate();
GetListCtrl().SetCallbackMask(LVIS_OVERLAYMASK);
}

缓存显示信息
在列表需要显示一个范围的项目之前,列表会发送LVN_ODCACHEHINT通知,应用程序可以捕获这个消息来缓存部分列表的显示信息,以提高性能。

void CPicViewView::OnOdcachehint(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVCACHEHINT* pCacheHint = (NMLVCACHEHINT*)pNMHDR;
PrepCache(0,min(5,m_arpFolderItems.GetSize()));
PrepCache(pCacheHint->iFrom,pCacheHint->iTo);
PrepCache(max(0,m_arpFolderItems.GetSize()-5),m_arpFolderItems.GetSize());
*pResult = 0;
}
在列表需要显示一个项目之前,列表会发送LVN_GETDISPINFO通知,应用程序可以捕获这个消息来提供项目的显示信息。如果显示时需要显示的列表项在缓存中,那么可以从缓存中获取显示信息。否则需要重新从文件获得。

void CPicViewView::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
if(pDispInfo->item.iItem==-1)return;
HRESULT hr=S_OK;
LPCITEMIDLIST pidlItem=m_arpFolderItems[pDispInfo->item.iItem];
CFolderItemInfo* pFolderItemInfo=FindItemInCache(pidlItem);
BOOL bCached=TRUE;
if(pFolderItemInfo==NULL){
bCached=FALSE;
pFolderItemInfo=new CFolderItemInfo;
GetItemInfo(pidlItem,pFolderItemInfo);
}
if(pDispInfo->item.mask&LVIF_TEXT){
lstrcpyn(pDispInfo->item.pszText,pFolderItemInfo->tszDisplayName,pDispInfo- >item.cchTextMax);
}
if(pDispInfo->item.mask&LVIF_IMAGE){
pDispInfo->item.iImage=pFolderItemInfo->iIcon;
}
if(pDispInfo->item.mask&LVIF_STATE){
pDispInfo->item.state=pFolderItemInfo->state;
}
if(!bCached)
delete pFolderItemInfo;
*pResult = 0;
}


文件图标的显示
默认情况下,列表项的图标就是其系统图标。首先获得系统图像列表

int CPicViewView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CListView::OnCreate(lpCreateStruct) == -1)
return -1;
HRESULT hr = SHGetMalloc(&m_pMalloc); if(FAILED(hr)) return -1;
hr = SHGetDesktopFolder(&m_psfDesktop);if(FAILED(hr)) return -1;
SHFILEINFO shfi;
ZeroMemory(&shfi,sizeof(SHFILEINFO));
HIMAGELIST hi=(HIMAGELIST)SHGetFileInfo(NULL,0,&shfi,sizeof(SHFILEINFO),SHGFI_ICON |SHGFI_SYSICONINDEX|SHGFI_SMALLICON);
GetListCtrl().SetImageList(CImageList::FromHandle(hi),LVSIL_SMALL);
hi=(HIMAGELIST)SHGetFileInfo(NULL,0,&shfi,sizeof(SHFILEINFO),SHGFI_ICON |SHGFI_SYSICONINDEX|SHGFI_LARGEICON);
GetListCtrl().SetImageList(CImageList::FromHandle(hi),LVSIL_NORMAL);
return 0;
}

然后在获取文件信息时,从文件获得其图标在系统图像列表中的索引。

如果列表项是图像文件,并且从文件成功载入图像,那么使用自画功能以替换默认的图标。

void CPicViewView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLVCUSTOMDRAW lpNMCustomDraw = (LPNMLVCUSTOMDRAW) pNMHDR;
switch(lpNMCustomDraw ->nmcd.dwDrawStage) {
case CDDS_PREPAINT : *pResult=CDRF_NOTIFYITEMDRAW;return;
case CDDS_ITEMPREPAINT:*pResult=CDRF_NOTIFYPOSTPAINT;return;
case CDDS_ITEMPOSTPAINT:
{
int iItem=lpNMCustomDraw ->nmcd.dwItemSpec;
if(iItem==-1){
*pResult=CDRF_DODEFAULT;return;
}
CFolderItemInfo* pItemInfo=FindItemInCache(m_arpFolderItems[iItem]);
if(pItemInfo==NULL||pItemInfo->bFailLoadPic||pItemInfo->pic.m_pPict==NULL){
*pResult=CDRF_DODEFAULT;return;
}
CRect rectIcon;
GetListCtrl().GetItemRect(iItem,&rectIcon,LVIR_ICON);
CDC* pDC=CDC::FromHandle(lpNMCustomDraw->nmcd.hdc);
pItemInfo->pic.Render(pDC,rectIcon,rectIcon);
}
*pResult=CDRF_NEWFONT;return;
}
* pResult=0;
}

上面的代码是使用获取的文件显示信息中的图像,在列表项图标的区域画图。

获取显示信息
为了缓存列表项的显示信息,或者显示列表项,需要获取列表项的文字、图标、Overlay图标和缩略图等信息。这里使用了ILCombine来把缓存中的相对PIDL转化为完整的Pidl,再据此获得文件的完整路径,然后调用OleLoadPicturePath函数载入图像。

void CPicViewView::GetItemInfo(LPCITEMIDLIST pidl,CFolderItemInfo* pItemInfo)
{
HRESULT hr = theApp.SHGetDisplayNameOf(pidl,pItemInfo->tszDisplayName);
IShellIcon* pShellIcon=NULL;
hr=m_psfFolder->QueryInterface(IID_IShellIcon,(LPVOID*)&pShellIcon);
if (SUCCEEDED(hr)&&pShellIcon){
pShellIcon->GetIconOf(pidl,0,&pItemInfo->iIcon);
pShellIcon->Release();
}
IShellIconOverlay* pShellIconOverlay =NULL;
hr=m_psfFolder->QueryInterface(IID_IShellIconOverlay,(LPVOID*)&pShellIconOverlay);
if (SUCCEEDED(hr)&&pShellIconOverlay){
int nOverlay=0;
pShellIconOverlay->GetOverlayIndex(pidl,&nOverlay);
pItemInfo->state=INDEXTOOVERLAYMASK (nOverlay);
pShellIconOverlay->Release();
}
LPITEMIDLIST pidlItemFull=ILCombine(m_pidlFolder,pidl);
if(pidlItemFull){
if(SHGetPathFromIDList(pidlItemFull,pItemInfo->tszPath)){
USES_CONVERSION;
hr=OleLoadPicturePath(
T2OLE(pItemInfo->tszPath)
,NULL,0,RGB(255,255,255)
,IID_IPicture,(LPVOID*)&pItemInfo->pic.m_pPict);
if(FAILED(hr)){
pItemInfo->bFailLoadPic=TRUE;
TRACE("OleLoadPicturePath failed %s\r\n",pItemInfo->tszPath);
}
}
}
m_pMalloc->Free(pidlItemFull);
}
}

缓存目录的数据
在更改目录时,需要重建目录内容的缓存。这包括目录的pidl和IShellFolder接口指针,目录内容的相对pidl,以及列表项的显示信息(基于性能上的考虑,列表项的显示信息是在接收到LVN_ODCACHEHINT通知的时候缓存的)。

LPITEMIDLIST m_pidlFolder;
IShellFolder * m_psfFolder;
CTypedPtrArray m_arpFolderItems;
CTypedPtrMap m_mapCache;


void CPicViewView::EnterFolder(LPCITEMIDLIST pidl)
{
USES_CONVERSION;
m_pidlFolder=ILClone(pidl);
if(m_pidlFolder){
LPENUMIDLIST ppenum = NULL;
LPITEMIDLIST pidlItems = NULL;
ULONG celtFetched;
HRESULT hr;
hr = m_psfDesktop->BindToObject(m_pidlFolder, NULL, IID_IShellFolder, (LPVOID *) &m_psfFolder);
if(SUCCEEDED(hr)){
hr = m_psfFolder->EnumObjects(NULL,SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &ppenum);
if(SUCCEEDED(hr)){
while( hr = ppenum->Next(1,&pidlItems, &celtFetched) == S_OK && (celtFetched) == 1){
m_arpFolderItems.Add(pidlItems);
}
}
}
GetListCtrl().SetItemCount(m_arpFolderItems.GetSize());
}
}



打开文件夹
本应用程序显示文件夹的内容而不是显示文档的内容,所以我重载了打开文件时的处理,显示目录选择对话框而不是文件打开对话框。

void CPicViewApp::OnFileOpen()
{
TCHAR tszDisplayName[_MAX_PATH];
TCHAR tszPathSelected[_MAX_PATH];
LPITEMIDLIST pidlSelected=PidlBrowse(m_pMainWnd->GetSafeHwnd(),0,tszDisplayName);
if(pidlSelected){
if(SHGetPathFromIDList(pidlSelected,tszPathSelected)){
CDocument* pDocument=OpenDocumentFile(tszPathSelected);
pDocument->SetTitle(tszDisplayName);
ILFree(pidlSelected);
}
}
}

注意从外壳调用获得的PIDL一般都需要调用ILFree或者IMalloc::Free释放。一个例外是调用函数SHBindToParent获得的相对pidl,因为它是输入的参数完整pidl的一部分,所以不必另外释放。

在新建或者打开“文件”时候,文档需要通知视图当前文件夹的更改,这是通过调用CDocument::UpdateAllViews和重载CView::OnUpdate实现的。视图对这个通知的处理是清除上一个目录的缓存数据,缓存新目录的数据,以及更新文档标题。


打开文件或者目录
为了使用方便,双击列表项时可以在同一窗口打开子目录,或者调用系统的默认处理程序打开文件。如果文件是快捷方式,那么打开快捷方式的目标。

void CPicViewView::OnDblclk(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLISTVIEW lpnm=(LPNMLISTVIEW)pNMHDR;
if(lpnm->iItem==-1)return;
*pResult = 0;
HRESULT hr=S_OK;
LPCITEMIDLIST pidlItem=m_arpFolderItems[lpnm->iItem];
LPITEMIDLIST pidlItemFull=ILCombine(m_pidlFolder,pidlItem);
LPITEMIDLIST pidlItemTarget=NULL;
hr=theApp.SHGetTargetFolderIDList(pidlItemFull,&pidlItemTarget);
if(pidlItemTarget){
if(theApp.ILIsFolder(pidlItemTarget)){
CFolderChange FolderChange;
FolderChange.m_pidlFolder=pidlItemTarget;
OnFolderChange(&FolderChange);
}
else{
SHELLEXECUTEINFO ShExecInfo;
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_IDLIST;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = NULL;
ShExecInfo.lpFile = NULL;
ShExecInfo.lpIDList= pidlItemTarget;
ShExecInfo.lpParameters = NULL;
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_MAXIMIZE;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);
}
m_pMalloc->Free(pidlItemTarget);
m_pMalloc->Free(pidlItemFull);
}
}


性能的优化
为了更好的用户体验,可以使用自定义的图标大小(这需要完全自行绘制列表项的图标区域),用单独的线程来载入图像,或者使用调整到图标大小的缩略图缓冲(这样每次绘制时不必拉伸图像)。但是这超出了本文的范围。有兴趣的读者可以自己试一下。

参考
需要更多信息的话,可以参考

Shell FAQ http://msdn.microsoft.com/library/en-us/shellcc/platform/shell/programmersguide/shell_faq.asp
List-View Controls Overview http://msdn.microsoft.com/library/en-us/shellcc/platform/commctls/listview/listview_overview.asp
Using List-View Controls http://msdn.microsoft.com/library/en-us/shellcc/platform/commctls/listview/listview_using.asp
Customizing a Control's Appearance Using Custom Draw http://msdn.microsoft.com/library/en-us/shellcc/platform/commctls/custdraw/custdraw.asp

V.
给资源管理器加上文件夹大小显示

作者:babbye
出处:http://www.vcer.net/showTip.jsp?tipid=2372

(按:很直白的一个shell例子)

ColHandler.h文件

[code]
// ColHandler.h : Declaration of the CColHandler

#ifndef __COLHANDLER_H_
#define __COLHANDLER_H_

#include
#include "resource.h" // main symbols

//COM Interface provided by the Shell. The interface methods are inplemented
//to create and display the column on the explorer.
struct __declspec(uuid("E8025004-1C42-11d2-BE2C-00A0C9A83DA1")) IColumnProvider;


/////////////////////////////////////////////////////////////////////////////
// CColHandler
class ATL_NO_VTABLE CColHandler :
public CComObjectRootEx,
public CComCoClass,
public IColumnProvider,
public IDispatchImpl
{
public:
CColHandler()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_COLHANDLER)
DECLARE_NOT_AGGREGATABLE(CColHandler)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CColHandler)
COM_INTERFACE_ENTRY(IColHandler)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IColumnProvider)
END_COM_MAP()

// IColumnProvider
STDMETHOD(Initialize)(LPCSHCOLUMNINIT psci);
STDMETHOD(GetColumnInfo)(DWORD dwIndex, SHCOLUMNINFO *psci);
STDMETHOD(GetItemData)(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData);


private:
DWORD64 GetFolderSize(LPCTSTR szPath, DWORD *dwFiles = NULL, DWORD *dwFolders=NULL);
void ConvertToThousands(DWORD64 dwSize, LPTSTR szConverted);
};

#endif //__COLHANDLER_H_
[/code]

ColHandler.cpp文件
[code]
// ColHandler.cpp : Implementation of CColHandler
#include "stdafx.h"
#include "DirSize.h"
#include "ColHandler.h"

STDMETHODIMP CColHandler::Initialize(LPCSHCOLUMNINIT psci)
{
return S_OK;
}

STDMETHODIMP CColHandler::GetColumnInfo(DWORD dwIndex, SHCOLUMNINFO *psci)
{
TCHAR szText[] = "Folder Size(Bccv@SMTH)"; (按:嘿嘿,水木中人)

psci->scid.fmtid.Data1 = 0xB725F130L;
psci->scid.fmtid.Data2 = 0x47EF;
psci->scid.fmtid.Data3 = 0x101A;
strcpy((char *)psci->scid.fmtid.Data4,"A5F102608C9EEBAC");

psci->scid.pid = 12;
psci->vt = VT_BSTR; //VT_UI8;
psci->fmt = LVCFMT_RIGHT;
psci->cChars = 23;
psci->csFlags = SHCOLSTATE_TYPE_INT|SHCOLSTATE_SLOW|SHCOLSTATE_ONBYDEFAULT;


MultiByteToWideChar( CP_ACP, 0, szText,
strlen(szText)+1, psci->wszTitle,
sizeof(psci->wszTitle)/sizeof(psci->wszTitle[0]) );

MultiByteToWideChar( CP_ACP, 0, szText,
strlen(szText)+1, psci->wszDescription,
sizeof(psci->wszDescription)/sizeof(psci->wszDescription[0]) );

if(dwIndex == 0)
{
return S_OK;
}
else
{
return S_FALSE;
}
}

STDMETHODIMP CColHandler::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData)
{
USES_CONVERSION;
V_VT(pvarData) = VT_BSTR; //VT_UI8;
DWORD64 dwSize = 0;
TCHAR szTmp[7];
DWORD dwFiles = 0, dwFolders = 0;
DWORD64 size = 0;
float nsize=0;
int decimal, sign,ccount,z;
char Tbuffer[8];

if(pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
dwSize = GetFolderSize(OLE2T(pscd->wszFile),&dwFiles,&dwFolders);
// size = dwSize/(1024*1024);
// if(size == 0)
// {
size = dwSize/1024;
//size/=1024;
// if(size == 0)
// {
if (size<1024) {

_i64toa(size,szTmp,10);
//ConvertToThousands(dwSize,szTmp); // if not Commented, coma seperator will be displayed, but sorting will go for a toss
strcat(szTmp," KB");
}
if ((size>=1024)&&(size<(1024*1024))) {
size /= 1024;
_i64toa(size,szTmp,10);
strcat(szTmp," MB");
}
if ((size>=(1024*1024))&&(size<(1024*1024*1024))) {
size/=1024; //2034M
_i64toa(size,szTmp,10);
nsize=atof(szTmp);
nsize/=1024;
strcpy(szTmp, _fcvt( nsize, 2, &decimal, &sign ));
z=ccount=0;
while ( z<8) {
if (z!=decimal) {
Tbuffer[z]=szTmp[ccount];
z++;ccount++;
}
else
{
Tbuffer[z]='.';
z++;
}
}
strcpy(szTmp,Tbuffer);

strcat(szTmp," GB");
}


// }
// else
// {
// _i64toa(size,szTmp,10);
// strcat(szTmp," KB");
// }
// }
// else
// {
// _i64toa(size,szTmp,10);
// strcat(szTmp," MB");
// }

V_BSTR(pvarData) = SysAllocString(T2OLE(szTmp));
}
else
{
V_BSTR(pvarData) = SysAllocString(T2OLE(""));
}

return S_OK;
}



DWORD64 CColHandler::GetFolderSize(LPCTSTR szPath, DWORD *dwFiles, DWORD *dwFolders)
{
TCHAR szFileFilter[512];
TCHAR szFilePath[512];
HANDLE hFind = NULL;
WIN32_FIND_DATA fileinfo;
DWORD64 dwSize = 0;

strcpy(szFilePath,szPath);
strcat(szFilePath,"\\");
strcpy(szFileFilter,szFilePath);
strcat(szFileFilter,"*.*");


hFind = FindFirstFile(szFileFilter,&fileinfo);
do
{
if(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (!strcmp(fileinfo.cFileName,".") || !strcmp(fileinfo.cFileName,".."))
{
}
else
{
TCHAR sztmp[512];
strcpy(sztmp,szFilePath);
strcat(sztmp,fileinfo.cFileName);
dwSize = dwSize + GetFolderSize(sztmp);
if(dwFolders != NULL)
{
++(*dwFolders);
}
}
}
else
{
if(dwFiles != NULL)
{
++(*dwFiles);
}
}


dwSize += fileinfo.nFileSizeLow;

}while(FindNextFile(hFind,&fileinfo));

FindClose(hFind);
return dwSize;

}

void CColHandler::ConvertToThousands(DWORD64 dwSize, LPTSTR szConverted)
{
TCHAR szTmp[50] = {0};
TCHAR szTmp1[50]= {0};
_i64toa(dwSize,szTmp,10);
strrev(szTmp);

unsigned int len = strlen(szTmp);
unsigned int i=0;
for(i=0;i {
strncat(szTmp1,szTmp+i,3);
if( strlen(szTmp+i) > 3 )
strcat(szTmp1,",");
}
strrev(szTmp1);
strcpy(szConverted,szTmp1);
}
[/code]

VI.
Getting File Icons Using The Shell in C#

1.
from http://vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/Getting_File_Icons_Using_The_Shell/article.asp

The .NET Framework doesn't provide much in the way for classes to interact with the Shell. This article provides a simple class which wraps the Shell's SHGetFileInfo to extract large and small icons plus the display name and file type for any file.

About the File Icon Class
The FileIcon class provided in the download has a handful or properties and methods which make it easy to get icons for any file. To use it, you specify the FileName and the Flags which control what type of icon to extract, and whether to extract the file type and display name for the file at the same time. These can either be specified whilst constructing the class or set at any time and applied using the GetIcon method.

Example:

using System.Drawing;
using vbAccelerator.Components.Shell;

...

private Icon getSmallFileIcon(string fileName)
{
FileIcon f = new FileIcon(
fileName,
FileIcon.SHGetFileInfoConstants.SHGFI_SMALLICON |
FileIcon.SHGetFileInfoConstants.SHGFI_ICON);
return f.ShellIcon;
}

Alpha Icons
Under Windows XP, 32 bit icons with an alpha channel are supported. However, there are a couple of issues with using these under .NET Framework 1.0:

Linking to ComCtl32.DLL Version 6.0
ToBitmap and DrawImage and don't Preserve Alpha
I'll cover these in turn.

Linking to ComCtl32.DLL Version 6.0
I believe .NET Framework 1.1 includes support for ComCtl32.DLL through the XP Visual Styles method, but version 1.0 of the Framework does not. Prior to this version of ComCtl32.DLL, icons do not draw with alpha channels, resulting in an ugly black border where the alpha area of the shadow is:


Icon with Alpha Channel Correctly Drawn


Icon with Alpha Channel Not Drawn

To link your application to ComCtl32.DLL you need to provide a Manifest with your application. As described in the VB article on Manifests, there are two ways of doing this in Windows but unfortunately the VS.NET IDE makes one of them difficult. The first (and simplest) way is to create a file containing the manifest, give it the same name as your executable but with an additional extension ".manifest" and place it in the same directory as the executable. Here is a sample manifest file:

?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
assemblyIdentity
version="1.0.0.0"
processorArchitecture="X86"
name="CompanyName.ProductName.YourAppName"
type="win32" />
description>Your application description here
dependency>
dependentAssembly>
assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*" />
/dependentAssembly>
/dependency>
/assembly>

The second way is to embed the manifest as a Win32 resource in your application. This is better but very hard to do in VS.NET because it does not support the /win32res compiler option: here's an excerpt from the documentation:

Remarks
The /win32res option inserts a Win32 resource in the output file. A Win32 resource file can be created with the Resource Compiler. The Resource Compiler is invoked when you compile a Visual C++ program; a .res file is created from the .rc file.

To set this compiler option in the Visual Studio development environment
This compiler option is unavailable in Visual Studio.

To set this compiler option programmatically
This compiler option cannot be changed programmatically.

Example
Compile in.cs and attach a Win32 resource file rf.res to produce in.exe:

csc /win32res:rf.res in.cs

ToBitmap and DrawImage and don't Preserve Alpha
In order to draw the icon with the alpha channel, you need to keep it as an icon. If you use the System.Drawing.Bitmap.ToBitmap on the icon handle, the alpha channel won't be preserved. In this sample the icons are added to ImageLists and drawn from there, however equally you could use a System.Drawing.Icon object to maintain the icon.

2.
from http://vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/SysImageList/article.asp

Although you can easily extract copies of icons for any file from the Shell using the SHGetFileInfo call, if you need to be able to display the icon for a large number of files you can end up with too many icons and run out of resources. The System Image List uses an intelligent caching technique to provide icons without this overhead.

Dr. Jeckyll and Dr. Jeckyll
The SystemImageList class provided with the download has two distinct personalities depending on which OS you're running on.

Under Windows XP, the Shell gains a new function for obtaining the System Image List, SHGetImageList. Well, it would if someone had built the DLL properly - for some reason they forgot to export this function by name, and therefore it is only available by the mysterious ordinal #727 (see KB article Q316931 for more details). In any case this function returns a COM IImageList interface to the ImageList. Under previous versions, the System Image List is obtained by a call to SHGetFileInfo which returns a handle to a ComCtl32.DLL ImageList which can be used with the standard exported ImageList_ API calls.

The advantage of the new XP function is that it provides access to the Extra Large Image List used for in the Tile and Thumbnail views of the Shell. So the code decides based on the System.Environment.OSVersion.Version which way to get at the ImageList. Once that's decided the only real difference between the COM ImageList interface and the standard DLL call version is that with the COM versions calls do not need you to tell it which ImageList handle you're working on, as the handle is the IUnknown pointer to the object.

Curses
It would be nice if you could use the System Image List directly with a ListView or similar control, but unfortunately the System.Windows.Forms.ImageList class is Sealed. There's probably a good reason for this, but it doesn't seem to be the best design decision in the Framework. This means that if you do want to use this class with a control that supports a System.Windows.Forms ImageList then you need to find another way to associated the ImageList.

For controls like the ListView and TreeView, this is just a matter of using the API to set the ImageLists. The code provided comes with a class, SysImageListHelper which provides two static methods allowing you to associate a SystemImageList rather than a standard ImageList to these controls.

For other controls, you can use the SystemImageList's drawing methods to draw onto the control itself. The UsingSysImageList download demonstrates some very basic code for attaching the System Image List to a ListBox and ComboBox.

Implementing the SystemImageList Class
The first point in implementing the class is to get a handle to the System Image List. As noted before there are two distinct interfaces to I'll cover these in turn.

Getting the System Image List Handle using SHGetFileInfo
The first step to doing this is to declare the SHGetFileInfo DLL call and the flags you need to use it:

using System.Runtime.InteropServices;

[Flags]
private enum SHGetFileInfoConstants : int
{
SHGFI_ICON = 0x100, // get icon
SHGFI_DISPLAYNAME = 0x200, // get display name
SHGFI_TYPENAME = 0x400, // get type name
SHGFI_ATTRIBUTES = 0x800, // get attributes
SHGFI_ICONLOCATION = 0x1000, // get icon location
SHGFI_EXETYPE = 0x2000, // return exe type
SHGFI_SYSICONINDEX = 0x4000, // get system icon index
SHGFI_LINKOVERLAY = 0x8000, // put a link overlay on icon
SHGFI_SELECTED = 0x10000, // show icon in selected state
SHGFI_ATTR_SPECIFIED = 0x20000, // get only specified attributes
SHGFI_LARGEICON = 0x0, // get large icon
SHGFI_SMALLICON = 0x1, // get small icon
SHGFI_OPENICON = 0x2, // get open icon
SHGFI_SHELLICONSIZE = 0x4, // get shell size icon
SHGFI_PIDL = 0x8, // pszPath is a pidl
SHGFI_USEFILEATTRIBUTES = 0x10, // use passed dwFileAttribute
SHGFI_ADDOVERLAYS = 0x000000020, // apply the appropriate overlays
SHGFI_OVERLAYINDEX = 0x000000040 // Get the index of the overlay
}

[StructLayout(LayoutKind.Sequential)]
private struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public int dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=80)]
public string szTypeName;
}

[DllImport("shell32")]
private static extern IntPtr SHGetFileInfo (
string pszPath,
int dwFileAttributes,
ref SHFILEINFO psfi,
uint cbFileInfo,
uint uFlags);

With this in place then the ImageList handle is the return value of SHGetFileInfo for any file when the SHGFI_SYSICONINDEX flag is specifed:

SHGetFileInfoConstants dwFlags =
SHGetFileInfoConstants.SHGFI_USEFILEATTRIBUTES |
SHGetFileInfoConstants.SHGFI_SYSICONINDEX ;
if (size == SysImageListSize.smallIcons)
{
dwFlags |= SHGetFileInfoConstants.SHGFI_SMALLICON;
}
// Get image list
SHFILEINFO shfi = new SHFILEINFO();
uint shfiSize = (uint)Marshal.SizeOf(shfi.GetType());

// Call SHGetFileInfo to get the image list handle
// using an arbitrary file:
hIml = SHGetFileInfo(
".txt",
FILE_ATTRIBUTE_NORMAL,
ref shfi,
shfiSize,
(uint)dwFlags);

Getting the System Image List using SHGetImageList
To get the System Image List with this function you need both to create a COM IImageList Interface to work with and to declare the SHGetImageList function. In order to get the handle correctly, the same function is declared twice; once to return the COM object and once to get the handle as an IntPtr - although in theory you can retrieve the handle from the IUnknown interface of IImageList directly, I found that the returned value was off by 4 bytes.

///
/// SHGetImageList is not exported correctly in XP. See KB316931
/// http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q316931
/// Apparently (and hopefully) ordinal 727 isn't going to change.
///

[DllImport("shell32.dll", EntryPoint = "#727")]
private extern static int SHGetImageList(
int iImageList,
ref Guid riid,
ref IImageList ppv
);

[DllImport("shell32.dll", EntryPoint = "#727")]
private extern static int SHGetImageListHandle(
int iImageList,
ref Guid riid,
ref IntPtr handle
);

#region Structures
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
int x;
int y;
}

[StructLayout(LayoutKind.Sequential)]
private struct IMAGELISTDRAWPARAMS
{
public int cbSize;
public IntPtr himl;
public int i;
public IntPtr hdcDst;
public int x;
public int y;
public int cx;
public int cy;
public int xBitmap; // x offest from the upperleft of bitmap
public int yBitmap; // y offset from the upperleft of bitmap
public int rgbBk;
public int rgbFg;
public int fStyle;
public int dwRop;
public int fState;
public int Frame;
public int crEffect;
}

[StructLayout(LayoutKind.Sequential)]
private struct IMAGEINFO
{
public IntPtr hbmImage;
public IntPtr hbmMask;
public int Unused1;
public int Unused2;
public RECT rcImage;
}
#endregion

#region Private ImageList COM Interop (XP)
[ComImportAttribute()]
[GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
//helpstring("Image List"),
interface IImageList
{
[PreserveSig]
int Add(
IntPtr hbmImage,
IntPtr hbmMask,
ref int pi);

[PreserveSig]
int ReplaceIcon(
int i,
IntPtr hicon,
ref int pi);

[PreserveSig]
int SetOverlayImage(
int iImage,
int iOverlay);

[PreserveSig]
int Replace(
int i,
IntPtr hbmImage,
IntPtr hbmMask);

[PreserveSig]
int AddMasked(
IntPtr hbmImage,
int crMask,
ref int pi);

[PreserveSig]
int Draw(
ref IMAGELISTDRAWPARAMS pimldp);

[PreserveSig]
int Remove(
int i);

[PreserveSig]
int GetIcon(
int i,
int flags,
ref IntPtr picon);

[PreserveSig]
int GetImageInfo(
int i,
ref IMAGEINFO pImageInfo);

[PreserveSig]
int Copy(
int iDst,
IImageList punkSrc,
int iSrc,
int uFlags);

[PreserveSig]
int Merge(
int i1,
IImageList punk2,
int i2,
int dx,
int dy,
ref Guid riid,
ref IntPtr ppv);

[PreserveSig]
int Clone(
ref Guid riid,
ref IntPtr ppv);

[PreserveSig]
int GetImageRect(
int i,
ref RECT prc);

[PreserveSig]
int GetIconSize(
ref int cx,
ref int cy);

[PreserveSig]
int SetIconSize(
int cx,
int cy);

[PreserveSig]
int GetImageCount(
ref int pi);

[PreserveSig]
int SetImageCount(
int uNewCount);

[PreserveSig]
int SetBkColor(
int clrBk,
ref int pclr);

[PreserveSig]
int GetBkColor(
ref int pclr);

[PreserveSig]
int BeginDrag(
int iTrack,
int dxHotspot,
int dyHotspot);

[PreserveSig]
int EndDrag();

[PreserveSig]
int DragEnter(
IntPtr hwndLock,
int x,
int y);

[PreserveSig]
int DragLeave(
IntPtr hwndLock);

[PreserveSig]
int DragMove(
int x,
int y);

[PreserveSig]
int SetDragCursorImage(
ref IImageList punk,
int iDrag,
int dxHotspot,
int dyHotspot);

[PreserveSig]
int DragShowNolock(
int fShow);

[PreserveSig]
int GetDragImage(
ref POINT ppt,
ref POINT pptHotspot,
ref Guid riid,
ref IntPtr ppv);

[PreserveSig]
int GetItemFlags(
int i,
ref int dwFlags);

[PreserveSig]
int GetOverlayImage(
int iOverlay,
ref int piIndex);
};
#endregion

Once this is done you get the handle like this (the isXpOrAbove function just uses System.Enivronent.OSVersion.Version to check whether you're running on XP or not:

if (isXpOrAbove())
{
// Get the System IImageList object from the Shell:
Guid iidImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
int ret = SHGetImageList(
(int)size,
ref iidImageList,
ref iImageList
);
// the image list handle is the IUnknown pointer, but
// using Marshal.GetIUnknownForObject doesn't return
// the right value. It really doesn't hurt to make
// a second call to get the handle:
SHGetImageListHandle((int)size, ref iidImageList, ref hIml);
}

Now You Have The Handle
That's most of the hard work pretty much done. Once you've got the handle, useful functions you can perform with the ImageList are:

Finding the index of the icon for the specified file
Drawing an icon
Extracting a copy of an icon
If you're using the IImageList interface, you have all of the ImageList declares you need; otherwise a few additional declares are needed:

[DllImport("comctl32")]
private extern static int ImageList_DrawIndirect(
ref IMAGELISTDRAWPARAMS pimldp);

[DllImport("comctl32")]
private extern static int ImageList_GetIconSize(
IntPtr himl,
ref int cx,
ref int cy);

[DllImport("comctl32")]
private extern static IntPtr ImageList_GetIcon(
IntPtr himl,
int i,
int flags);

To find the index of an icon in the System Image List, use the SHGetFileInfo call with the SHGFI_SYSICONINDEX flag:

public int IconIndex(
string fileName,
bool forceLoadFromDisk,
ShellIconStateConstants iconState
)
{
SHGetFileInfoConstants dwFlags =
SHGetFileInfoConstants.SHGFI_SYSICONINDEX;
int dwAttr = 0;
if (size == SysImageListSize.smallIcons)
{
dwFlags |= SHGetFileInfoConstants.SHGFI_SMALLICON;
}

// We can choose whether to access the disk or not. If you don't
// hit the disk, you may get the wrong icon if the icon is
// not cached. Also only works for files.
if (!forceLoadFromDisk)
{
dwFlags |= SHGetFileInfoConstants.SHGFI_USEFILEATTRIBUTES;
dwAttr = FILE_ATTRIBUTE_NORMAL;
}
else
{
dwAttr = 0;
}

// sFileSpec can be any file. You can specify a
// file that does not exist and still get the
// icon, for example sFileSpec = "C:\PANTS.DOC"
SHFILEINFO shfi = new SHFILEINFO();
uint shfiSize = (uint)Marshal.SizeOf(shfi.GetType());
IntPtr retVal = SHGetFileInfo(
fileName, dwAttr, ref shfi, shfiSize,
((uint)(dwFlags) | (uint)iconState));

if (retVal.Equals(IntPtr.Zero))
{
System.Diagnostics.Debug.Assert(
(!retVal.Equals(IntPtr.Zero)),
"Failed to get icon index");
return 0;
}
else
{
return shfi.iIcon;
}
}

Once you have the index, you can draw or extract the image:

Extracting an Icon
public Icon Icon(int index)
{
Icon icon = null;

IntPtr hIcon = IntPtr.Zero;
if (iImageList == null)
{
hIcon = ImageList_GetIcon(
hIml,
index,
(int)ImageListDrawItemConstants.ILD_TRANSPARENT);

}
else
{
iImageList.GetIcon(
index,
(int)ImageListDrawItemConstants.ILD_TRANSPARENT,
ref hIcon);
}

if (hIcon != IntPtr.Zero)
{
icon = System.Drawing.Icon.FromHandle(hIcon);
}
return icon;
}

Drawing an Icon

public void DrawImage(
IntPtr hdc,
int index,
int x,
int y,
ImageListDrawItemConstants flags
)
{
IMAGELISTDRAWPARAMS pimldp = new IMAGELISTDRAWPARAMS();
pimldp.hdcDst = hdc;
pimldp.cbSize = Marshal.SizeOf(pimldp.GetType());
pimldp.i = index;
pimldp.x = x;
pimldp.y = y;
pimldp.rgbFg = -1;
pimldp.fStyle = (int)flags;

if (iImageList == null)
{
// Using DLL Calls:
pimldp.hIml = this.hIml;
int ret = ImageList_DrawEx(
ref pimldp);
}
else
{
// Using IImageList interface:
this.iImageList.Draw(ref pimldp);
}

}

To get a device context to draw on, use the Graphics GetHdc and ReleaseHdc methods like this:

// assuming gfx is a Graphics object:
IntPtr hDc = gfx.GetHdc();

// .. do drawing here

gfx.ReleaseHdc(hDc);

When drawing there are a number of other parameters you can set to modify the alpha, saturation and colours used to render the image. Refer to the code for more details on using these.

VII.
Thumbnail Extraction Using the Shell

from http://vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/Thumbnail_Extraction/article.asp

The Shell provides an IExtractImage interface which allows you to obtain thumbnail images for any file which has a Shell extension that supports the interface. By default there are Shell extensions for images, most Office documents and folders; and other programs can install their own Shell extensions for other file types. This sample provides .NET code you can use to easily get hold of thumbnails using this technique.

Using the Code
The code itself should be very straightforward to use. Just add the code file ThumbnailCreator.cs to your project and you'll find you can create a ThumbnailCreator class from the vbAccelerator.Components.Shell namespace. This object has one property and one method:

DesiredSize
Gets/sets the size of thumbnail you'd like. Explorer uses 100 x 100 pixels.
GetThumbNail
Returns a System.Drawing.Bitmap for the specified file with the thumbnail, or throws an exception if there's a problem.
Note that you should always check for exceptions when processing for thumbnails. Some objects simply don't support Thumbnails; in Explorer the default processing is to draw the Extra Large Icon from the System Image List in the centre of the Thumbnail area) and other objects only support Thumbnails under specific circumstances. For example, although Word and Excel documents support thumbnailing, they only do so when there is an embedded metafile thumbnail - a specific option you have to enable on the Properties dialog of the document. The error message you get thrown back tends to be not particularly valuable either in determining what happened.

By default, you should be able to create thumbnails for all image files, HTML and MHT documents and file folders. The sample code demonstrates adding these to an ImageList which is linked to a ListView; it is also single-threaded and you would probably want to improve matters by requesting the thumbnail on a separate thread using BeginInvoke in a real application.

The Details
The Shell's thumbnail feature is exposed through the IExtractImage interface of a Shell object. In order to get an IExtractImage interface, you first need to have an IShellFolder interface. This is covered in some depth in the Folder Browser article, so I won't cover it in more detail here. I'll cover the steps you need to go through to get at the IExtractImage interface now in turn.

1. Getting a Reference to The IShellFolder Object
First get an IShellFolder instance using the returned SHGetDesktopFolder API call as described in the Folder Browser sample, and then call the ParseDisplayName to get a pidl for the containing folder (note error handling is not shown in these samples for clarity):

IShellFolder folder = null;
SHGetDesktopFolder(out folder);

IntPtr pidlMain = IntPtr.Zero;
int cParsed = 0;
int pdwAttrib = 0;
string directory = Path.GetDirectoryName(file);

folder.ParseDisplayName(
IntPtr.Zero,
IntPtr.Zero,
directory,
out cParsed,
out pidlMain,
out pdwAttrib);

Now we can get an IShellFolder object for this pidl:

Guid iidShellFolder =
new Guid("000214E6-0000-0000-C000-000000000046");
IShellFolder item = null;
folder.BindToObject(
pidlMain,
IntPtr.Zero,
ref iidShellFolder,
ref item);

2. Finding Sub-Item pidls
Having done that, we now want to find the pidl of the object we desire a thumbnail for from the owning folder. To do this the objects within the folder need to be enumerated until the correct item is found:

IEnumIDList idEnum = null;
item.EnumObjects(
IntPtr.Zero,
(ESHCONTF.SHCONTF_FOLDERS | ESHCONTF.SHCONTF_NONFOLDERS),
ref idEnum);

IntPtr pidl = IntPtr.Zero;
int fetched = 0;
bool complete = false;
while (!complete)
{
hRes = idEnum.Next(1, ref pidl, out fetched);
if (hRes != 0)
{
// end of enumeration
pidl = IntPtr.Zero;
complete = true;
}
else
{
// check if pidl is the correct item and
// get the thumbnail. This function returns
// true as soon as it finds a match,
// regardless of whether the thumbnail
// can be extracted:
if (getThumbnail(file, item, pidl)
{
complete = true;
}
}
if (pidl != IntPtr.Zero)
{
Allocator.Free(pidl);
}
}

Finally, clear up any objects we created - the Allocator object referred to here is the IMalloc object described in the Folder Browser sample:

Marshal.ReleaseComObject(idEnum);
Marshal.ReleaseComObject(item);
Allocator.Free(pidlMain);
Marshal.ReleaseComObject(folder);

3. Processing An Item's Pidl
The final stage is to check if the pidl in question is the file or folder we're thumbnailing, and if it is then to start the thumbnail extraction process:

private bool getThumbnail(
string file,
IShellFolder item,
IntPtr pidl)
{
IntPtr hBmp = IntPtr.Zero;
IExtractImage extractImage = null;
bool ret = false;

string pidlPath = PathFromPidl(pidl);
// pidl is a relative pidl, so only the filename
// portion is relevant:
if (Path.GetFileName(pidlPath).Equals(
Path.GetFileName(file))
{
// This is the right item:
ret = true;

// Try and get the IExtractImage interface:
IUnknown iunk = null;
int prgf = 0;
Guid iidExtractImage = new
Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1");
item.GetUIObjectOf(
IntPtr.Zero,
1,
ref pidl,
ref iidExtractImage,
out prgf,
ref iunk);
extractImage = (IExtractImage)iunk;

// we now have an IExtractImage object...
}

return ret;
}

To support the code above, you need COM Interop libraries for the IExtractImage and IUnknown interfaces:

[ComImport, Guid("00000000-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IUnknown
{
[PreserveSig]
IntPtr QueryInterface(ref Guid riid, out IntPtr pVoid);
[PreserveSig]
IntPtr AddRef();
[PreserveSig]
IntPtr Release();
}

[ComImportAttribute()]
[GuidAttribute("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
//helpstring("IExtractImage"),
private interface IExtractImage
{
void GetLocation(
[Out(), MarshalAs(UnmanagedType.LPWStr)]
StringBuilder pszPathBuffer,
int cch,
ref int pdwPriority,
ref SIZE prgSize,
int dwRecClrDepth,
ref int pdwFlags);

void Extract(
out IntPtr phBmpThumbnail);
}

Finally, its time to actually exercise the IExtractImage interface itself. To get the thumbnail, first call the GetLocation method to set extraction options and desired image size, and then call Extract to receive a GDI HBITMAP handle to the bitmap, which can then be moved into the GDI+ world using the Image.FromHandle method:

SIZE sz = new SIZE();
sz.cx = desiredSize.Width;
sz.cy = desiredSize.Height;
StringBuilder location = new StringBuilder(260, 260);
int priority = 0;
int requestedColourDepth = 32;
EIEIFLAG flags = EIEIFLAG.IEIFLAG_ASPECT | EIEIFLAG.IEIFLAG_SCREEN;
int uFlags = (int)flags;

extractImage.GetLocation(
location,
location.Capacity,
ref priority,
ref sz,
requestedColourDepth,
ref uFlags);

extractImage.Extract(out hBmp);
if (hBmp != IntPtr.Zero)
{
// create the image object:
thumbNail = System.Drawing.Bitmap.FromHbitmap(hBmp);
}

VIII.
编程实现更改文件夹图标

来源:不详

方法一:
修改注册表,在下面这个分支新建两个键:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\explorer\ShellIcons

3 值为你的图标路径,如"C:\MyCloseIcon.ico"
4 值为你的图标路径,如"C:\MyOpenIcon.ico"

还要删除"c:\windows\ShellIconCache"这个隐含文件,重启计算机。

方法二:
通常的办法是在你要更换的目录下用记事本新建一个Desktop.ini,写入下边几行:

  [.ShellClassInfo]
  InfoTip=
  IconFile=
  IconIndex=

  InfoTip后边可以写入文件夹提示信息;IconFile就是你的图标文件,一般采用.ico格文件,也可以使用.exe、.dll等这类有图标的文件充当,写下绝对或相对路径都可以;IconIndex是选择哪一个图标,如果你指定的exe或者dll中包含不止一个图标,那就需要选择一下,如果你想使用第一个标,则IconIndex=0,没有该行定义的情况下,系统默认的是使用第四个图标,即IconIndex=3,这就是好多时候图标不能显示的原因,因为这类文件通常里面只有一个图标。

最后,最关键的一步,就是给文件夹加上系统属性!
试试看!当然要给文件夹加系统属性,只能用DOS命令了!
attrib +s MyFolder
如: c:\server
attrib +s c:\server
OK

方法三:更改系统固定图标
  所谓系统固定图标就是像“我的电脑”、“网上邻居”之类的图标。

  打开“开始”菜单,在“运行”项中输入“regedit”打开注册表编辑器。设定这些系统的特殊文件夹的条目在“HKEY_CLASSES_ROOT”的“CLSID”主键下,一些常见项目的主键为:

  我的电脑 {20D04FE0-3AEA-1069-A2D8-08002B30309D}
  回收站  {645FF040-5081-101B-9F08-00AA002F954E}
  网上邻居 {208D2C60-3AEA-1069-A2D7-08002B30309D}

  双击想要修改的项目的键名,在下边有一个“DefaultIcon”的主键,单击这个主键,在右边有一个“默认”的项目,双击它然后指定图标路径,如“%SystemRoot%\Explorer.exe,0”,0是指Explorer.exe的第一个图标;在回收站下会有“默认”“Empty”“Full”三个栏目,其中“Empty”是清空状态下的图标,“Full”是有未删除项目时状态。其他系统固定图标也可以通过相应主键进行修改。



<< Home

This page is powered by Blogger. Isn't yours?