65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.74/5 (15投票s)

2008年7月18日

CPOL

3分钟阅读

viewsIcon

60018

downloadIcon

981

在 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);
            }
        }

注意事项

  1. 您可以使用 regasm 注册此 dll
  2. 通过检查 Project->Properties->Buid->Register for COM Interop。

编译并运行以更新注册表和 DLL

再次编译时,您可能会收到 dll 的复制错误,因此请从结束任务和新任务中重新启动资源管理器。

最后的话

如果您需要任何帮助,可以通过 ashwanisihag@yahoo.com 给我发邮件。

本文的灵感来自
http://www.kbcafe.com/juice/?guid=20041022155459

shellextguide8.aspx


感谢这些人提供了他们精彩的文章。
温暖的问候
Ashwani Sihag
ION 解决方案
莫哈里

© . All rights reserved.