• (C#)Windows Shell 外壳编程系列3 上下文菜单(iContextMenu)(一)右键菜单


    (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢~)

    这里解释上一节中获取名称的方法

    GetDisplayNameOf
    定义:

    void GetDisplayNameOf(
                IntPtr pidl,
                SHGNO uFlags,
                IntPtr lpName);

    该方法是用来转换PIDL成为可显示的名称字符串。PIDL必须是相对于对象的父目录的。换句话说,它必须包含一个非空的SHITEMID 结构。因为有多种命名对象的方式,资源管理器通过在uFlags参数中定义SHGNO标识的组合来表示名称类型。SHGDN_NORMAL或SHGDN_INFOLDER将被用来指定名称是相对于文件夹的还是相对于桌面的。其他三个值SHGDN_FOREDITING、SHGDN_FORADDRESSBAR和SHGDN_FORPARSING可以用来指定名称的用途。 名称必须按STRRET的结构形式返回,如果SHGDN_FOREDITING、SHGDN_FORADDRESSBAR和 SHGDN_FORPARSING没有设定,就返回外壳对象的显示名称。
    具体实现方法:

    /**//// <summary>
    /// 获取显示名称
    /// </summary>
    public static string GetNameByIShell(IShellFolder Root, IntPtr pidlSub)
    {
                IntPtr strr = Marshal.AllocCoTaskMem(MAX_PATH * 2 + 4);
                Marshal.WriteInt32(strr, 0, 0);
                StringBuilder buf = new StringBuilder(MAX_PATH);
                Root.GetDisplayNameOf(pidlSub, SHGNO.INFOLDER, strr);
                API.StrRetToBuf(strr, pidlSub, buf, MAX_PATH);
                Marshal.FreeCoTaskMem(strr);
    return buf.ToString();
            }

    SHGNO
    public enum SHGNO
    {
            NORMAL = 0x0,
            INFOLDER = 0x1,
            FOREDITING = 0x1000,
            FORADDRESSBAR = 0x4000,
            FORPARSING = 0x8000,
        }

    事实上,只要修改 SHGNO ,就可以获取其绝对路径:

    /**//// <summary>
    /// 根据路径获取 IShellFolder 和 PIDL
    /// </summary>
    public static IShellFolder GetShellFolder(IShellFolder desktop, string path, out IntPtr Pidl)
    {
                IShellFolder IFolder;
    uint i, j = 0;
                desktop.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, path, out i, out Pidl, ref j);
                desktop.BindToObject(Pidl, IntPtr.Zero, ref Guids.IID_IShellFolder, out IFolder);
    return IFolder;
            }

    但我们还关心类似“桌面”、“我的文档”这种既是普通文件夹又是特殊对象的绝对路径如何获得,这里就要用到 SHGetSpecialFolderPath API 了。

    [DllImport("Shell32.Dll")]
    private static extern bool SHGetSpecialFolderPath(
                IntPtr hwndOwner, 
                StringBuilder lpszPath,
                ShellSpecialFolders nFolder,
    bool fCreate);

    ShellSpecialFolders
    public enum ShellSpecialFolders
    {
            DESKTOP = 0x0000,         // <desktop>
            INTERNET = 0x0001,
            PROGRAMS = 0x0002,        // Start Menu\Programs
            CONTROLS = 0x0003,        // My Computer\Control Panel
            PRINTERS = 0x0004,        // My Computer\Printers
            PERSONAL = 0x0005,        // My Documents
            FAVORITES = 0x0006,        // <user name>\Favorites
            STARTUP = 0x0007,        // Start Menu\Programs\Startup
            RECENT = 0x0008,        // <user name>\Recent
            SENDTO = 0x0009,        // <user name>\SendTo
            BITBUCKET = 0x000a,        // <desktop>\Recycle Bin
            STARTMENU = 0x000b,        // <user name>\Start Menu
            MYDOCUMENTS = 0x000c,        // logical "My Documents" desktop icon
            MYMUSIC = 0x000d,        // "My Music" folder
            MYVIDEO = 0x000e,        // "My Videos" folder
            DESKTOPDIRECTORY = 0x0010,        // <user name>\Desktop
            DRIVES = 0x0011,        // My Computer
            NETWORK = 0x0012,        // Network Neighborhood (My Network Places)
            NETHOOD = 0x0013,        // <user name>\nethood
            FONTS = 0x0014,        // windows\fonts
            TEMPLATES = 0x0015,
            COMMON_STARTMENU = 0x0016,        // All Users\Start Menu
            COMMON_PROGRAMS = 0X0017,        // All Users\Start Menu\Programs
            COMMON_STARTUP = 0x0018,        // All Users\Startup
            COMMON_DESKTOPDIRECTORY = 0x0019,        // All Users\Desktop
            APPDATA = 0x001a,        // <user name>\Application Data
            PRINTHOOD = 0x001b,        // <user name>\PrintHood
            LOCAL_APPDATA = 0x001c,        // <user name>\Local Settings\Applicaiton Data (non roaming)
            ALTSTARTUP = 0x001d,        // non localized startup
            COMMON_ALTSTARTUP = 0x001e,        // non localized common startup
            COMMON_FAVORITES = 0x001f,
            INTERNET_CACHE = 0x0020,
            COOKIES = 0x0021,
            HISTORY = 0x0022,
            COMMON_APPDATA = 0x0023,        // All Users\Application Data
            WINDOWS = 0x0024,        // GetWindowsDirectory()
            SYSTEM = 0x0025,        // GetSystemDirectory()
            PROGRAM_FILES = 0x0026,        // C:\Program Files
            MYPICTURES = 0x0027,        // C:\Program Files\My Pictures
            PROFILE = 0x0028,        // USERPROFILE
            SYSTEMX86 = 0x0029,        // x86 system directory on RISC
            PROGRAM_FILESX86 = 0x002a,        // x86 C:\Program Files on RISC
            PROGRAM_FILES_COMMON = 0x002b,        // C:\Program Files\Common
            PROGRAM_FILES_COMMONX86 = 0x002c,        // x86 Program Files\Common on RISC
            COMMON_TEMPLATES = 0x002d,        // All Users\Templates
            COMMON_DOCUMENTS = 0x002e,        // All Users\Documents
            COMMON_ADMINTOOLS = 0x002f,        // All Users\Start Menu\Programs\Administrative Tools
            ADMINTOOLS = 0x0030,        // <user name>\Start Menu\Programs\Administrative Tools
            CONNECTIONS = 0x0031,        // Network and Dial-up Connections
            COMMON_MUSIC = 0x0035,        // All Users\My Music
            COMMON_PICTURES = 0x0036,        // All Users\My Pictures
            COMMON_VIDEO = 0x0037,        // All Users\My Video
            RESOURCES = 0x0038,        // Resource Direcotry
            RESOURCES_LOCALIZED = 0x0039,        // Localized Resource Direcotry
            COMMON_OLINKS = 0x003a,        // Links to All Users OEM specific apps
            CDBURN_AREA = 0x003b,        // USERPROFILE\Local Settings\Application Data\Microsoft\CD Burning
            COMPUTERSNEARME = 0x003d,        // Computers Near Me (computered from Workgroup membership)
            FLAG_CREATE = 0x8000,        // combine with  value to force folder creation in SHGetFolderPath()
            FLAG_DONT_VERIFY = 0x4000,        // combine with  value to return an unverified folder path
            FLAG_NO_ALIAS = 0x1000,        // combine with  value to insure non-alias versions of the pidl
            FLAG_PER_USER_INIT = 0x0800,        // combine with  value to indicate per-user init (eg. upgrade)
            FLAG_MASK = 0xFF00,        // mask for all possible flag values
        }

    /**//// <summary>
    /// 获取特殊文件夹的路径
    /// </summary>
    public static string GetSpecialFolderPath(IntPtr hwnd, ShellSpecialFolders nFolder)
    {
                StringBuilder sb = new StringBuilder(MAX_PATH);
                SHGetSpecialFolderPath(hwnd, sb, nFolder, false);
    return sb.ToString();
            }

    上下文菜单
    对象的上下文菜单相关的接口是IContextMenu,通过对象的父文件夹的IShellFolder.GetUIObjectOf方法可得到该接口。得到该接口后,可以用IContextMenu.QueryContextMenu方法来生成上下文菜单的菜单项,用IContextMenu.InvokeCommand调用相应的命令。
    好,让我们一步一步来实现 IShellFolder 对象的上下文菜单弹出。
    首先假设我们已经获得某个 IShellFolder 对象的 PIDL 和其上级 IShellFolder 对象:

    IntPtr PIDL;
    IShellFolder IParent;

    然后我们定义一个存放 PIDL 的数组:

    IntPtr[] pidls = new IntPtr[1];
    pidls[0] = PIDL;

    没错,我们的确要用到 PIDL 数组。可以理解,你在资源管理器中选择了多个文件/文件夹,再点击右键,弹出的上下文菜单将有所不同。你可以根据需要,把同一级的多个 PIDL 放到数组里面,实现这个效果。由于我们在例2的树中弹出菜单,所以只存放一个节点的 PIDL。
    IContextMenu 是一个接口,我们这样定义:

    IContextMenu.cs
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;

    namespace WinShell
    {
        [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), GuidAttribute("000214e4-0000-0000-c000-000000000046")]
    public interface IContextMenu
    {
            [PreserveSig()]
            Int32 QueryContextMenu(
                IntPtr hmenu,
    uint iMenu,
    uint idCmdFirst,
    uint idCmdLast,
                CMF uFlags);

            [PreserveSig()]
            Int32 InvokeCommand(
    ref CMINVOKECOMMANDINFOEX info);

            [PreserveSig()]
    void GetCommandString(
    int idcmd,
                GetCommandStringInformations uflags,
    int reserved,
                StringBuilder commandstring,
    int cch);
        }
    }

    然后,通过 IParent 的 GetUIObjectOf 方法我们可以得到该节点的一个或多个指定子节点的 IContextMenu 接口:

    GetUIObjectOf
    IntPtr GetUIObjectOf(
                IntPtr hwndOwner,
    uint cidl,
                [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl,
                [In()] ref Guid riid,
    out IntPtr rgfReserved);

    //得到 IContextMenu 接口
                        IntPtr iContextMenuPtr = IntPtr.Zero;
                        iContextMenuPtr = IParent.GetUIObjectOf(IntPtr.Zero, (uint)pidls.Length, 
                            pidls, ref Guids.IID_IContextMenu, out iContextMenuPtr);
                        IContextMenu iContextMenu = (IContextMenu)Marshal.GetObjectForIUnknown(iContextMenuPtr);

    得到 IContextMenu 后我们需要提供一个弹出式菜单的句柄,并把他传给 IContextMenu.QueryContextMenu,如果该方法执行成功的话,会在我们的菜单里加入相应的菜单项。

    //提供一个弹出式菜单的句柄
    IntPtr contextMenu = API.CreatePopupMenu();
    iContextMenu.QueryContextMenu(contextMenu, 0,
    API.CMD_FIRST, API.CMD_LAST, CMF.NORMAL | CMF.EXPLORE);

    有了菜单项,我们就可以弹出该菜单了,我们用 TPM_RETURNCMD 标志指定 TrackPopupMenu 必须返回用户所选菜单项的 ID,以便稍后通过IContextMenu.InvokeCommand 来执行菜单命令:

    //弹出菜单
    uint cmd = API.TrackPopupMenuEx(contextMenu,TPM.RETURNCMD,
    MousePosition.X, MousePosition.Y, this.Handle, IntPtr.Zero);

    //获取命令序号,执行菜单命令
    if (cmd >= API.CMD_FIRST)
    {
        CMINVOKECOMMANDINFOEX invoke = new CMINVOKECOMMANDINFOEX();
        invoke.cbSize = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFOEX));
        invoke.lpVerb = (IntPtr)(cmd - 1);
        invoke.lpDirectory = string.Empty;
        invoke.fMask = 0;
        invoke.ptInvoke = new POINT(MousePosition.X, MousePosition.Y);
        invoke.nShow = 1;
        iContextMenu.InvokeCommand(ref invoke);
    }

    惯例附上图片和源代码:

    源代码:/Files/lemony/WinShell3.rar

  • 相关阅读:
    25、Base64
    24、AES RSA加密处理记录
    23、获取app所占据的内存
    22、DDMS(转载)
    21、HierarchyViewer使用记录
    kubernetes 操作 serviceaccounts
    删除dashboard
    数据库建库指定UTF-8和jdbc连接字符串
    apt 被卸载
    EasyReport报表工具部署
  • 原文地址:https://www.cnblogs.com/MaxWoods/p/1764039.html
Copyright © 2020-2023  润新知