Shell扩展编程完全指南(9)如何编写定制文件类型显示图标的Shell

Shell扩展编程完全指南 内容来自dedecms

linghuye

内容来自dedecms

第九节如何编写定制文件类型显示图标的Shell扩展

织梦内容管理系统

啊, 我们到第九节了! 本文是应另一位读者的要求而写, 将讨论怎样为同一种文件类型的每一个文件显示定制的图标(在本文中以文本文件为例). 示例代码适用于Windows 9x 和NT/2000. (我还没用过WinMe, 所以没在Me上测试过,但应该也可以) 织梦好,好织梦

浏览器中的文件图标

大家都知道每种文件类型在浏览器中都有一个特殊的图标来标识. 位图文件显示一个画筒图标, HTML文件显示IE页的图标,等等. 浏览器根据注册表中的数据决定该使用哪个图标, 并读取HKEY_CLASSES_ROOT下的对应于文件类型的键的数据. 这样的结果是每种类型文件显示同一个图标.

dedecms.com

但是, 这并不是指定图标的唯一方法. 使用图标扩展处理,浏览器可以让我们自定义对应每一个文件的图标. 实际上, Windows内部就有一个这样的扩展. 打开Windows 目录(或任何一个有有许多EXE 文件的目录) ,你会发现每一个EXE 都有不同的图标(除开没有图标资源的EXE文件). ICO 和CUR 文件也都有不同的图标. copyright dedecms

本文将编写一个图标扩展处理器,根据文本文件的大小为其显示4种图标. 图标如下:

织梦内容管理系统

 [file icon - 1K] - 8K 或更大

dedecms.com

 [file icon - 1K] - 4K 到8K

copyright dedecms

 [file icon - 1K] - 1 字节到4K

织梦好,好织梦

 [file icon - 1K] - 0字节 copyright dedecms

使用AppWizard 开始

运行AppWizard 并生成一个名为TxtFileIconsATL工程. 由于这次我们要使用MFC,所以要选中Support MFC设置, 点击”完成”. 然后,在ClassView树中右击TxtFileIcons classes项,在弹出的菜单中选择New ATL Object,添加一个COM 对象类到DLL中.

dedecms.com

在ATL Object Wizard 中, 第一页面已经选中了Simple Object, 因此, 单击Next. 在第二页面中, 在Short Name编辑框中输入TxtIconShlExt点击OK. (其余编辑框中的内容将自动填写.) 这样就创建了一个名为CTxtIconShlExt 的已实现基本的COM接口的新COM对象类. 我们将在这个类中添加实现代码. 本文来自织梦

扩展接口

图标扩展处理器实现两个接口IPersistFileIExtractIcon. 记得IPersistFile用于初始化只涉及一个选择文件时的处理, 而IShellExtInit接口用于一次有多个选择文件时的处理. IExtractIcon有两个方法,它们的作用是告诉浏览器所使用的图标.

本文来自织梦

记住:浏览器为显示的每一个文件都将创建一个COM 对象. 这就是说每一个文件都将有一个COM C++类对象对应. 因此在你的扩展中应该避免费时的操作以防止浏览界面反应迟滞. dedecms.com

初始化接口

要添加IPersistFile接口道COM 对象, 打开TxtIconShlExt.h并添加如下标红的代码.

织梦好,好织梦

#include <comdef.h> copyright dedecms 
#include <shlobj.h> 内容来自dedecms 
#include <atlconv.h> 织梦好,好织梦 
 本文来自织梦 
///////////////////////////////////////////////////////////////////////////// 
内容来自dedecms
// CTxtIconShlExt 本文来自织梦 
 织梦内容管理系统 
