畢業設計需要實現類似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就老老實實地呆在左下角了。