原文 (C#)Windows Shell 编程系列3 - 上下文菜单(iContextMenu)(一)右键菜单
接上一节:(C#)Windows Shell 编程系列2 - 解释,从“桌面”开始展开
这里解释上一节中获取名称的方法 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没有设定,就返回外壳对象的显示名称。
具体实现方法:

);

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();

}


public enum SHGNO

{

NORMAL = 0x0,

INFOLDER = 0x1,

FOREDITING = 0x1000,

FORADDRESSBAR = 0x4000,

FORPARSING = 0x8000,

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

;

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



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 的数组:

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


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 接口:


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,如果该方法执行成功的话,会在我们的菜单里加入相应的菜单项。

,

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

;

iContextMenu.InvokeCommand(ref invoke);

}
惯例附上图片和源代码:

源代码:/Files/lemony/WinShell3.rar
下一节深入讲述 iContextMenu,让我们可以插入自己的菜单,或者直接调用菜单命令。