class ATL_NO_VTABLE CTxtIconShlExt :  
内容来自dedecms
public CComObjectRootEx<CComSingleThreadModel>, 
本文来自织梦
public CComCoClass<CTxtIconShlExt, &CLSID_TxtIconShlExt>, 内容来自dedecms 
public IDispatchImpl<ITxtIconShlExt, &IID_ITxtIconShlExt, &LIBID_TXTFILEICONSLib>, 
本文来自织梦
public IPersistFile 织梦好,好织梦 
{ copyright dedecms 
BEGIN_COM_MAP(CTxtIconShlExt) copyright dedecms 
COM_INTERFACE_ENTRY(ITxtIconShlExt) 织梦内容管理系统 
COM_INTERFACE_ENTRY(IDispatch) 

dedecms.com

COM_INTERFACE_ENTRY(IPersistFile) 

本文来自织梦

END_COM_MAP() 内容来自dedecms 
 
内容来自dedecms
public: 织梦内容管理系统 
// IPersistFile 织梦内容管理系统 
STDMETHOD(GetClassID)( CLSID* ){ return E_NOTIMPL; } 

织梦内容管理系统

STDMETHOD(IsDirty)(){ return E_NOTIMPL; } copyright dedecms 
STDMETHOD(Save)( LPCOLESTR, BOOL ){ return E_NOTIMPL; } 本文来自织梦 
STDMETHOD(SaveCompleted)( LPCOLESTR ) { return E_NOTIMPL; } 
dedecms.com
STDMETHOD(GetCurFile)( LPOLESTR* ){ return E_NOTIMPL; } 

dedecms.com

STDMETHOD(Load)( LPCOLESTR wszFile, DWORD /*dwMode*/ ) 织梦内容管理系统 
{  copyright dedecms 
USES_CONVERSION; 

dedecms.com

lstrcpyn ( m_szFilename, OLE2CT(wszFile), MAX_PATH ); 本文来自织梦 
return S_OK; 本文来自织梦 
} 织梦内容管理系统 
 本文来自织梦 
protected: 
织梦好,好织梦
TCHARm_szFilename [MAX_PATH];// Full path to the file in question. 

织梦好,好织梦

DWORDLONG m_ldwFileSize;// File size; used by extraction method 2. 本文来自织梦 
}; 
织梦内容管理系统

跟其它使用IpersistFile 接口的扩展一样, 所需要实现的接口方法只有Load(), 在这里浏览器将通知我们被选择操作的文件. Load()的实现只是拷贝文件名到m_szFilename变量以备后用. 本文来自织梦

IExtractIcon 接口

图标扩展处理器实现IExtractIcon接口, 当浏览器需要为文件显示一个图标时将调用该接口. 因为我们的扩展用于文本文件, 浏览器将在每次显示文本文件对象时调用IExtractIcon的方法. 要添加IExtractIcon接口, 打开TxtIconShlExt.h并添加如下标红的代码: copyright dedecms

///////////////////////////////////////////////////////////////////////////// 内容来自dedecms 
// CTxtIconShlExt 织梦好,好织梦 
 

dedecms.com

class ATL_NO_VTABLE CTxtIconShlExt :  织梦内容管理系统 
public CComObjectRootEx<CComSingleThreadModel>, 

织梦好,好织梦

public CComCoClass<CTxtIconShlExt, &CLSID_TxtIconShlExt>, 
本文来自织梦
public IDispatchImpl<ITxtIconShlExt, &IID_ITxtIconShlExt, &LIBID_TXTFILEICONSLib>, 
内容来自dedecms
public IPersistFile, copyright dedecms 
public IExtractIcon dedecms.com 
{ 织梦内容管理系统 
BEGIN_COM_MAP(CTxtIconShlExt) 
copyright dedecms
COM_INTERFACE_ENTRY(ITxtIconShlExt) 内容来自dedecms 
COM_INTERFACE_ENTRY(IDispatch) copyright dedecms 
COM_INTERFACE_ENTRY(IPersistFile) 
内容来自dedecms
COM_INTERFACE_ENTRY(IExtractIcon) 

内容来自dedecms

END_COM_MAP() 

内容来自dedecms

 
本文来自织梦
public: copyright dedecms 
// IExtractIcon 

dedecms.com

STDMETHOD(GetIconLocation)( UINT uFlags, LPTSTR szIconFile, UINT cchMax, 

copyright dedecms

int* piIndex, UINT* pwFlags ); 

内容来自dedecms

STDMETHOD(Extract)( LPCTSTR pszFile, UINT nIconIndex, HICON* phiconLarge, 

织梦好,好织梦

HICON* phiconSmall, UINT nIconSize ); 织梦好,好织梦 
}; 
本文来自织梦

