vba - 使用 FolderItemVerbs 并调用或 DoIt 从 VBA 执行 windows shell 中的子菜单项
问题描述
下面的代码显示了一种获取文件夹中所有文件的所有动词(上下文菜单或右键单击菜单中可用的命令)的方法。示例:如果我想通过使用 VBA 中的右键菜单“编辑”来调用文件的“编辑”命令,则下面的代码可以正常工作。我可以简单地列出文件(这是一个文件夹项)可用的所有动词,然后如果它是命令编辑则调用它。到目前为止,一切都很好。
但是,如果我想调用一个子菜单(例如“使用/写字板打开”),那么该子菜单的动词就是空白。下面的例程将打印“子菜单”以表明我们可以知道有一个子菜单,但我不知道该子菜单中有什么可用的命令,也不能调用它们。
有人知道该怎么做吗?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 Objects非常有用(并且为许多开发人员所熟知......),包括 .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:\mypath\myfile.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:\mypath\myfile.txt", item => item.Verb == "properties");
}
}
请注意,它如何工作(或不工作)很大程度上取决于许多上下文参数,例如进程位数(32 位或 64 位)、进程类型(控制台与 Windows)或当前线程的 COM 单元状态(STA 与 MTA 等) .)。它还取决于动态上下文菜单处理程序如何选择添加或不添加菜单项。
例如,如果您知道 Notepad++,则“使用 Notepad++ 编辑”条目仅在控制台模式下列出,因此只能从控制台应用程序调用。这是一个例外,因为大多数标准 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('\0', (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,
}
推荐阅读
- python - 如何使用 dtype 预定义数据框的 dtypes
- google-sheets - 使用两列中的值返回新列中的特定值
- scheme - 尝试用新元素替换列表中元素的所有实例 [Racket]
- python - 如何在类的方法中正确运行方法(python)
- jasmine - 量角器 - 无法使用 ElemArrayFinder 循环每个项目
- java - 如何使用 --add-opens 参数来摆脱非法反射访问警告?
- python - 从作业处理器启动线程时,如何跟踪作业工作线程的进度?
- r - Loop character values in ggtitle
- javascript - 如何将身份验证标头传递给其他组件
- android - 错误:程序类型已存在:com.google.android.gms.common.internal.zzb