C# 中图标、上下文菜单、列和工具提示处理程序






2.74/5 (15投票s)
在 C# 中扩展 Windows Shell。
引言
...........................................................................................................................
非常非常重要.....使用 dot Net 3.5 存在风险,当打开文件打开对话框时会崩溃其他应用程序 ...尝试使用非托管代码,如 VC++...或更早版本的 dot net,如 1.1... 不确定发生这种情况的原因, 将会进行一些研发。
............................................................................................................................
此代码可用于在 Windows 资源管理器中处理图标、列、上下文菜单和工具提示。 首先,让我告诉您什么是 Shell 扩展。 Shell 扩展是程序,可以添加功能或更改现有的 shell 行为。 Shell 是我们在 Windows 资源管理器中看到的 GUI,如进度条、文件夹、开始菜单、桌面等。
背景
编写 Windows 的 Shell 扩展并不容易,因为没有太多可用的代码。 我在网上搜索,找到了 VC++ 和 VB 的示例,但没有 C# 的代码。 所以我从不同的地方收集了信息,并将 VB 和 VC++ 代码转换为 C#。
Using the Code
您也可以只提取您想要的功能,提取单个接口很容易,例如
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("0000010e-0000-0000-C000-000000000046")] public interface IDataObject { [PreserveSig()] int GetData(ref FORMATETC a, ref STGMEDIUM b); [PreserveSig()] void GetDataHere(int a, ref STGMEDIUM b); [PreserveSig()] int QueryGetData(int a); [PreserveSig()] int GetCanonicalFormatEtc(int a, ref int b); [PreserveSig()] int SetData(int a, int b, int c); [PreserveSig()] int EnumFormatEtc(uint a, ref Object b); [PreserveSig()] int DAdvise(int a, uint b, Object c, ref uint d); [PreserveSig()] int DUnadvise(uint a); [PreserveSig()] int EnumDAdvise(ref Object a); } [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("000214e8-0000-0000-c000-000000000046")] public interface IShellExtInit { [PreserveSig()] int Initialize(IntPtr pidlFolder, IntPtr lpdobj, uint /*HKEY*/ hKeyProgID); } [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("000214e4-0000-0000-c000-000000000046")] public interface IContextMenu { [PreserveSig()] int QueryContextMenu(uint hmenu, uint iMenu, int idCmdFirst, int idCmdLast, uint uFlags); [PreserveSig()] void InvokeCommand(IntPtr pici); [PreserveSig()] void GetCommandString(int idcmd, uint uflags, int reserved, StringBuilder commandstring, int cch); }
这仅定义了接口。 您可以在代码中查找不同的结构类型,因为将所有代码粘贴在这里会使文章不必要地冗长。 现在是方法实现的时候了。 首先,我们必须实现 IShellExtInit 的 Initialize。 当文件资源管理器初始化上下文菜单扩展、属性表扩展或非默认拖放扩展时,会调用 Initialize 方法。 参数:pidlFolder [in] 对于上下文菜单扩展或属性表扩展,这指定父文件夹。 对于非默认拖放扩展,这指定目标文件夹。 lpdobj [in] 对于上下文菜单扩展或属性表扩展,这指定在该文件夹中选择的项的集合。 对于非默认拖放扩展,这指定被拖放的项。 hKeyProgID [in] 对于上下文菜单扩展或属性表扩展,这指定所选内容的焦点项的类型。 对于非默认拖放扩展,这指定文件夹类型。 返回值 如果成功,此方法应返回 S_OK,如果失败,则返回相应的错误。
int IShellExtInit.Initialize(IntPtr pidlFolder, IntPtr lpdobj, uint hKeyProgID)///Uses IShellExtInit for IColumnProvider and IContextMenu { try { if (lpdobj != (IntPtr)0) { // Get info about the directory IDataObject dataObject = ( 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(); dataObject.GetData(ref fmt, ref medium); m_hDrop = medium.hGlobal; } } catch (Exception e) { Logger.WriteLog("Initialize " + e.Message); } return 0; }
QueryContextMenu 方法将新菜单项添加到上下文菜单。 参数:hmenu [in] 将添加其他项目的菜单。 iMenu [in] 指定在菜单上的位置(绝不能为 –1)。 idCmdFirst [in] 指定范围的开始。 idCmdLast [in] 指定范围的结束。 uFlags [in] 指定上下文。 返回值:QueryContextMenu 返回添加到上下文菜单的项数。 有关更多信息,请参阅 http://msdn.microsoft.com/en-us/library/bb416552.aspx
int IContextMenu.QueryContextMenu(uint hMenu, uint iMenu, int idCmdFirst, int idCmdLast, uint uFlags)//Using IShellExtInit.Initialize and IContextMenu { // Create the popup to insert uint handleMenuPopup = Win32Helpers.CreatePopupMenu(); StringBuilder szFile = new StringBuilder(260); int id = 1; if ((uFlags & 0xf) == 0 || (uFlags & (uint)CMF.CMF_EXPLORE) != 0) { //Take the number of selected files uint nselected = Win32Helpers.DragQueryFile(m_hDrop, 0xffffffff, null, 0); //Get each file name for (uint uFile = 0; uFile < nselected; uFile++) { // Get the next filename. Win32Helpers.DragQueryFile(m_hDrop, uFile, szFile, szFile.Capacity + 1); fileNames += szFile.ToString() + "|"; } id = PopulateMenu(handleMenuPopup, idCmdFirst + id); // Add the popup to the context menu MENUITEMINFO menuItemInfo = new MENUITEMINFO(); menuItemInfo.cbSize = 48; menuItemInfo.fMask = (uint)MIIM.TYPE | (uint)MIIM.STATE | (uint)MIIM.SUBMENU; menuItemInfo.hSubMenu = (int)handleMenuPopup; menuItemInfo.fType = (uint)MF.STRING; menuItemInfo.dwTypeData = "&My Options"; // adding a new menu menuItemInfo.fState = (uint)MF.ENABLED; Win32Helpers.InsertMenuItem(hMenu, (uint)iMenu, 1, ref menuItemInfo); // Add a separator MENUITEMINFO seperator = new MENUITEMINFO(); seperator.cbSize = 48; seperator.fMask = (uint)MIIM.TYPE; seperator.fType = (uint)MF.SEPARATOR; Win32Helpers.InsertMenuItem(hMenu, iMenu + 1, 1, ref seperator); } return id; }
这部分将实际菜单添加到上下文菜单
// Add the popup to the context menu MENUITEMINFO menuItemInfo = new MENUITEMINFO(); menuItemInfo.cbSize = 48; menuItemInfo.fMask = (uint)MIIM.TYPE | (uint)MIIM.STATE | (uint)MIIM.SUBMENU; menuItemInfo.hSubMenu = (int)handleMenuPopup; menuItemInfo.fType = (uint)MF.STRING; menuItemInfo.dwTypeData = "&My Options"; // adding a new menu menuItemInfo.fState = (uint)MF.ENABLED; Win32Helpers.InsertMenuItem(menuItemInfo);
注册/注销
首先,我们必须将类的 GUID 放入已批准的扩展中。
// For set as approved shellex RegistryKey root; RegistryKey rk; root = Registry.LocalMachine; rk = root.OpenSubKey( "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true); rk.SetValue(guid.ToString(), "My shell extension"); rk.Close(); root.Close();
然后,在根目录下创建一个 .Myf 条目,并将其值设置为我们的文件类型。这个名字可以是任何东西,比如“Myf”或“MyFile”。
root = Registry.ClassesRoot; rk = root.CreateSubKey(".Myf"); rk.SetValue("", "Myf"); rk.Close();
然后在“Myf\\shellx\\ContextMenuHandlers”下创建“Myf”子键,并将其值设置为我们类的 GUID。
rk = root.CreateSubKey("Myf\\shellex\\ContextMenuHandlers\\Myf"); rk.SetValue("", guid.ToString()); rk.Close();
再次在创建的“Myf”子键下创建一个子键“\\shellex\\IconHandler”,并将其值设置为我们的 GUID 类。
rk = root.CreateSubKey("Myf\\shellex\\IconHandler"); rk.SetValue("", guid.ToString()); rk.Close();
现在,在根目录下,在“Folder\\shellex\\ColumnHandlers\\”下创建一个我们类的 GUID 子键,并将其值设置为“RuntimeType”(我不知道我们是否可以给其他名称或任何东西)
rk = root.CreateSubKey(@"Folder\shellex\ColumnHandlers\" + guid.ToString()); rk.SetValue(string.Empty, "RuntimeType"); rk.Close();
最后,为工具提示输入以下内容到“.Myf”下。
rk = root.CreateSubKey(".Myf\\shellex\\{00021500-0000-0000-C000-000000000046}"); rk.SetValue("", guid.ToString()); rk.Close();
要自动刷新资源管理器。
// Tell Explorer to refresh SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
要注销,只需删除我们创建的所有键。
注册/注销的完整代码
[ComRegisterFunction] static void RegisterServer(Type t) { try { // For Winnt set me as an approved shellex RegistryKey root; RegistryKey rk; root = Registry.LocalMachine; rk = root.OpenSubKey( "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true); rk.SetValue(guid.ToString(), "My shell extension"); rk.Close(); root.Close(); root = Registry.ClassesRoot; rk = root.CreateSubKey(".Myf"); rk.SetValue("", "Myf"); rk.Close(); rk = root.CreateSubKey("Myf\\shellex\\ContextMenuHandlers\\Myf"); rk.SetValue("", guid.ToString()); rk.Close(); rk = root.CreateSubKey("Myf\\shellex\\IconHandler"); rk.SetValue("", guid.ToString()); rk.Close(); rk = root.CreateSubKey(@"Folder\shellex\ColumnHandlers\" + guid.ToString()); rk.SetValue(string.Empty, "RuntimeType"); rk.Close(); rk = root.CreateSubKey( ".Myf\\shellex\\{00021500-0000-0000-C000-000000000046}"); rk.SetValue("", guid.ToString()); rk.Close(); // Tell Explorer to refresh SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero); Logger.WriteLog("Registered at: " + System.DateTime.Now); } catch (Exception e) { Logger.WriteLog(e.Message); } } //[System.Runtime.InteropServices.ComUnregisterFunctionAttribute()] [ComUnregisterFunction] static void UnregisterServer(Type t) { try { RegistryKey root; RegistryKey rk; // Remove ShellExtenstions registration root = Registry.LocalMachine; rk = root.OpenSubKey( "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true); rk.DeleteValue(guid); rk.Close(); root.Close(); // Delete regkey root = Registry.ClassesRoot; root.DeleteSubKeyTree(".Myf"); root.DeleteSubKeyTree("Myf"); root.DeleteSubKey(@"Folder\shellex\ColumnHandlers\" + guid.ToString()); root.Close(); SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero); Logger.WriteLog("Un-Registered at: " + System.DateTime.Now); } catch (Exception e) { Logger.WriteLog(e.Message); } }
注意事项
- 您可以使用 regasm 注册此 dll
- 通过检查 Project->Properties->Buid->Register for COM Interop。
编译并运行以更新注册表和 DLL
再次编译时,您可能会收到 dll 的复制错误,因此请从结束任务和新任务中重新启动资源管理器。
最后的话
如果您需要任何帮助,可以通过 ashwanisihag@yahoo.com 给我发邮件。
本文的灵感来自
http://www.kbcafe.com/juice/?guid=20041022155459
和
shellextguide8.aspx
感谢这些人提供了他们精彩的文章。
温暖的问候
Ashwani Sihag
ION 解决方案
莫哈里