有两种方法可将图标返回给浏览器. 第一种是GetIconLocation()可以返回文件名/索引对以指出包含图标的文件,和图标在该文件中索引位置(以0为基). 例如本例子中为C:\windows\system\shell32.dll/9, 这就是告诉浏览器使用Shell32.dll的第9个图标(以0为基). 这不是说使用ID 为9的图标, 而是使用第九个图标. Extract()只需返回S_FALSE给浏览器让它自己来解析图标. 内容来自dedecms

该方法的特别之处在于浏览器在GetIconLocation()返回之后不一定会调用Extract(). 浏览器会保持一个图标缓存以存储最近使用的图标. 如果GetIconLocation()返回最近已使用的文件名/索引对, 而且图标仍然在缓存中, 浏览器就可以直接使用缓存中的图标而不会去调用Extract().

织梦内容管理系统

第二种方法是从GetIconLocation() 返回不要查看缓冲的标志, 这样会使浏览器去调用Extract(). Extract()则负责加载图标资源并将其句柄返回给浏览器. 本文来自织梦

第一种解析方法

IExtractIconGetIconLocation() 方法最先被调用. 该函数检查所选择的文件名并返回文件名/索引对. 其原型为:

织梦内容管理系统

HRESULT IExtractIcon::GetIconLocation ( 

内容来自dedecms

UINTuFlags, 织梦好,好织梦 
LPTSTR szIconFile, 
织梦内容管理系统
UINTcchMax, 
本文来自织梦
int*piIndex, 
内容来自dedecms
UINT*pwFlags ); dedecms.com 

其参数为: 本文来自织梦

uFlags

内容来自dedecms

改变扩展行为的标志. GIL_ASYNC表示询问扩展的处理要不要花费长时间, 如果要, 扩展可以要求扩展以后台线程运行, 这样浏览器界面不会出现迟滞. 其余的标志, GIL_FORSHELLGIL_OPENICON, 只在名字空间扩展中有意义. 现在我们不必去管这些标志,因为我们的扩展不会耗时太长. 内容来自dedecms

 

copyright dedecms

szIconFile, cchMax

copyright dedecms

szIconFile是由shell 提供的一个缓冲要求我们填入包含所使用的图标的文件名. cchMax是该缓冲区的大小.

copyright dedecms

 

织梦好,好织梦

piIndex

本文来自织梦

int的指针,要求我们添入图标在文件中的索引.

dedecms.com

 

dedecms.com

pwFlags 本文来自织梦

UINT的指针,要求我们返回影响浏览器行为的标志. copyright dedecms

GetIconLocation()填写szIconFilepiIndex参数并返回S_OK. 如果我们根本不想使用自定义的图标也可以返回S_FALSE,此时浏览器会显示一个未知文件类型的图标( [def. icon - 2K] ). 可以在pwFlags 中返回的标志有:

本文来自织梦

GIL_DONTCACHE copyright dedecms

告诉浏览器不要检查图标缓冲而去使用最近的szIconFile/piIndex对. 其结果是IExtractIcon::Extract()将被调用. 本文来自织梦

GIL_NOTFILENAME dedecms.com

根据MSDN, 该标志告诉浏览器当GetIconLocation()返回时忽略szIconFile/piIndex的内容. 很显然, 这是扩展告诉浏览器去调用IExtractIcon::Extract() 的方法, 然而实际上该标志对浏览器的行为并无影响

copyright dedecms

GIL_SIMULATEDOC

dedecms.com

该标志告诉浏览器将返回的图标叠放在卷边文档图标上, 并使用生成的这个图标. 我将在下面解释这一点.

本文来自织梦

在方法1中, 我们的扩展的GetIconLocation()方法取得文件大小, 并根据文件大小, 返回0 到3 的一个索引. 这带来该方法的一个缺陷– 你需要注意你的资源ID 确保它们的顺序固定. 我们的扩展只有4 个图标, 所以这不会困难, 但如果你有多个图标, 或者你在工程中添加/删除图标时, 你必须注意你的资源ID.

织梦内容管理系统

