使用FolderItemVerbs和invoke或DoIt从VBA执行windows shell中的子菜单项



下面的代码显示了一种获取文件夹中所有文件的所有谓词(上下文菜单或右键菜单中可用的命令(的方法。示例:如果我想通过在VBA中使用右键菜单"编辑"来调用文件的"编辑"命令,下面的代码可以正常工作。我可以简单地列出文件(它是一个文件夹(的所有可用谓词,然后如果是命令edit,则调用它。到目前为止还不错。

但是,如果我想调用一个子菜单(比如"用/Wordpad打开"(,那么该子菜单的动词就是空白的。下面的例程将打印"子菜单",表示我们可以判断有一个子菜单,但我无法判断该子菜单中有哪些可用命令,也无法调用它们。

有人知道怎么做吗?FolderItemVerbs接口似乎只捕获上下文菜单的第一级,而不是任何子菜单。

Dim MySh As Object
Dim objFolder2 As Folder2
Set MySh = CreateObject("Shell.Application")
Set objFolder2 = MySh.Namespace(follow_direc)
If (Not objFolder2 Is Nothing) Then
Dim objFolderItem As FolderItem
Dim objItemVerbs As FolderItemVerbs
Dim objVerb As FolderItemVerb
For Each objFolderItem In objFolder2.Items
If (Not objFolderItem Is Nothing) Then
Debug.Print objFolderItem.Verbs.Count
Set objItemVerbs = objFolderItem.Verbs
If (Not objItemVerbs Is Nothing) Then
For Each objVerb In objItemVerbs
If Len(objVerb.Name) > 1 Then
Debug.Print objVerb.Name
Else
Debug.Print "submenu"
End If
Next
End If
Set objItemVerbs = Nothing
Else
'FolderItem object returned nothing.
End If
Next
Set objFolderItem = objFolder2.Self
Set objFolderItem = Nothing
Else
'Folder object returned nothing.
End If
Set objFolder2 = Nothing
Set MySh = Nothing

虽然Scriptable Shell对象非常有用(许多开发人员都知道…(,包括.NET,但它们并不完全支持上下文项和子项(看起来微软已经对这个COM实用程序失去兴趣很长一段时间了(。

因此,这里有一个.NET类(抱歉,它是C#,但您应该能够将它转换为VB.NET(,它对这些有更好的支持。

以下是如何在控制台应用程序中列出给定文件的菜单项层次结构:

class Program
{
// [STAThread] things can vary with that set or not...
static void Main(string[] args)
{
foreach (var item in ShellMenuItem.ExtractMenu(@"c:mypathmyfile.txt"))
{
Dump(0, item);
}
}
static void Dump(int indent, ShellMenuItem item)
{
var s = new string(' ', indent);
if (item.IsSeparator)
{
Console.WriteLine("-");
return;
}
Console.WriteLine(s + item.Text);
Console.WriteLine(s + " id:" + item.Id);
Console.WriteLine(s + " state:" + item.State);
Console.WriteLine(s + " type:" + item.Type);
Console.WriteLine(s + " verb:" + item.Verb);
foreach (var child in item.Items)
{
Dump(indent + 1, child);
Console.WriteLine();
}
if (item.Items.Count == 0)
{
Console.WriteLine();
}
}
}

以下是如何从Windows窗体应用程序调用文件上的"属性"菜单项:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ShellMenuItem.InvokeMenuItem(@"c:mypathmyfile.txt", item => item.Verb == "properties");
}
}

请注意,这在很大程度上取决于许多上下文参数,如进程位(32或64位(、进程类型(控制台与Windows(或当前线程的COM单元状态(STA与MTA等(。它还取决于动态上下文菜单处理程序选择如何添加或不添加菜单项。

例如,如果您知道Notepad++,则"使用Notepad++编辑"条目仅在Console模式下列出,因此只能从Console应用程序调用。这是一个例外,因为大多数标准的Shell菜单项(如"属性"(只能在Windowed应用程序中工作。

public sealed class ShellMenuItem
{
private List<ShellMenuItem> _items = new List<ShellMenuItem>();
private ShellMenuItem()
{
}
public int Id { get; private set; }
public string Text { get; private set; }
public string Verb { get; private set; }
public MFS State { get; private set; }
public MFT Type { get; private set; }
public bool IsSeparator => Type.HasFlag(MFT.MFT_SEPARATOR);
public IReadOnlyList<ShellMenuItem> Items => _items;
public override string ToString() => IsSeparator ? "-" : Text;
public static IReadOnlyList<ShellMenuItem> ExtractMenu(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
var list = new List<ShellMenuItem>();
ExtractMenu(path, (parent, item) =>
{
if (parent == null)
{
list.Add(item);
}
else
{
parent._items.Add(item);
}
});
return list;
}
public static void ExtractMenu(string path, Action<ShellMenuItem, ShellMenuItem> action)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (action == null)
throw new ArgumentNullException(nameof(action));
ExtractMenu(path, (parent, item, cm) => action(parent, item));
}
private static void ExtractMenu(string path, Action<ShellMenuItem, ShellMenuItem, IContextMenu2> action)
{
int hr = SHCreateItemFromParsingName(path, null, typeof(IShellItem).GUID, out var item);
if (hr < 0)
throw new Win32Exception(hr);
var pai = (IParentAndItem)item;
hr = pai.GetParentAndItem(out var folderPidl, out var folder, out var itemPidl);
if (hr < 0)
throw new Win32Exception(hr);
hr = folder.GetUIObjectOf(IntPtr.Zero, 1, new[] { itemPidl }, typeof(IContextMenu).GUID, IntPtr.Zero, out var obj);
if (hr < 0)
throw new Win32Exception(hr);
var menu = CreateMenu();
try
{
var cm = (IContextMenu2)obj;
hr = cm.QueryContextMenu(menu, 0, 0, 0x7FFF, CMF.CMF_NORMAL);
if (hr < 0)
throw new Win32Exception(hr);
ExtractMenu(path, cm, menu, null, action);
}
finally
{
DestroyMenu(menu);
Marshal.FreeCoTaskMem(folderPidl);
Marshal.FreeCoTaskMem(itemPidl);
}
}
public static void InvokeMenuItem(string path, Func<ShellMenuItem, bool> predicate) => InvokeMenuItem(path, IntPtr.Zero, predicate);
public static void InvokeMenuItem(string path, IntPtr hwnd, Func<ShellMenuItem, bool> predicate)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
ExtractMenu(path, (parent, item, cm) =>
{
if (predicate(item))
{
var info = new CMINVOKECOMMANDINFOEX();
info.cbSize = Marshal.SizeOf(info);
info.hwnd = hwnd;
info.lpVerb = new IntPtr(item.Id);
int hr = cm.InvokeCommand(ref info);
if (hr < 0)
throw new Win32Exception(hr);
}
});
}
private static void ExtractMenu(string path, IContextMenu2 cm, IntPtr menuHandle, ShellMenuItem parent,
Action<ShellMenuItem, ShellMenuItem, IContextMenu2> action)
{
int count = GetMenuItemCount(menuHandle);
for (int i = 0; i < count; i++)
{
var mii = new MENUITEMINFO();
mii.cbSize = Marshal.SizeOf(typeof(MENUITEMINFO));
mii.fMask = MIIM.MIIM_FTYPE | MIIM.MIIM_ID | MIIM.MIIM_STATE | MIIM.MIIM_STRING | MIIM.MIIM_SUBMENU | MIIM.MIIM_DATA;
if (!GetMenuItemInfo(menuHandle, i, true, ref mii))
throw new Win32Exception(Marshal.GetLastWin32Error());
if (mii.fType == MFT.MFT_STRING)
{
mii.dwTypeData = new string('', (mii.cch + 1) * 2);
mii.cch++;
if (!GetMenuItemInfo(menuHandle, i, true, ref mii))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var item = new ShellMenuItem();
item.Text = mii.dwTypeData;
item.Id = mii.wID;
item.Type = mii.fType;
item.State = mii.fState;
if (!item.IsSeparator)
{
var sb = new StringBuilder(256);
cm.GetCommandString(new IntPtr(item.Id), GCS_VERBW, IntPtr.Zero, sb, sb.Capacity);
if (!string.IsNullOrWhiteSpace(sb.ToString()))
{
item.Verb = sb.ToString();
}
if (mii.hSubMenu != IntPtr.Zero)
{
ExtractMenu(path, cm, mii.hSubMenu, item, action);
}
}
action(parent, item, cm);
}
}
private const int GCS_VERBW = 4;
[DllImport("shell32", CharSet = CharSet.Unicode)]
private static extern int SHCreateItemFromParsingName(string path, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
[DllImport("user32")]
private static extern IntPtr CreateMenu();
[DllImport("user32")]
private static extern bool DestroyMenu(IntPtr hMenu);
[DllImport("user32")]
private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
[DllImport("user32")]
private static extern int GetMenuItemCount(IntPtr hMenu);
[DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, ref MENUITEMINFO pmii);
[Flags]
private enum MIIM
{
MIIM_STATE = 0x00000001,
MIIM_ID = 0x00000002,
MIIM_SUBMENU = 0x00000004,
MIIM_CHECKMARKS = 0x00000008,
MIIM_TYPE = 0x00000010,
MIIM_DATA = 0x00000020,
MIIM_STRING = 0x00000040,
MIIM_BITMAP = 0x00000080,
MIIM_FTYPE = 0x00000100,
}
[Flags]
private enum CMF
{
CMF_NORMAL = 0x00000000,
CMF_DEFAULTONLY = 0x00000001,
CMF_VERBSONLY = 0x00000002,
CMF_EXPLORE = 0x00000004,
CMF_NOVERBS = 0x00000008,
CMF_CANRENAME = 0x00000010,
CMF_NODEFAULT = 0x00000020,
CMF_INCLUDESTATIC = 0x00000040,
CMF_ITEMMENU = 0x00000080,
CMF_EXTENDEDVERBS = 0x00000100,
CMF_DISABLEDVERBS = 0x00000200,
CMF_ASYNCVERBSTATE = 0x00000400,
CMF_OPTIMIZEFORINVOKE = 0x00000800,
CMF_SYNCCASCADEMENU = 0x00001000,
CMF_DONOTPICKDEFAULT = 0x00002000,
CMF_UNDOCUMENTED1 = 0x00004000,
CMF_DVFILE = 0x10000,
CMF_UNDOCUMENTED2 = 0x20000,
CMF_RESERVED = unchecked((int)0xffff0000)
}
[Flags]
public enum CMIC_MASK
{
CMIC_MASK_ASYNCOK = 0x00100000,
CMIC_MASK_HOTKEY = 0x00000020,
CMIC_MASK_FLAG_NO_UI = 0x00000400,
CMIC_MASK_UNICODE = 0x00004000,
CMIC_MASK_NO_CONSOLE = 0x00008000,
CMIC_MASK_NOASYNC = 0x00000100,
CMIC_MASK_SHIFT_DOWN = 0x10000000,
CMIC_MASK_CONTROL_DOWN = 0x40000000,
CMIC_MASK_FLAG_LOG_USAGE = 0x04000000,
CMIC_MASK_NOZONECHECKS = 0x00800000,
CMIC_MASK_PTINVOKE = 0x20000000,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct MENUITEMINFO
{
public int cbSize;
public MIIM fMask;
public MFT fType;
public MFS fState;
public int wID;
public IntPtr hSubMenu;
public IntPtr hbmpChecked;
public IntPtr hbmpUnchecked;
public IntPtr dwItemData;
public string dwTypeData;
public int cch;
public IntPtr hbmpItem;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct CMINVOKECOMMANDINFOEX
{
public int cbSize;
public CMIC_MASK fMask;
public IntPtr hwnd;
public IntPtr lpVerb;
[MarshalAs(UnmanagedType.LPStr)]
public string lpParameters;
[MarshalAs(UnmanagedType.LPStr)]
public string lpDirectory;
public int nShow;
public int dwHotKey;
public IntPtr hIcon;
[MarshalAs(UnmanagedType.LPStr)]
public string lpTitle;
public IntPtr lpVerbW;
public string lpParametersW;
public string lpDirectoryW;
public string lpTitleW;
public long ptInvoke;
}
[Guid("000214e4-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IContextMenu
{
// we don't need anything from this, all is in IContextMenu2
}
[Guid("000214f4-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IContextMenu2
{
// IContextMenu
[PreserveSig]
int QueryContextMenu(IntPtr hmenu, int indexMenu, int idCmdFirst, int idCmdLast, CMF uFlags);
[PreserveSig]
int InvokeCommand(ref CMINVOKECOMMANDINFOEX pici);
[PreserveSig]
int GetCommandString(IntPtr idCmd, int uType, IntPtr pReserved, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMax);
// IContextMenu2
[PreserveSig]
int HandleMenuMsg(int uMsg, IntPtr wParam, IntPtr lParam);
}
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellItem
{
// we don't need anything from this
}
[Guid("000214e6-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellFolder
{
void _VtblGap1_7(); // skip 7 methods we don't need
[PreserveSig]
int GetUIObjectOf(IntPtr hwndOwner, int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, IntPtr rgfReserved, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
}
[Guid("b3a4b685-b685-4805-99d9-5dead2873236"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IParentAndItem
{
void _VtblGap1_1(); // skip 1 method we don't need
[PreserveSig]
int GetParentAndItem(out IntPtr ppidlParent, out IShellFolder ppsf, out IntPtr ppidlChild);
}
}
[Flags]
public enum MFS
{
MFS_GRAYED = 3,
MFS_CHECKED = 8,
MFS_HILITE = 128,
MFS_ENABLED = 0,
MFS_UNCHECKED = 0,
MFS_UNHILITE = 0,
MFS_DEFAULT = 4096,
}
[Flags]
public enum MFT
{
MFT_STRING = 0,
MFT_BITMAP = 4,
MFT_MENUBARBREAK = 32,
MFT_MENUBREAK = 64,
MFT_RADIOCHECK = 512,
MFT_SEPARATOR = 2048,
MFT_RIGHTORDER = 8192,
MFT_RIGHTJUSTIFY = 16384,
}

最新更新