Использование FolderItemVerbs и вызов или DoIt для выполнения элемента подменю в оболочке Windows из VBA - PullRequest
0 голосов
/ 28 октября 2018

В приведенном ниже коде показан метод получения всех глаголов (команд, доступных в контекстном меню или контекстном меню) для всех файлов в папке.Пример: если я хочу вызвать команду «Редактировать» для файла с помощью контекстного меню «Правка» в 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

1 Ответ

0 голосов
/ 29 октября 2018

Хотя Scriptable Shell Shell Objects очень полезны (и хорошо известны многим разработчикам ...), в том числе с .NET, они не полностью поддерживают элементы контекста и подэлементы (похоже, Microsoftуже давно теряет интерес к этой утилите 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 Forms:

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 бит), тип процесса (консоль)vs Windows), или состояние квартиры COM текущего потока (STA vs MTA и т. д.).Это также зависит от того, как обработчики динамического контекстного меню выбирают добавлять или не добавлять элементы меню.

Например, если вы знаете Notepad ++, запись «Редактировать с помощью Notepad ++» отображается только в режиме консоли и, следовательно, может вызываться толькоиз консольного приложения.Это исключение, так как большинство стандартных пунктов меню оболочки (например, «Свойства») будут работать только из оконных приложений.

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,
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...