(C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?
原文 (C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)
接上一节:(C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单
关于注册
动态库必须注册才能使用。除了使用 regasm 来注册 DLL 以外,还应该在代码中增加 RegisterServer 和 UnregisterServer 方法,以指导 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”。然而如果具体到某种文件类型。可能会稍微复杂一点。我们可以从代码中看到一些启示:

private static void RegTXT()
{
RegistryKey root;
RegistryKey rk;
root = Registry.ClassesRoot;
rk = root.OpenSubKey(".txt");
string txtclass = (string)rk.GetValue("");
if (string.IsNullOrEmpty(txtclass))
{
txtclass = "TXT";
rk.SetValue("", txtclass);
}
rk.Close();
rk = root.CreateSubKey(txtclass + "\\shellex\\ContextMenuHandlers\\" + KEYNAME);
rk.SetValue("", GUID);
rk.Close();
rk = root.CreateSubKey(txtclass + "\\shellex\\IconHandler");
rk.SetValue("", GUID);
rk.Close();
rk = root.CreateSubKey(txtclass + "\\shellex\\{00021500-0000-0000-C000-000000000046}");
rk.SetValue("", GUID);
rk.Close();
}
对于关联 .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 则用于初始化只涉及一个选择文件时的处理。

[ComImport(), ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("0000010b-0000-0000-C000-000000000046")]
public interface IPersistFile
{
[PreserveSig]
uint GetClassID(out Guid pClassID);
[PreserveSig]
uint IsDirty();
[PreserveSig]
uint Load([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, [In] uint dwMode);
[PreserveSig]
uint Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, [In] bool fRemember);
[PreserveSig]
uint SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
[PreserveSig]
uint GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName);
}
对于这个接口,我们只需要用到 Load 方法

public uint Load(string pszFileName, uint dwMode)
{
szFileName = pszFileName;
return S_OK;
}
szFileName 是全局变量,用来记住当前操作的文件路径。
IExtractIcon 接口图标扩展处理器实现 IExtractIcon 接口,当浏览器需要为文件显示一个图标时将调用该接口。
因为我们的扩展用于文本文件,浏览器将在每次显示文本文件对象时调用 IExtractIcon 的方法。
IExtractIcon 有两个方法,它们的作用是告诉浏览器所使用的图标。记住:浏览器为显示的每一个文件都将创建一个COM 对象。
这就是说每一个文件都将有一个COM C++类对象对应. 因此在你的扩展中应该避免费时的操作以防止浏览界面反应迟滞。

[ComVisible(true), ComImport, Guid("000214eb-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IExtractIcon
{
[PreserveSig]
int GetIconLocation([In] ExtractIconOptions uFlags,
[In] IntPtr szIconFile,
[In] uint cchMax,
[Out] out int piIndex,
[Out] out ExtractIconFlags pwFlags);
[PreserveSig]
int Extract([In, MarshalAs(UnmanagedType.LPWStr)] string pszFile,
uint nIconIndex,
[Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(IconMarshaler))] out Icon phiconLarge,
[Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(IconMarshaler))] out Icon phiconSmall,
[In] uint nIconSize);
}
和许多教程上面说的一样,有两种方法可将图标返回给浏览器。但我在尝试第一种方式的时候,未能成功,十分奇怪。不过我还是应该把这种方法简单说明。
第一种是 GetIconLocation() 可以返回文件名/索引对以指出包含图标的文件,和图标在该文件中索引位置(以0为基)。Extract() 只需返回 S_FALSE 给浏览器让它自己来解析图标。该方法的特别之处在于浏览器在 GetIconLocation() 返回之后不一定会调用 Extract().。浏览器会保持一个图标缓存以存储最近使用的图标。
如果 GetIconLocation() 返回最近已使用的文件名/索引对,而且图标仍然在缓存中,浏览器就可以直接使用缓存中的图标而不会去调用Extract()。
第二种方法是从GetIconLocation() 中返回不要查看缓冲的标志,这样会使浏览器去调用Extract(),Extract() 则负责加载图标资源并将其句柄返回给浏览器。
这里具体介绍第二种方法。在这方法中,GetIconLocation() 作用仅仅是设置一些标志位,以及获取文件大小。

int IExtractIcon.GetIconLocation(ExtractIconOptions uFlags, IntPtr szIconFile, uint cchMax, out int piIndex, out ExtractIconFlags pwFlags)
{
piIndex = -1;
try
{
FileInfo f = new FileInfo(szFileName);
lngFileSize = f.Length;
pwFlags = ExtractIconFlags.DontCache | ExtractIconFlags.NotFilename;
return S_OK;
}
catch { }
pwFlags = ExtractIconFlags.None;
return S_FALSE;
}
其参数为: 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 给浏览器 – 一个是大图标, 一个是小图标。该方法的好处是你不必考虑你的图标资源在文件中的顺序位置。其缺陷在于它忽略了浏览器的图标缓冲,这会使显示速度减慢,特别是在有浏览有无数个文件的目录时。

int IExtractIcon.Extract(string pszFile, uint nIconIndex, out Icon phiconLarge, out Icon phiconSmall, uint nIconSize)
{
phiconLarge = null;
phiconSmall = null;
try
{
if (lngFileSize > 16 * 1024)
{
phiconLarge = Resource1._2;
phiconSmall = new Icon(Resource1._2, new Size(16, 16));
}
else if (lngFileSize > 0)
{
phiconLarge = Resource1._1;
phiconSmall = new Icon(Resource1._1, new Size(16, 16));
}
else
{
phiconLarge = Resource1._0;
phiconSmall = new Icon(Resource1._0, new Size(16, 16));
}
return S_OK;
}
catch { }
return S_FALSE;
}
其参数为:
pszFile/nIconIndex 文件名和索引指定图标位置。其值与从 GetIconLocation() 返回的一样。
phiconSmall HICON 的指针,由 Extract() 返回指向大图标和小图标的句柄数组。
nIconSize 指定要求的图标大小。高字为小图标的长度 (长宽一致),低字为大图标的长度。在一般情况下, 其值为0x00100020 (高字16, 低字 32) 表示小图标应该是 16x16,大图标为 32x32。在我们的扩展中, 我们并没有在 GetIconLocation() 里填写 pszFile 和 nIconIndex 所以在这忽略,我们只加载图标并返回给浏览器。
从代码可以看到,根据文件大小的不同,加载了相应的图标资源返回给浏览器。效果如下:
代码:http://files.cnblogs.com/lemony/MyContextMenu.rar
关于代码:代码里面还包括了提示扩展的代码,如果有兴趣,可自行阅读。
题外话:还有相当多的关于 Shell 扩展的内容无法一一说明,如果有机会,以后会尽量补上。或大家查阅网上的“Windows Shell扩展编程完全指南”(虽然是VC版的,但内容相当丰富)
(C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?的更多相关文章
- (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
原文 (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示 (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows She ...
- (C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单
原文 (C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单 (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windo ...
- (C#)Windows Shell 外壳编程系列6 - 执行
原文(C#)Windows Shell 外壳编程系列6 - 执行 (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列5 - ...
- (C#)Windows Shell 外壳编程系列5 - 获取图标
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令 有 ...
- (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列3 - 上下文菜单(iContextMenu)(一)右键菜单 上一节说到如 ...
- (C#)Windows Shell 外壳编程系列2 - 解释,从“桌面”开始展开
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一篇:(C#)Windows Shell 外壳编程系列1 - 基础,浏览一个文件夹 让我们详细解释一下 Shell 编程中最基本 ...
- (C#)Windows Shell 外壳编程系列3 - 上下文菜单(iContextMenu)(一)右键菜单
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列2 - 解释,从“桌面”开始展开 这里解释上一节中获取名称的方法 GetD ...
- (C#)Windows Shell 外壳编程系列1 - 基础,浏览一个文件夹
1 - 基础,浏览一个文件夹 我们知道,在win32中是以外壳名字空间的形式来组织文件系统的,在外壳名字空间里的每一个对象(注)都实现了一个IShellFolder的接口,通过这个接口我们可以直接查询 ...
- 【Shell脚本编程系列】知识储备以及建立规范的脚本
前言 学习shell脚本编程需要的知识储备: vi/vim编辑器命令 vimrc设置要熟练 基础命令,100多个要熟练 基础和常用的网络服务命令要会:nfs . rsync. inotify . la ...
随机推荐
- md笔记——使用 @font-face 引入你喜欢的字体
使用 @font-face 引入你喜欢的字体 原理 CSS3的自定义字体@font-face 规则的工作原理 使用@font-face规则初看起来非常简单.从本质上看,它只需要两个步骤. 首先,使用 ...
- Lc.exe已退出 代码为-1问题解决方法
对于用vs作为开发工具的同学来说,可能常常会碰到“Lc.exe已退出 代码为-1”的问题,造成这个结果的一般是因为加入了第三方的插件程序造成的,今天一一讲解如何解决. 工具/原料 vs各版本开发工具 ...
- C++结构体中sizeof
说明: 结构体的sizeof值,并不是简单的将其中各元素所占字节相加,而是要考虑到存储空间的字节对齐问题.这些问题在平时编程的时候也确实不怎么用到,但在一些笔试面试题目中出是常常出现,一.解释 现代计 ...
- 【转载】谈谈Cookie
0×00 引言 在Web技术的发展史上,Cookie技术的出现是一次重大的 变革.但是, Cookie技术又是一项非常有争议的技术,从它诞生之日起就成了广大网络用户和Web开发人员的一个争论焦点,原因 ...
- [暂停一天]从零开始PHP学习 - 第六天
今天这个系列没有时间去写了 在公司完善一个项目 已经备好6瓶咖啡 两天 + 一夜 完成这个项目 真是苦逼 诶 反正这几天 明白一个道理:别以为你多牛B 你不会的东西多了! 比你牛B的人也多 ...
- bzoj 2141 : 排队 分块
题目链接 2141: 排队 Time Limit: 4 Sec Memory Limit: 259 MBSubmit: 1169 Solved: 465[Submit][Status][Discu ...
- 四轴飞行器1.3 MPU6050(大端)和M4的FPU开启方法
四轴飞行器1.3 MPU6050(大端)和M4的FPU开启方法 原创文章,欢迎转载,转载请注明出处 最近时间花在最多的地方就是STM32的I2C上了.之前就知道STM32的I2C并不好用, ...
- 未能从程序集“System.ServiceModel, Version=3.0.0.0”中加载类型“System.ServiceModel.Activation.HttpModule” 的解决办法
未能从程序集“System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089”中加载类型“ ...
- Mybatis入门 digest
http://www.mybatis.org/mybatis-3/zh/configuration.html userDao-mapping.xml相当于是UserDao的实现, 同时也将User实体 ...
- 高级复制实验配置添加复制节点操作时报错:ORA-23308: object GP.T does not exist or is invalid
出错原因: 使用高级复制时,在源端启动复制支持,执行语句:REPADMIN@bys1>execute dbms_repcat.generate_replication_support('gp', ...