下面是GetIconLocation()函数.我们首先打开文件,获取大小. 如果中途发生错误, 我们返回S_FALSE让浏览器使用默认的图标. 内容来自dedecms

STDMETHODIMP CTxtIconShlExt::GetIconLocation ( 
本文来自织梦
UINTuFlags, dedecms.com 
LPTSTR szIconFile, 

本文来自织梦

UINTcchMax, 

本文来自织梦

int*piIndex, 
织梦内容管理系统
UINT*pwFlags ) 织梦内容管理系统 
{ 织梦好,好织梦 
DWORDdwFileSizeLo, dwFileSizeHi; 

本文来自织梦

DWORDLONG ldwSize; 本文来自织梦 
HANDLEhFile; 内容来自dedecms 
 
织梦内容管理系统
hFile = CreateFile ( m_szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, 

织梦内容管理系统

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); 
本文来自织梦
 

copyright dedecms

if ( INVALID_HANDLE_VALUE == hFile ) dedecms.com 
return S_FALSE;//让浏览器使用默认的图标 

织梦好,好织梦

 织梦内容管理系统 
dwFileSizeLo = GetFileSize ( hFile, &dwFileSizeHi ); 织梦好,好织梦 
 
织梦好,好织梦
CloseHandle ( hFile ); 
dedecms.com
 

dedecms.com

if ( (DWORD) -1 == dwFileSizeLo&&GetLastError() != NO_ERROR ) 

内容来自dedecms

return S_FALSE;//让浏览器使用默认的图标 内容来自dedecms 
 copyright dedecms 
ldwSize = ((DWORDLONG) dwFileSizeHi)<<32 | dwFileSizeLo; copyright dedecms 

接着我们取得我们的DLL的路径名,因为它包含图标. 并将其拷贝到szIconFile缓冲里. 织梦内容管理系统

TCHAR szModulePath[MAX_PATH]; 织梦内容管理系统 
 
织梦好,好织梦
GetModuleFileName ( _Module.GetModuleInstance(), szModulePath, MAX_PATH ); 
本文来自织梦
lstrcpyn ( szIconFile, szModulePath, cchMax ); copyright dedecms 

接着, 我们检查文件大小并设置piIndex.

copyright dedecms

if ( 0 == ldwSize ) dedecms.com 
*piIndex = 0; dedecms.com 
else if ( ldwSize < 4096 ) 
copyright dedecms
*piIndex = 1; 内容来自dedecms 
else if ( ldwSize < 8192 ) 本文来自织梦 
*piIndex = 2; 织梦好,好织梦 
else  内容来自dedecms 
*piIndex = 3; 织梦内容管理系统 

最后我们设置pwFlags为0让浏览器执行默认动作. 就是检查图标缓冲看szIconFile/piIndex对是否已在缓冲里. 如果在, 就不调用IExtractIcon::Extract(). 我们返回S_OK表示GetIconLocation()成功了. 本文来自织梦

*pwFlags = 0; dedecms.com 
return S_OK; 

织梦内容管理系统

} 织梦内容管理系统 

由于我们已经告诉浏览器图标的位置,Extract()返回S_FALSE 即可.

内容来自dedecms

STDMETHODIMP CTxtIconShlExt::Extract ( 
内容来自dedecms
LPCTSTR pszFile,  

dedecms.com

UINTnIconIndex,  内容来自dedecms 
HICON*phiconLarge, 内容来自dedecms 
HICON*phiconSmall, 织梦好,好织梦 
UINTnIconSize ) 内容来自dedecms 
{ 内容来自dedecms 
return S_FALSE;// 告诉浏览器自己去解析图标. copyright dedecms 
} 本文来自织梦 

下面是运行中的扩展样子: dedecms.com

 [custom large icons - 24K] 内容来自dedecms

 [custom small icons - 26K] 内容来自dedecms

如果你改变GetIconLocation()pwFlags设为GIL_SIMULATEDOC, 图标如下所示: dedecms.com

 [custom large icons - 24K]

copyright dedecms

 [custom small icons - 25K]

内容来自dedecms

注意大图标视图, 使用了图标的16x16 版本. 在小图标视图, 浏览器缩小图标, 这样显示不太准确. 内容来自dedecms

