毕业设计需要实现类似Dropbox样式的文件夹及文件的效果,即如果文件已经同步,则需要在相应的文件图标上添加一个标志。
经过一番百度google发现这个效果的实现是通过windows shell extension实现的,其中涉及的技术主要是COM与VC++。
由于各种原因,不愿意用VC++来实现,于是选择了用较新的C#来实现。
IconOverlay效果的实现原理比较简单,如果有COM的基础的话,用visual studio 中vc++ 的 ATL是很容易实现的。
下面的两个连接可以提供很多参考
http://msdn.microsoft.com/en-us/library/windows/desktop/bb761265(v=vs.85).aspx
http://www.codeproject.com/Articles/7484/How-to-overlay-an-icon-over-existing-shell-objects
MSDN中简单讲述了如何实现,codeproject中的文章则基本上是手把手地讲了实现的细节。
不过想要移植到C#中,不是那么简单。
最大的问题是COM与C#托管代码的互操作性,COM接口是基于C/C++的,其中的数据类型和C#有所不同,要解决两种语言之间数据类型的Marshal。
COM与C#互操作原理的讲述可以从MSDN上找到,下面是两个或许有用的链接,话说我找了挺久的~~
http://msdn.microsoft.com/zh-cn/library/aa686045.aspx
http://msdn.microsoft.com/zh-cn/magazine/cc164193.aspx
--------------------------------------------------------我是分割线------------------------------------------------------------------------------
以上是一些资料,以下是晒一晒我的实现过程
实现过程分为三部曲:
- 声明C#版本的 IShellIconOverlayIdentifier
-
[ComVisible(false)] [ComImport] [Guid("0C6C4200-C589-11D0-999A-00C04FD655E1")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IShellIconOverlayIdentifier { [PreserveSig] int IsMemberOf([MarshalAs(UnmanagedType.LPWStr)] string path, uint attributes); [PreserveSig] int GetOverlayInfo(IntPtr iconFileBuffer, int iconFileBufferSize, out int iconIndex, out uint flags); [PreserveSig] int GetPriority(out int priority); }
其中需要注意的是这里的Guid一定要和Com中的IShellIconOverlayIdentifier的GUID保持一致,因为这里只是声明了IShellIconOverlayIdentifier的C#版本,并非定义一个新的接口
-
- 实现上述接口
-
[ComVisible(true)] [Guid("95B6DB50-D997-4E2C-9E57-17447992F8B1")] publicclass HHIconOverlayA : IShellIconOverlayIdentifier { privateconststring GUID = "{95B6DB50-D997-4E2C-9E57-17447992F8B1}"; #region IShellIconOverlayIdentifier Members publicint IsMemberOf(string path, uint attributes) { if(true) //test condition, here show everything with icon overlay { return 0; //S_OK } return 1; // S_FALSE } publicint GetOverlayInfo(IntPtr iconFileBuffer, int iconFileBufferSize, outint iconIndex, outuint flags) { string icnFile = @"C:\overlay.ico"; byte[] bytes = Encoding.Unicode.GetBytes(icnFile); if (bytes.Length + 2 < iconFileBufferSize) { for (int i = 0; i < bytes.Length; i++) { Marshal.WriteByte(iconFileBuffer, i, bytes[i]); } //write the "\0\0" Marshal.WriteByte(iconFileBuffer, bytes.Length, 0); Marshal.WriteByte(iconFileBuffer, bytes.Length + 1, 0); } iconIndex = 0; flags = 1; // ISIOI_ICONINDEX 2 | ISIOI_ICONFILE 1 return0; // S_OK } publicint GetPriority(outint priority) { priority = 0; // 0-100 (0 is highest priority)return0; // S_OK } #endregion
我在GetOverlayInfo这个函数这里遇到了巨大的困难,第一个参数在Com接口里的类型是PWSTR,也就是wchar_t *类型,根据前述资料里面的讲解,它既是输入参数也是输出参数,我刚开始的时候用的是[In, Out, MarshalAs(UnmanagedType.LPWSTR)]String,然后发现效果不对,我又换成StringBuilder,最后的效果还是不对。
- 之后我在链接http://www.mombu.com/programming/c/t-c-shell-extension-handler-icon-overlay-handler-1568092.html中看到别人的实现,然后按着提示试了一下,发现 it works!
- 看来.Net CLR在String和wchar_t *转换的时候,并没有在最后自动加上宽字符的字符串结束符 \0\0,需要自己来完成Marshal
-
- 实现之后需要注册我们的服务,以使得Explorer能够识别并使用我们的服务。
-
#region Registry [System.Runtime.InteropServices.ComRegisterFunctionAttribute()] static void RegisterServer(String str1) { RegistryKey rk = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\_HHIconOverlayA"); rk.SetValue(string.Empty, GUID); rk.Close(); } [System.Runtime.InteropServices.ComUnregisterFunctionAttribute()] static void UnregisterServer(String str1) { Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\_HHIconOverlayA"); } #endregion
以上是类中的用来注册的代码
- 在项目属性中,选择签名,以产生程序集的强名;然后生成解决方案,会在bin目录的debug或release目录下产生xxx.dll文件
- 注册服务: regasm xxx.dll /codebase
- 重启Explorer,即可看到效果
-
GetOverlayInfo只能够设置那个图标,不能通过参数控制其大小,网上某些资料表明windows默认使用32x32大小的icon,所以,要注意自己的图标的设计。
比如要想自己的Overlay图标显示在左下角,可以做一个大小为32x32的图标,将自己要显示的部分放在左下角的16x16的地方,背景设置为透明,这样Overlay就老老实实地呆在左下角了。