Win32 context menu items issues

123244 100 Reputation points
2025-01-28T15:52:31.3966667+00:00

Hello everyone, could you please tell me what can go wrong with getting context menu items via win32?

First of all, do I understand correctly that the ampersand in the name is normal? But at the same time, some elements have a localized dwTypeData, some do not (In this case "Include in library" is localized if I run app at other machine)

Secondly, I tried to get the context menu information for the folder on my PC, just like in windows, there is an option “Include in library”. But it doesn't work in my application like it does in Windows. “Include in library” has only 1 submenu item available. Moreover, on Windows 11, the next time I request it, I get a normal menu, but on Windows 10, I don't.

Incorrect library

Thirdly, why does this message appear when I execute the menu of the item that sends the folder to the library? I just pass id of menu item and invoke it using context menu object
message

Code sample:

                var pici = new Shell32.CMINVOKECOMMANDINFOEX
                {
                    lpVerb = Macros.MAKEINTRESOURCE(item.Id),
                    nShow = ShowWindowCommand.SW_NORMAL
                };
                pici.cbSize = (uint)Marshal.SizeOf(pici);
                // HRESULT here is successful
                var hr = contextMenu?.InvokeCommand(pici);

Sorry for the large number of questions, if you need more detailed code samples, I can provide them.

Update: Here is a code sample of getting context menu items information:

var itemCount = User32.GetMenuItemCount(hMenu);
var menuItemInfo = new User32.MENUITEMINFO()
{
fMask =
   User32.MenuItemInfoMask.MIIM_BITMAP |
   User32.MenuItemInfoMask.MIIM_FTYPE |
   User32.MenuItemInfoMask.MIIM_STRING |
   User32.MenuItemInfoMask.MIIM_ID |
   User32.MenuItemInfoMask.MIIM_SUBMENU,
};
menuItemInfo.cbSize = (uint)Marshal.SizeOf(menuItemInfo);

for (uint index = 0; index < itemCount; index++)
{
    using var container = new SafeCoTaskMemString(512);
    var cMenu2 = shellMenu as Shell32.IContextMenu2;
    menuItemInfo.dwTypeData = (IntPtr)container;
    menuItemInfo.cch = (uint)container.Capacity - 1;
    var result = User32.GetMenuItemInfo(hMenu, index, true, ref menuItemInfo);
    if (!result || string.IsNullOrEmpty(container))
    {
        container.Dispose();
        continue;
    }
    var createItem = new Win32ContextMenuItem();
    if (menuItemInfo.fType == User32.MenuItemType.MFT_STRING)
    {
        createItem.Id = (int)(menuItemInfo.wID - 1);
        createItem.Name = 	            		 
		RemoveMenuAmpersands(Marshal.PtrToStringUni((IntPtr)menuItemInfo.dwTypeData));
    }
    if (menuItemInfo.hSubMenu != HMENU.NULL)
    {
        cMenu2?.HandleMenuMsg((uint)User32.WindowMessage.WM_INITMENUPOPUP,
 			(IntPtr)menuItemInfo.hSubMenu, new IntPtr(index));

		createItem.SubItems = 
            EnumerateMenuItems(shellMenu, menuItemInfo.hSubMenu).ToArray();
    }
}

Update 2: Thank you all for trying to solve this. Here's a screenshot of the failed “Include in library” menu, this is the result I always get on windows 10, and on windows 11 only on my I first call the win32 API (other are just fine). I'd like to clarify that I'm not using the information about the menu items from win32 to run the menu itself through the API, but rather to create my own menu based on it. That's why I get the menu metadata for the file -> pass it to the modules above -> they make their own menu from MenuFlyout (Win UI element) based on it

User's image

I'm not an expert in win32, so the cause of the problem could be an incorrect call to api.

On windows 11, after 2 calls to the win32 api for menu data, I get them correctly (the result is on the screenshot)
User's image