解析方法2

方法2 涉及我们的扩展自己解析图标, 并忽略浏览器缓冲的图标. 使用这个方法, IExtractIcon::Extract()总被调用,并负责加载图标并返回两个图标句柄HICON 给浏览器– 一个是大图标, 一个是小图标. 该方法的好处是你不必考虑你的图标资源在文件中的顺序位置. 其缺陷在于它忽略了浏览器的图标缓冲,这会使显示速度减慢,特别是在有浏览有无数个文件的目录时. 内容来自dedecms

GetIconLocation()同方法1, 但在这里它只要获得文件的大小即可. 织梦内容管理系统

STDMETHODIMP CTxtIconShlExt::GetIconLocation ( 

织梦内容管理系统

UINTuFlags, 内容来自dedecms 
LPTSTR szIconFile, 织梦好,好织梦 
UINTcchMax, copyright dedecms 
int*piIndex, 

dedecms.com

UINT*pwFlags ) 本文来自织梦 
{ 
copyright dedecms
DWORDdwFileSizeLo, dwFileSizeHi; 
织梦好,好织梦
HANDLE hFile; copyright dedecms 
 织梦内容管理系统 
hFile = CreateFile ( m_szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, 
内容来自dedecms
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); 内容来自dedecms 
 

内容来自dedecms

if ( INVALID_HANDLE_VALUE == hFile ) 
copyright dedecms
return S_FALSE;//让浏览器使用默认的图标 织梦好,好织梦 
 
本文来自织梦
dwFileSizeLo = GetFileSize ( hFile, &dwFileSizeHi ); 织梦好,好织梦 
 
copyright dedecms
CloseHandle ( hFile ); 织梦内容管理系统 
 织梦好,好织梦 
if ( (DWORD) -1 == dwFileSizeLo&&GetLastError() != NO_ERROR ) 
本文来自织梦
return S_FALSE;//让浏览器使用默认的图标 内容来自dedecms 
 
织梦内容管理系统
m_ldwFileSize = ((DWORDLONG) dwFileSizeHi)<<32 | dwFileSizeLo; 内容来自dedecms 

一旦我们保存了文件大小, 我们就可以设置pwFlags 为GIL_DONTCACHE以告诉浏览器不要检查图标内存. 我们必须设置该标志因为我们不填写szIconFile/piIndex对并且通知浏览器忽略它们.

织梦好,好织梦

GIL_NOTFILENAME标志也应该被设置, 尽管在当前版本中其没有任何效果. 文档上是说这会告诉浏览器我们没有填写szIconFile/piIndex , 似乎其并没有被浏览器所检查. 但包含该标志总是有好处的, 以防未来的版本会测试这个标志.

dedecms.com

*pwFlags = GIL_NOTFILENAME | GIL_DONTCACHE; dedecms.com 
return S_OK; 
copyright dedecms
} 
本文来自织梦

现在仔细看一下Extract(). 其原型为:

dedecms.com

HRESULT IExtractIcon::Extract ( 本文来自织梦 
LPCTSTR pszFile,  copyright dedecms 
UINTnIconIndex,  内容来自dedecms 
HICON*phiconLarge, 织梦内容管理系统 
HICON*phiconSmall, 
织梦内容管理系统
UINTnIconSize ); 

本文来自织梦

其参数为: copyright dedecms

pszFile/nIconIndex

copyright dedecms

文件名和索引指定图标位置. 其值与从GetIconLocation() 返回的一样.

织梦好,好织梦

phiconLarge, phiconSmall

内容来自dedecms

HICON 的指针,由Extract()返回指向大图标和小图标的句柄数组. 织梦内容管理系统

nIconSize

织梦好,好织梦

指定要求的图标大小. 高字为小图标的长度(长宽一致), 低字为大图标的长度. 在一般情况下, 其值为0x00100020 (高字16, 低字32) 表示小图标应该是16x16, 大图标为32x32. dedecms.com

在我们的扩展中, 我们并没有在GetIconLocation() 填写pszFilenIconIndex 所以在这忽略之. 我们只加载图标并返回给浏览器.

