在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
(本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)
接上一节:(C#)Windows Shell 外壳编程系列6 - 执行
从本节起,我所要讲述的是对 Windows 系统的“Shell 扩展”。“Shell 扩展”从字面上分两个部分:Shell 与 Extension。Shell 指 Windows Explorer,而Extension 则指由你编写的当某一预先约定好的事件(如在以. doc 为后缀的文件图标上单击右键)发生时由 Explorer 调用执行的代码。因此一个“Shell 扩展”就是一个为 Explorer 添加功能的 COM 对象。
现在你可能想知道“Shell 扩展”到底是什么样的,不过我还是乐意把我后面所实现的技术效果直接展示出来。以下三副图片分别代表了三种“Shell 扩展”:
(1)实现类似 WinRAR 的右键菜单
(2)根据文本大小,显示不同的 TXT 文件图标
(3)当鼠标移动到 TXT 文件图标上的时候,显示内容预览。
好,废话不多说了,赶紧进入今天要讲述的内容: ContextMenu 注册文件右键菜单。
对于 WinRAR 所实现的效果,其实叫做上下文菜单。例如我们把扩展关联到 .TXT 文件,当用户右键单击文本文件对象时扩展就会被调用,然后向系统菜单增加菜单项,并响应相应的命令。由此可见,基本上每种 Shell 扩展,都需要做一些几乎一样的事情。 初始化接口 当我们的shell扩展被加载时,Explorer 将调用我们所实现的COM对象的 QueryInterface() 函数以取得一个 IShellExtInit 接口指针。该接口仅有一个方法 Initialize(),其函数原型为:
IShellExtInit
{ [PreserveSig()] int Initialize(IntPtr pidlFolder, IntPtr lpdobj, uint hKeyProgID); }
Explorer 使用该方法传递给我们各种各样的信息.
因此我们可以在这个方法中,获取到被右击选择的一个或多个文件/文件夹名。
hKeyProgID)
{ try { m_dataObject = null; if (lpdobj != (IntPtr)0) { m_dataObject = (ShellLib.IDataObject)Marshal.GetObjectForIUnknown(lpdobj); FORMATETC fmt = new FORMATETC(); fmt.cfFormat = CLIPFORMAT.CF_HDROP; fmt.ptd = 0; fmt.dwAspect = DVASPECT.DVASPECT_CONTENT; fmt.lindex = -1; fmt.tymed = TYMED.TYMED_HGLOBAL; STGMEDIUM medium = new STGMEDIUM(); m_dataObject.GetData(ref fmt, ref medium); m_hDrop = medium.hGlobal; } } catch (Exception) { } return S_OK; }
IDataObject 是一个接口,包含了一些获取文件名的方法,后面可以用到。
与上下文菜单交互的接口
IContextMenu
{ [PreserveSig()] int QueryContextMenu(HMenu hmenu, uint iMenu, uint idCmdFirst, uint idCmdLast, CMF uFlags); [PreserveSig()] void InvokeCommand(IntPtr pici); [PreserveSig()] void GetCommandString(uint idcmd, GCS uflags, uint reserved, IntPtr commandstring, int cchMax); }
第一个是 QueryContextMenu(), 它让我们可以修改上下文菜单。其参数: hmenu 上下文菜单句柄。
我们调用该方法,为上下文菜单增加几个菜单项:
idCmdLast, CMF uFlags)
{ int id = 0; if ((uFlags & (CMF.CMF_VERBSONLY | CMF.CMF_DEFAULTONLY | CMF.CMF_NOVERBS)) == 0 || (uFlags & CMF.CMF_EXPLORE) != 0) { //创建子菜单 HMenu submenu = ShellLib.Helpers.CreatePopupMenu(); Helpers.AppendMenu(submenu, MFMENU.MF_STRING, new IntPtr(idCmdFirst + id++), "复制路径(&C)"); Helpers.AppendMenu(submenu, MFMENU.MF_STRING, new IntPtr(idCmdFirst + id++), "复制文本内容(&T)"); Helpers.AppendMenu(submenu, MFMENU.MF_STRING, new IntPtr(idCmdFirst + id++), "柠檬的博客(&L)"); //将子菜单插入到上下文菜单中 Helpers.InsertMenu(hMenu, 1, MFMENU.MF_BYPOSITION | MFMENU.MF_POPUP, submenu.handle, "MyContextMenu(&Y)"); //为菜单增加图标 Bitmap bpCopy = Resource1.copy; Helpers.SetMenuItemBitmaps(submenu, 0, MFMENU.MF_BYPOSITION, bpCopy.GetHbitmap(), bpCopy.GetHbitmap()); Helpers.SetMenuItemBitmaps(submenu, 1, MFMENU.MF_BYPOSITION, bpCopy.GetHbitmap(), bpCopy.GetHbitmap()); Bitmap bpHome = Resource1.home; Helpers.SetMenuItemBitmaps(submenu, 2, MFMENU.MF_BYPOSITION, bpHome.GetHbitmap(), bpHome.GetHbitmap()); } return id; }
在状态栏上显示提示帮助
cchMax)
{ string tip = ""; switch (uflags) { case GCS.VERB: break; case GCS.HELPTEXTW: switch (idcmd) { case 0: tip = "把选中的文件/文件夹的全路径复制到剪切板"; break; case 1: tip = "把选中的 TXT 文本内容复制到剪切板"; break; case 2: tip = "访问柠檬的博客 http://lemony.cnblogs.com"; break; default: break; } if (!string.IsNullOrEmpty(tip)) { byte[] data = new byte[cchMax * 2]; Encoding.Unicode.GetBytes(tip, 0, tip.Length, data, 0); Marshal.Copy(data, 0, commandstring, data.Length); } break; } }
执行用户的选择 CMINVOKECOMMANDINFO 结构带有大量的信息, 但我们只关心 lpVerb 和 hwnd 这两个成员。 hwnd 是用户激活我们的菜单扩展时所在的浏览器窗口的句柄。
我们可以根据被点击的菜单项索引,来执行相应的操作。
IContextMenu.InvokeCommand(IntPtr pici)
{ INVOKECOMMANDINFO ici = (INVOKECOMMANDINFO)Marshal.PtrToStructure(pici, typeof(ShellLib.INVOKECOMMANDINFO)); StringBuilder sb = new StringBuilder(1024); StringBuilder sbAll = new StringBuilder(); uint nselected; switch (ici.verb) { case 0: //复制文件名 nselected = Helpers.DragQueryFile(m_hDrop, 0xffffffff, null, 0); for (uint i = 0; i < nselected; i++) { ShellLib.Helpers.DragQueryFile(m_hDrop, i, sb, sb.Capacity + 1); sbAll.Append(sb.ToString() + "\n"); } Clipboard.Clear(); Clipboard.SetDataObject(sbAll.ToString(), true); break; case 1: //复制文件内容 nselected = Helpers.DragQueryFile(m_hDrop, 0xffffffff, null, 0); for (uint i = 0; i < nselected; i++) { ShellLib.Helpers.DragQueryFile(m_hDrop, i, sb, sb.Capacity + 1); StreamReader sr = new StreamReader(sb.ToString(), Encoding.GetEncoding("gb2312")); sbAll.Append(sr.ReadToEnd()); sr.Close(); } Clipboard.Clear(); Clipboard.SetDataObject(sbAll.ToString(), true); break; case 2: //调用浏览器,打开网页 Process proc = new Process(); proc.StartInfo.FileName = "IExplore.exe"; proc.StartInfo.Arguments = "http://lemony.cnblogs.com"; proc.Start(); break; default: break; } }
注册Shell扩展 (请原谅我未能抽出时间对注册扩展做详细的说明(如果以后有机会会补上),大家可以自行研究)
RegisterServer(String str1)
{ try { //注册 DLL RegistryKey root; RegistryKey rk; root = Registry.LocalMachine; rk = root.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true); rk.SetValue(GUID, KEYNAME); rk.Close(); root.Close(); //注册文件 RegTXT(); } catch{ } } [System.Runtime.InteropServices.ComUnregisterFunctionAttribute()] static void UnregisterServer(String str1) { try { //注销动态库 RegistryKey root; RegistryKey rk; root = Registry.LocalMachine; rk = root.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true); rk.DeleteValue(GUID); rk.Close(); root.Close(); //注销文件 UnRegTXT(); } catch { } } 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(); } private static void UnRegTXT() { RegistryKey root; RegistryKey rk; root = Registry.ClassesRoot; rk = root.OpenSubKey(".txt"); rk.Close(); string txtclass = (string)rk.GetValue(""); if (!string.IsNullOrEmpty(txtclass)) { root.DeleteSubKey(txtclass + "\\shellex\\ContextMenuHandlers\\" + KEYNAME); root.DeleteSubKey(txtclass + "\\shellex\\IconHandler"); root.DeleteSubKey(txtclass + "\\shellex\\{00021500-0000-0000-C000-000000000046}"); } }
.NET 开发的动态库有些特别,需要在 .NET SDK 中注册 regasm MyContextMenu.dll /CodeBase
代码:https://files.cnblogs.com/lemony/MyContextMenu.rar
关于代码:代码里面还包括了图标扩展和提示扩展的代码,如果有兴趣,可自行阅读。
题外话:还有相当多的关于 Shell 扩展的内容无法一一说明,如果有机会,以后会尽量补上。或大家查阅网上的“Windows Shell扩展编程完全指南”(虽然是VC版的,但内容相当丰富)
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论