(C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?

(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)

 

接上一节:(C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单

 

关于注册

动态库必须注册才能使用。除了使用 regasm 来注册 DLL 以外,还应该在代码中增加 RegisterServerUnregisterServer 方法,以指导 DLL 注册时,在 Windows 注册表中增加什么键。关于具体键以下做简单说明:

1) 注册 DLL 的 Shell Extensions具体位置是 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved,增加以 GUID 为名称的键,值则是动态库说明。(此位置里面全是 Shell 扩展的动态库注册,许多相关软件就是从里面获取信息,例如 ShexView

2) 关联文件。Shell 扩展一般是针对文件或者文件夹的,因此必须关联。许多人都熟知“HKEY_CLASSES_ROOT\*”的作用,就是用来关联所有文件。而文件夹则是“HKEY_CLASSES_ROOT\Folder”。然而如果具体到某种文件类型。可能会稍微复杂一点。我们可以从代码中看到一些启示:

 

RegTXT

对于关联 .TXT,首先应该找寻“HKEY_CLASSES_ROOT\.txt”,但事情远远没那么简单,因为相当多的文本编辑工具,都会把改键重定向,例如EMEditor会把改键的默认值改为“emeditor.txt”。被重定向后,我们为了不破坏原有关联,应该到新的地方去注册(如果没有,我们就修改重定向至 TXT)。

 

无论是 *、文件夹还是具体文件类型,都会有 ShellEx 的键,为 Shell 扩展专用。具体不同的扩展,应该注册不同的键。例如 ContextMenuHandlers、IconHandler、或者{00021500-0000-0000-C000-000000000046}(其实这就是QueryInfo)。注册的方法很简单,把默认值改为 GUID 即可。

 

相同文件类型不同图标?

如果是以前,我会对这句话十分吃惊。但现在这种现象比比皆是。除了我们的例子外,.NET 程序员最熟悉的莫过于 Sln 解决方案文件了。不同版本的 Sln 图标不同,上面有个小版本号提示。

 

不过后来我了解到,原来不同 Exe 显示不同的图标,也是这种原理,我晕。。。

 

扩展接口
图标扩展处理器实现两个接口 IPersistFile IExtractIcon
记得 IShellExtInit 接口用于一次有多个选择文件时的处理,而 IPersistFile 则用于初始化只涉及一个选择文件时的处理。

 

IPersistFile 原型

 

对于这个接口,我们只需要用到 Load 方法

 

Load

 

szFileName 是全局变量,用来记住当前操作的文件路径。

 

IExtractIcon 接口图标扩展处理器实现 IExtractIcon 接口,当浏览器需要为文件显示一个图标时将调用该接口。
因为我们的扩展用于文本文件,浏览器将在每次显示文本文件对象时调用 IExtractIcon 的方法。

IExtractIcon 有两个方法,它们的作用是告诉浏览器所使用的图标。记住:浏览器为显示的每一个文件都将创建一个COM 对象。
这就是说每一个文件都将有一个COM C++类对象对应. 因此在你的扩展中应该避免费时的操作以防止浏览界面反应迟滞。

IExtractIcon 原型

 

和许多教程上面说的一样,有两种方法可将图标返回给浏览器。但我在尝试第一种方式的时候,未能成功,十分奇怪。不过我还是应该把这种方法简单说明。

 

第一种是 GetIconLocation() 可以返回文件名/索引对以指出包含图标的文件,和图标在该文件中索引位置(以0为基)。Extract() 只需返回 S_FALSE 给浏览器让它自己来解析图标。该方法的特别之处在于浏览器在 GetIconLocation() 返回之后不一定会调用 Extract().。浏览器会保持一个图标缓存以存储最近使用的图标。
如果 GetIconLocation() 返回最近已使用的文件名/索引对,而且图标仍然在缓存中,浏览器就可以直接使用缓存中的图标而不会去调用Extract()

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

 

这里具体介绍第二种方法。在这方法中,GetIconLocation() 作用仅仅是设置一些标志位,以及获取文件大小。

 

GetIconLocation

其参数为: uFlags 改变扩展行为的标志。
ExtractIconFlags.DONTCACHE 告诉浏览器不要检查图标缓冲而去使用最近的 szIconFile/piIndex 对。其结果是IExtractIcon::Extract() 将被调用.。
ExtractIconFlags.NOTFILENAME 根据 MSDN,该标志告诉浏览器当GetIconLocation()返回时忽略 szIconFile/piIndex 的内容。
szIconFile 是由shell 提供的一个缓冲要求我们填入包含所使用的图标的文件名.
cchMax 是该缓冲区的大小。
piIndex int 的指针,要求我们添入图标在文件中的索引。
pwFlags UINT 的指针,要求我们返回影响浏览器行为的标志。

 

使用第二种方法,我们并不需要填写 piIndex 和 szIconFile,而 IExtractIcon.Extract() 总被调用,并负责加载图标并返回两个图标句柄 HICON 给浏览器 – 一个是大图标, 一个是小图标。该方法的好处是你不必考虑你的图标资源在文件中的顺序位置。其缺陷在于它忽略了浏览器的图标缓冲,这会使显示速度减慢,特别是在有浏览有无数个文件的目录时。

 

IExtractIcon.Extract

其参数为:

pszFile/nIconIndex 文件名和索引指定图标位置。其值与从 GetIconLocation() 返回的一样。
phiconSmall HICON 的指针,由 Extract() 返回指向大图标和小图标的句柄数组。
nIconSize 指定要求的图标大小。高字为小图标的长度 (长宽一致),低字为大图标的长度。在一般情况下, 其值为0x00100020 (高字16, 低字 32) 表示小图标应该是 16x16,大图标为 32x32。在我们的扩展中, 我们并没有在 GetIconLocation() 里填写 pszFile 和 nIconIndex 所以在这忽略,我们只加载图标并返回给浏览器。

 

从代码可以看到,根据文件大小的不同,加载了相应的图标资源返回给浏览器。效果如下:

 

 

代码:https://files.cnblogs.com/lemony/MyContextMenu.rar

 

关于代码:代码里面还包括了提示扩展的代码,如果有兴趣,可自行阅读。

 

题外话:还有相当多的关于 Shell 扩展的内容无法一一说明,如果有机会,以后会尽量补上。或大家查阅网上的“Windows Shell扩展编程完全指南”(虽然是VC版的,但内容相当丰富)

posted on 2008-09-01 09:38  柠檬隐士  阅读(8986)  评论(17编辑  收藏  举报