copyright dedecms

STDMETHODIMP CTxtIconShlExt::Extract (  dedecms.com 
LPCTSTR pszFile, dedecms.com 
UINTnIconIndex,  
织梦内容管理系统
HICON*phiconLarge, 

dedecms.com

HICON*phiconSmall, copyright dedecms 
UINTnIconSize ) 

内容来自dedecms

{ 织梦内容管理系统 
UINT uIconID; 本文来自织梦 
 
织梦内容管理系统
// 根据文件大小决定使用哪个图标. 内容来自dedecms 
if ( 0 == m_ldwFileSize ) 

织梦好,好织梦

uIconID = IDI_ZERO_BYTES; 织梦好,好织梦 
else if ( m_ldwFileSize < 4096 ) 织梦内容管理系统 
uIconID = IDI_UNDER_4K; dedecms.com 
else if ( m_ldwFileSize < 8192 ) 内容来自dedecms 
uIconID = IDI_UNDER_8K; 

内容来自dedecms

else  
dedecms.com
uIconID = IDI_OVER_8K; 

dedecms.com

 本文来自织梦 
// 加载图标 织梦内容管理系统 
*phiconLarge = (HICON) LoadImage ( _Module.GetResourceInstance(),  
dedecms.com
MAKEINTRESOURCE(uIconID), IMAGE_ICON, 

本文来自织梦

32, 32, LR_DEFAULTCOLOR ); 
内容来自dedecms
 

内容来自dedecms

*phiconSmall = (HICON) LoadImage ( _Module.GetResourceInstance(),  
内容来自dedecms
MAKEINTRESOURCE(uIconID), IMAGE_ICON, 织梦内容管理系统 
16, 16, LR_DEFAULTCOLOR ); 
织梦内容管理系统
 
织梦好,好织梦
return S_OK; 
dedecms.com
} 
dedecms.com

终于完了! 浏览器将显示我们返回的图标.

织梦内容管理系统

要注意的一个细节是当使用方法2, 从GetIconLocation() 返回GIL_SIMULATEDOC标志没有任何效果.

织梦内容管理系统

注册扩展

图标扩展处理器注册在文件类型键下, 所以在我们的例子中它在HKCR\txtfile . 正如其它例子, 在txtfile键下有个ShellEx. 接着是个IconHandler键, 该键的默认值为我们的扩展的GUID. 注意每种文件类型只能有一个图标扩展处理器. 我们也必须更改DefaultIcon键的默认值为"%1" 以激活我们的扩展. 织梦内容管理系统

下面是注册所需的RGS 脚本文件: 本文来自织梦

HKCR 
织梦内容管理系统
{ 

dedecms.com

NoRemove txtfile copyright dedecms 
{ 

本文来自织梦

NoRemove DefaultIcon = s '%%1' copyright dedecms 
NoRemove ShellEx 本文来自织梦 
{ 
织梦好,好织梦
ForceRemove IconHandler = s '{DF4F5AE4-E795-4C12-BC26-7726C27F71AE}' dedecms.com 
} copyright dedecms 
} 本文来自织梦 
} 
本文来自织梦

注意为了指定"%1" 字符串我们要在脚本中写"%%1" , 因为% 是个表示可替换参数的特殊字符(如, "%MODULE%").

copyright dedecms

覆盖现存的DefaultIcon值会产生一个问题. 当我们注销扩展时怎样回写DefaultIcon原值? 答案是我们在DllRegisterServer()将原来DefaultIcon的值保存下来, 并在DllUnregisterServer() 中还原. 我们必须这样做使注册清除干净,恢复原来文件图标的显示. copyright dedecms

你可以看一下注册/注销函数是如何工作的. 我们调用ATL处理RGS脚本进行备份, 因为如果我们按其它方法,DefaultIcon在我们有机会备份之前就会被覆盖.

内容来自dedecms

要设置目录的特别图标,只要在该目录下生成一个desktop.ini文件,格式如下:

dedecms.com

[.ShellClassInfo] 织梦好,好织梦

IconFile=C:\VcFolder.ico

织梦好,好织梦

IconIndex=0 织梦好,好织梦

待续...?