Realization: project .NET version 8.
using package Vanara.Windows.Shell (link: https://www.nuget.org/packages/Vanara.Windows.Shell#dependencies-body-tab) for a better and more structured usage of win32 imports

Windows 10
Windows 10
A Microsoft operating system that runs on personal computers and tablets.
12,020 questions
Windows App SDK
Windows App SDK
A set of Microsoft open-source libraries, frameworks, components, and tools to be used in apps to access Windows platform functionality on many versions of Windows. Previously known as Project Reunion.
815 questions
Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,717 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,261 questions
Windows 11
Windows 11
A Microsoft operating system designed for productivity, creativity, and ease of use.
10,600 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Castorix31 86,516 Reputation points
    2025-01-30T17:23:22.6533333+00:00

    This test seems to work correctly on my Windows 10 OS (22H2) , Windows App SDK 1.6.241114003,

    click on a Button, with a "E:\Test\Temp" folder :

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.WindowsRuntime;
    using System.Text;
    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Controls.Primitives;
    using Microsoft.UI.Xaml.Data;
    using Microsoft.UI.Xaml.Input;
    using Microsoft.UI.Xaml.Media;
    using Microsoft.UI.Xaml.Navigation;
    using Windows.Foundation;
    using Windows.Foundation.Collections;
    
    
    // To learn more about WinUI, the WinUI project structure,
    // and more about our project templates, see: http://aka.ms/winui-project-info.
    
    namespace WinUI3_IContextMenu2
    {
        /// <summary>
        /// An empty window that can be used on its own or navigated to within a Frame.
        /// </summary>
        public sealed partial class MainWindow : Window
        {
            public enum HRESULT : int
            {
                S_OK = 0,
                S_FALSE = 1,
                E_NOINTERFACE = unchecked((int)0x80004002),
                E_NOTIMPL = unchecked((int)0x80004001),
                E_FAIL = unchecked((int)0x80004005),
                E_UNEXPECTED = unchecked((int)0x8000FFFF),
                E_OUTOFMEMORY = unchecked((int)0x8007000E)
            }
    
            [DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern IntPtr ILCreateFromPath([MarshalAs(UnmanagedType.LPWStr)] string pszPath);
    
            [DllImport("Shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern HRESULT SHBindToParent(IntPtr pidl, ref Guid riid, ref IShellFolder ppv, ref IntPtr ppidlLast);
    
    
            [ComImport]
            [Guid("000214E6-0000-0000-C000-000000000046")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IShellFolder
            {
                HRESULT ParseDisplayName(IntPtr hwnd,
                    // IBindCtx pbc,
                    IntPtr pbc,
                    [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, [In, Out] ref uint pchEaten, out IntPtr ppidl, [In, Out] ref SFGAO pdwAttributes);
                HRESULT EnumObjects(IntPtr hwnd, SHCONTF grfFlags, out IEnumIDList ppenumIDList);
                [PreserveSig()]
                HRESULT BindToObject(IntPtr pidl,
                    //IBindCtx pbc,
                    IntPtr pbc,
                    [In] ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppv);
                HRESULT BindToStorage(IntPtr pidl, IntPtr pbc, [In] ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppv);
                HRESULT CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2);
                //HRESULT CreateViewObject(IntPtr hwndOwner, [In] ref Guid riid, out IntPtr ppv);
                HRESULT CreateViewObject(IntPtr hwndOwner, [In] ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppv);
                HRESULT GetAttributesOf(uint cidl, IntPtr apidl, [In, Out] ref SFGAO rgfInOut);
                HRESULT GetUIObjectOf(IntPtr hwndOwner, uint cidl, ref IntPtr apidl, [In] ref Guid riid, [In, Out] ref uint rgfReserved, out IntPtr ppv);
                HRESULT GetDisplayNameOf(IntPtr pidl, SHGDNF uFlags, out STRRET pName);
                HRESULT SetNameOf(IntPtr hwnd, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string pszName, SHGDNF uFlags, out IntPtr ppidlOut);
            }
    
            [ComImport]
            [Guid("000214F2-0000-0000-C000-000000000046")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IEnumIDList
            {
                [PreserveSig()]
                HRESULT Next(uint celt, out IntPtr rgelt, out int pceltFetched);
                [PreserveSig()]
                HRESULT Skip(uint celt);
                void Reset();
                [return: MarshalAs(UnmanagedType.Interface)]
                IEnumIDList Clone();
            }
    
            [Flags]
            public enum SHCONTF : ushort
            {
                SHCONTF_CHECKING_FOR_CHILDREN = 0x0010,
                SHCONTF_FOLDERS = 0x0020,
                SHCONTF_NONFOLDERS = 0x0040,
                SHCONTF_INCLUDEHIDDEN = 0x0080,
                SHCONTF_INIT_ON_FIRST_NEXT = 0x0100,
                SHCONTF_NETPRINTERSRCH = 0x0200,
                SHCONTF_SHAREABLE = 0x0400,
                SHCONTF_STORAGE = 0x0800,
                SHCONTF_NAVIGATION_ENUM = 0x1000,
                SHCONTF_FASTITEMS = 0x2000,
                SHCONTF_FLATLIST = 0x4000,
                SHCONTF_ENABLE_ASYNC = 0x8000
            }
    
            [Flags]
            public enum SFGAO : uint
            {
                CANCOPY = 0x00000001,
                CANMOVE = 0x00000002,
                CANLINK = 0x00000004,
                STORAGE = 0x00000008,
                CANRENAME = 0x00000010,
                CANDELETE = 0x00000020,
                HASPROPSHEET = 0x00000040,
                DROPTARGET = 0x00000100,
                CAPABILITYMASK = 0x00000177,
                ENCRYPTED = 0x00002000,
                ISSLOW = 0x00004000,
                GHOSTED = 0x00008000,
                LINK = 0x00010000,
                SHARE = 0x00020000,
                READONLY = 0x00040000,
                HIDDEN = 0x00080000,
                DISPLAYATTRMASK = 0x000FC000,
                STREAM = 0x00400000,
                STORAGEANCESTOR = 0x00800000,
                VALIDATE = 0x01000000,
                REMOVABLE = 0x02000000,
                COMPRESSED = 0x04000000,
                BROWSABLE = 0x08000000,
                FILESYSANCESTOR = 0x10000000,
                FOLDER = 0x20000000,
                FILESYSTEM = 0x40000000,
                HASSUBFOLDER = 0x80000000,
                CONTENTSMASK = 0x80000000,
                STORAGECAPMASK = 0x70C50008,
                PKEYSFGAOMASK = 0x81044000
            }
    
            public enum SHGDNF
            {
                SHGDN_NORMAL = 0,
                SHGDN_INFOLDER = 0x1,
                SHGDN_FOREDITING = 0x1000,
                SHGDN_FORADDRESSBAR = 0x4000,
                SHGDN_FORPARSING = 0x8000
            }
            [StructLayout(LayoutKind.Explicit, Size = 264)]
            public struct STRRET
            {
                [FieldOffset(0)]
                public uint uType;
                [FieldOffset(4)]
                public IntPtr pOleStr;
                [FieldOffset(4)]
                public uint uOffset;
                [FieldOffset(4)]
                public IntPtr cStr;
            }
    
            [ComImport]
            [Guid("000214e4-0000-0000-c000-000000000046")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IContextMenu
            {
                HRESULT QueryContextMenu(IntPtr hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
                [PreserveSig()]
                HRESULT InvokeCommand(ref CMINVOKECOMMANDINFO pici);
    
                [PreserveSig()]
                HRESULT GetCommandString(uint idCmd, uint uType, IntPtr pReserved, StringBuilder pszName, uint cchMax);
            }
    
            [ComImport]
            [Guid("000214f4-0000-0000-c000-000000000046")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IContextMenu2 : IContextMenu
            {
                new HRESULT QueryContextMenu(IntPtr hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
                [PreserveSig()]
                new HRESULT InvokeCommand(ref CMINVOKECOMMANDINFO pici);
    
                [PreserveSig()]
                new HRESULT GetCommandString(uint idCmd, uint uType, IntPtr pReserved, StringBuilder pszName, uint cchMax);
    
                [PreserveSig()]
                HRESULT HandleMenuMsg(uint uMsg, IntPtr wParam, IntPtr lParam);
            }
    
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            public struct CMINVOKECOMMANDINFO
            {
                public int cbSize;
                public int fMask;
                public IntPtr hwnd;
                public IntPtr lpVerb;
                public IntPtr lpParameters;
                public IntPtr lpDirectory;
                public int nShow;
                public int dwHotKey;
                public IntPtr hIcon;
            }
    
            public const int CMF_NORMAL = 0x00000000;
            public const int CMF_DEFAULTONLY = 0x00000001;
            public const int CMF_VERBSONLY = 0x00000002;
            public const int CMF_EXPLORE = 0x00000004;
            public const int CMF_NOVERBS = 0x00000008;
            public const int CMF_CANRENAME = 0x00000010;
            public const int CMF_NODEFAULT = 0x00000020;
            public const int CMF_INCLUDESTATIC = 0x00000040;
            public const int CMF_ITEMMENU = 0x00000080;
            public const int CMF_EXTENDEDVERBS = 0x00000100;
            public const int CMF_DISABLEDVERBS = 0x00000200;
            public const int CMF_ASYNCVERBSTATE = 0x00000400;
            public const int CMF_OPTIMIZEFORINVOKE = 0x00000800;
            public const int CMF_SYNCCASCADEMENU = 0x00001000;
            public const int CMF_DONOTPICKDEFAULT = 0x00002000;
            public const int CMF_RESERVED = unchecked((int)0xffff0000);
    
            public const int SW_SHOWNORMAL = 1;
    
            public const int GCS_VERBA = 0x00000000;     // canonical verb
            public const int GCS_HELPTEXTA = 0x00000001;     // help text (for status bar)
            public const int GCS_VALIDATEA = 0x00000002;     // validate command exists
            public const int GCS_VERBW = 0x00000004;     // canonical verb (unicode)
            public const int GCS_HELPTEXTW = 0x00000005;     // help text (unicode version)
            public const int GCS_VALIDATEW = 0x00000006;     // validate command exists (unicode)
            public const int GCS_VERBICONW = 0x00000014;     // icon string (unicode)
            public const int GCS_UNICODE = 0x00000004;     // for bit testing - Unicode string
    
            [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern IntPtr CreatePopupMenu();
    
            [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern uint TrackPopupMenu(IntPtr hMenu, uint uFlags, int x, int y, int nReserved, IntPtr hWnd, IntPtr prcRect);
    
            public const int TPM_LEFTBUTTON = 0x0000;
            public const int TPM_RIGHTBUTTON = 0x0002;
            public const int TPM_LEFTALIGN = 0x0000;
            public const int TPM_CENTERALIGN = 0x0004;
            public const int TPM_RIGHTALIGN = 0x0008;
            public const int TPM_TOPALIGN = 0x0000;
            public const int TPM_VCENTERALIGN = 0x0010;
            public const int TPM_BOTTOMALIGN = 0x0020;
            public const int TPM_HORIZONTAL = 0x0000;     /* Horz alignment matters more */
            public const int TPM_VERTICAL = 0x0040;     /* Vert alignment matters more */
            public const int TPM_NONOTIFY = 0x0080;     /* Don't send any notification msgs */
            public const int TPM_RETURNCMD = 0x0100;
            public const int TPM_RECURSE = 0x0001;
            public const int TPM_HORPOSANIMATION = 0x0400;
            public const int TPM_HORNEGANIMATION = 0x0800;
            public const int TPM_VERPOSANIMATION = 0x1000;
            public const int TPM_VERNEGANIMATION = 0x2000;
            public const int TPM_NOANIMATION = 0x4000;
            public const int TPM_LAYOUTRTL = 0x8000;
            public const int TPM_WORKAREA = 0x10000;
    
            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            public static extern bool GetCursorPos(ref POINT lpPoint);
    
            [StructLayout(LayoutKind.Sequential)]
            public struct POINT
            {
                public int x;
                public int y;
    
                public POINT(int x, int y)
                {
                    this.x = x;
                    this.y = y;
                }
            }
    
    
            public delegate int SUBCLASSPROC(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, uint dwRefData);
    
            [DllImport("Comctl32.dll", SetLastError = true)]
            public static extern bool SetWindowSubclass(IntPtr hWnd, SUBCLASSPROC pfnSubclass, uint uIdSubclass, uint dwRefData);
    
            [DllImport("Comctl32.dll", SetLastError = true)]
            public static extern int DefSubclassProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
    
            private SUBCLASSPROC SubClassDelegate;
    
            public const int WM_INITMENUPOPUP = 0x0117;
    
            [DllImport("Ole32.dll", SetLastError = true)]
            public static extern HRESULT OleInitialize(IntPtr pvReserved);
    
            private IntPtr hWndMain = IntPtr.Zero;
            IContextMenu2? m_pContextMenu2 = null;
    
            public MainWindow()
            {
                this.InitializeComponent();
                hWndMain = WinRT.Interop.WindowNative.GetWindowHandle(this);
    
                // For some submenus
                SubClassDelegate = new SUBCLASSPROC(WindowSubClass);
                bool bRet = SetWindowSubclass(hWndMain, SubClassDelegate, 0, 0);
    
                // For copy/cut
                OleInitialize(IntPtr.Zero);
            }
    
            private int WindowSubClass(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, uint dwRefData)
            {
                switch (uMsg)
                {
                    case WM_INITMENUPOPUP:
                        {
                            if (m_pContextMenu2 != null)
                                m_pContextMenu2.HandleMenuMsg(uMsg, wParam, lParam);
                            return 0;
                        }
                        break;            
                }
                return DefSubclassProc(hWnd, uMsg, wParam, lParam);
            }
    
            private void myButton_Click(object sender, RoutedEventArgs e)
            {
                //myButton.Content = "Clicked";
    
                IntPtr pItemIDL = ILCreateFromPath("E:\\Test\\Temp");
                if (pItemIDL != IntPtr.Zero)
                {
                    IntPtr pcm = IntPtr.Zero;
                    Guid IID_IContextMenu = new Guid("000214E4-0000-0000-C000-000000000046");
                    HRESULT hr = SHGetUIObjectFromFullPIDL(pItemIDL, IntPtr.Zero, ref IID_IContextMenu, ref pcm);
                    if (hr == HRESULT.S_OK)
                    {
                        IContextMenu? pContextMenu = Marshal.GetObjectForIUnknown(pcm) as IContextMenu;
                        IntPtr hMenu = CreatePopupMenu();
                        hr = pContextMenu.QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_EXPLORE);
                        if (hr == HRESULT.S_OK)
                        {
                            m_pContextMenu2 = (IContextMenu2)pContextMenu;
                            POINT pt = new POINT();
                            GetCursorPos(ref pt);
                            int nX = pt.x, nY = pt.y;
                            uint nCmd = TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, nX, nY, 0, hWndMain, IntPtr.Zero);
                            if (nCmd != 0)
                            {
                                CMINVOKECOMMANDINFO cmi = new CMINVOKECOMMANDINFO();
                                cmi.cbSize = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO));
                                cmi.fMask = 0;
                                cmi.hwnd = hWndMain;
                                cmi.lpVerb = (IntPtr)(nCmd - 1);
                                cmi.lpParameters = IntPtr.Zero;
                                cmi.lpDirectory = IntPtr.Zero;
                                cmi.nShow = SW_SHOWNORMAL;
                                cmi.dwHotKey = 0;
                                cmi.hIcon = IntPtr.Zero; ;
                                hr = pContextMenu.InvokeCommand(ref cmi);
                            }
                        }
                    }
                }
            }
    
            // From MSDN : https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shbrowseforfolderw
            private HRESULT SHGetUIObjectFromFullPIDL(IntPtr pidl, IntPtr hwnd, ref Guid riid, ref IntPtr ppv)
            {
                IntPtr pidlChild = IntPtr.Zero;
                IShellFolder? psf = null;
                ppv = IntPtr.Zero;
                Guid IID_IShellFolder = new Guid("000214E6-0000-0000-C000-000000000046");
                HRESULT hr = SHBindToParent(pidl, ref IID_IShellFolder, ref psf, ref pidlChild);
                if (hr == HRESULT.S_OK)
                {
                    uint rgfReserved = 0;
                    hr = psf.GetUIObjectOf(hwnd, 1, ref pidlChild, riid, ref rgfReserved, out ppv);
                    Marshal.ReleaseComObject(psf);
                }
                return hr;
            }
        }
    }
    
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.