What Is the Best Way to Display the Native Windows Explorer Context Menu for Files in VB.NET?

Mansour_Dalir 2,036 Reputation points
2025-03-06T11:39:55.11+00:00

What Is the Best Way to Display the Native Windows Explorer Context Menu for Files in VB.NET?I'm developing a VB.NET application and aiming to replicate the native context menu of Windows Explorer (the menu shown when you right-click a file in Windows Explorer). My objective is to show the exact same menu in my application, including all system-defined options and third-party extensions.

There is a ready-made library for this task that, by giving the file path and the location of the menu, can display the actual menu of that file in the program.

VB
VB
An object-oriented programming language developed by Microsoft that is implemented on the .NET Framework. Previously known as Visual Basic .NET.
2,793 questions
0 comments No comments
{count} votes

Accepted answer
  1. Castorix31 87,706 Reputation points
    2025-03-07T16:24:30.94+00:00

    For example, a test on a Button Click with Notepad :

    Imports System.Runtime.InteropServices
    Imports System.Text
    
    Public Class Form1
        Public Enum HRESULT As Integer
            S_OK = 0
            S_FALSE = 1
            E_NOINTERFACE = &H80004002
            E_NOTIMPL = &H80004001
            E_FAIL = &H80004005
            E_UNEXPECTED = &H8000FFFF
            E_OUTOFMEMORY = &H8007000E
        End Enum
    
        <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function ILCreateFromPath(<MarshalAs(UnmanagedType.LPWStr)> pszPath As String) As IntPtr
        End Function
    
        <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Sub ILFree(pidl As IntPtr)
        End Sub
    
        <ComImport>
        <Guid("000214e4-0000-0000-c000-000000000046")>
        <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
        Interface IContextMenu
            Function QueryContextMenu(hmenu As IntPtr, indexMenu As UInteger, idCmdFirst As UInteger, idCmdLast As UInteger, uFlags As UInteger) As HRESULT
            <PreserveSig()>
            Function InvokeCommand(ByRef pici As CMINVOKECOMMANDINFO) As HRESULT
            <PreserveSig()>
            Function GetCommandString(idCmd As UInteger, uType As UInteger, pReserved As IntPtr, pszName As StringBuilder, cchMax As UInteger) As HRESULT
        End Interface
    
        <ComImport>
        <Guid("000214f4-0000-0000-c000-000000000046")>
        <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
        Interface IContextMenu2
            Inherits IContextMenu
            Overloads Function QueryContextMenu(hmenu As IntPtr, indexMenu As UInteger, idCmdFirst As UInteger, idCmdLast As UInteger, uFlags As UInteger) As HRESULT
            <PreserveSig()>
            Overloads Function InvokeCommand(ByRef pici As CMINVOKECOMMANDINFO) As HRESULT
            <PreserveSig()>
            Overloads Function GetCommandString(idCmd As UInteger, uType As UInteger, pReserved As IntPtr, pszName As StringBuilder, cchMax As UInteger) As HRESULT
            <PreserveSig()>
            Function HandleMenuMsg(uMsg As UInteger, wParam As Integer, lParam As IntPtr) As HRESULT
        End Interface
    
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
        Public Structure CMINVOKECOMMANDINFO
            Public cbSize As Integer
            Public fMask As Integer
            Public hwnd As IntPtr
            Public lpVerb As IntPtr
            Public lpParameters As IntPtr
            Public lpDirectory As IntPtr
            Public nShow As Integer
            Public dwHotKey As Integer
            Public hIcon As IntPtr
        End Structure
    
        Public Const CMF_NORMAL As Integer = &H0
        Public Const CMF_DEFAULTONLY As Integer = &H1
        Public Const CMF_VERBSONLY As Integer = &H2
        Public Const CMF_EXPLORE As Integer = &H4
        Public Const CMF_NOVERBS As Integer = &H8
        Public Const CMF_CANRENAME As Integer = &H10
        Public Const CMF_NODEFAULT As Integer = &H20
        Public Const CMF_INCLUDESTATIC As Integer = &H40
        Public Const CMF_ITEMMENU As Integer = &H80
        Public Const CMF_EXTENDEDVERBS As Integer = &H100
        Public Const CMF_DISABLEDVERBS As Integer = &H200
        Public Const CMF_ASYNCVERBSTATE As Integer = &H400
        Public Const CMF_OPTIMIZEFORINVOKE As Integer = &H800
        Public Const CMF_SYNCCASCADEMENU As Integer = &H1000
        Public Const CMF_DONOTPICKDEFAULT As Integer = &H2000
        Public Const CMF_RESERVED As Integer = &HFFFF0000
        Public Const SW_SHOWNORMAL As Integer = 1
        Public Const GCS_VERBA As Integer = &H0
        Public Const GCS_HELPTEXTA As Integer = &H1
        Public Const GCS_VALIDATEA As Integer = &H2
        Public Const GCS_VERBW As Integer = &H4
        Public Const GCS_HELPTEXTW As Integer = &H5
        Public Const GCS_VALIDATEW As Integer = &H6
        Public Const GCS_VERBICONW As Integer = &H14
        Public Const GCS_UNICODE As Integer = &H4
    
        <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function SHBindToParent(pidl As IntPtr, ByRef riid As Guid, ByRef ppv As IShellFolder, ByRef ppidlLast As IntPtr) As HRESULT
        End Function
    
        <ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214E6-0000-0000-C000-000000000046")>
        Interface IShellFolder
            Function ParseDisplayName(hwnd As IntPtr, pbc As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> pszDisplayName As String, <[In], Out> ByRef pchEaten As UInteger, <Out> ByRef ppidl As IntPtr, <[In], Out> ByRef pdwAttributes As SFGAO) As HRESULT
            Function EnumObjects(hwnd As IntPtr, grfFlags As SHCONTF, <Out> ByRef ppenumIDList As IEnumIDList) As HRESULT
            Function BindToObject(pidl As IntPtr, pbc As IntPtr, <[In]> ByRef riid As Guid, <Out> <MarshalAs(UnmanagedType.[Interface])> ByRef ppv As Object) As HRESULT
            Function BindToStorage(pidl As IntPtr, pbc As IntPtr, <[In]> ByRef riid As Guid, <Out> <MarshalAs(UnmanagedType.[Interface])> ByRef ppv As Object) As HRESULT
            Function CompareIDs(lParam As IntPtr, pidl1 As IntPtr, pidl2 As IntPtr) As HRESULT
            Function CreateViewObject(hwndOwner As IntPtr, <[In]> ByRef riid As Guid, <Out> <MarshalAs(UnmanagedType.[Interface])> ByRef ppv As Object) As HRESULT
            Function GetAttributesOf(cidl As UInteger, apidl As IntPtr, <[In], Out> ByRef rgfInOut As SFGAO) As HRESULT
            Function GetUIObjectOf(hwndOwner As IntPtr, cidl As UInteger, ByRef apidl As IntPtr, <[In]> ByRef riid As Guid, <[In], Out> ByRef rgfReserved As UInteger, <Out> ByRef ppv As IntPtr) As HRESULT
            Function GetDisplayNameOf(pidl As IntPtr, uFlags As SHGDNF, <Out> ByRef pName As STRRET) As HRESULT
            Function SetNameOf(hwnd As IntPtr, pidl As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> pszName As String, uFlags As SHGDNF, <Out> ByRef ppidlOut As IntPtr) As HRESULT
        End Interface
    
        Public Enum SHCONTF
            SHCONTF_CHECKING_FOR_CHILDREN = &H10
            SHCONTF_FOLDERS = &H20
            SHCONTF_NONFOLDERS = &H40
            SHCONTF_INCLUDEHIDDEN = &H80
            SHCONTF_INIT_ON_FIRST_NEXT = &H100
            SHCONTF_NETPRINTERSRCH = &H200
            SHCONTF_SHAREABLE = &H400
            SHCONTF_STORAGE = &H800
            SHCONTF_NAVIGATION_ENUM = &H1000
            SHCONTF_FASTITEMS = &H2000
            SHCONTF_FLATLIST = &H4000
            SHCONTF_ENABLE_ASYNC = &H8000
        End Enum
    
        Public Enum SFGAO
            CANCOPY = &H1
            CANMOVE = &H2
            CANLINK = &H4
            STORAGE = &H8
            CANRENAME = &H10
            CANDELETE = &H20
            HASPROPSHEET = &H40
            DROPTARGET = &H100
            CAPABILITYMASK = &H177
            ENCRYPTED = &H2000
            ISSLOW = &H4000
            GHOSTED = &H8000
            LINK = &H10000
            SHARE = &H20000
            [READONLY] = &H40000
            HIDDEN = &H80000
            DISPLAYATTRMASK = &HFC000
            STREAM = &H400000
            STORAGEANCESTOR = &H800000
            VALIDATE = &H1000000
            REMOVABLE = &H2000000
            COMPRESSED = &H4000000
            BROWSABLE = &H8000000
            FILESYSANCESTOR = &H10000000
            FOLDER = &H20000000
            FILESYSTEM = &H40000000
            HASSUBFOLDER = &H80000000
            CONTENTSMASK = &H80000000
            STORAGECAPMASK = &H70C50008
            PKEYSFGAOMASK = &H81044000
        End Enum
    
        Public Enum SHGDNF
            SHGDN_NORMAL = 0
            SHGDN_INFOLDER = &H1
            SHGDN_FOREDITING = &H1000
            SHGDN_FORADDRESSBAR = &H4000
            SHGDN_FORPARSING = &H8000
        End Enum
    
        <StructLayout(LayoutKind.Explicit, Size:=264)>
        Public Structure STRRET
            <FieldOffset(0)>
            Public uType As UInteger
            <FieldOffset(4)>
            Public pOleStr As IntPtr
            <FieldOffset(4)>
            Public uOffset As UInteger
            <FieldOffset(4)>
            Public cString As IntPtr
        End Structure
    
        <ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F2-0000-0000-C000-000000000046")>
        Interface IEnumIDList
            <PreserveSig()>
            Function [Next](celt As UInteger, <Out> ByRef rgelt As IntPtr, <Out> ByRef pceltFetched As Integer) As HRESULT
            <PreserveSig()>
            Function Skip(celt As UInteger) As HRESULT
            Sub Reset()
            Function Clone() As IEnumIDList
        End Interface
    
        <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function CreatePopupMenu() As IntPtr
        End Function
    
        <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function TrackPopupMenu(hMenu As IntPtr, uFlags As UInteger, x As Integer, y As Integer, nReserved As Integer, hWnd As IntPtr, prcRect As IntPtr) As UInteger
        End Function
    
        <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
        Public Shared Function DestroyMenu(hMenu As IntPtr) As Boolean
        End Function
    
        Public Const TPM_LEFTBUTTON As Integer = &H0
        Public Const TPM_RIGHTBUTTON As Integer = &H2
        Public Const TPM_LEFTALIGN As Integer = &H0
        Public Const TPM_CENTERALIGN As Integer = &H4
        Public Const TPM_RIGHTALIGN As Integer = &H8
        Public Const TPM_TOPALIGN As Integer = &H0
        Public Const TPM_VCENTERALIGN As Integer = &H10
        Public Const TPM_BOTTOMALIGN As Integer = &H20
        Public Const TPM_HORIZONTAL As Integer = &H0
        Public Const TPM_VERTICAL As Integer = &H40
        Public Const TPM_NONOTIFY As Integer = &H80
        Public Const TPM_RETURNCMD As Integer = &H100
        Public Const TPM_RECURSE As Integer = &H1
        Public Const TPM_HORPOSANIMATION As Integer = &H400
        Public Const TPM_HORNEGANIMATION As Integer = &H800
        Public Const TPM_VERPOSANIMATION As Integer = &H1000
        Public Const TPM_VERNEGANIMATION As Integer = &H2000
        Public Const TPM_NOANIMATION As Integer = &H4000
        Public Const TPM_LAYOUTRTL As Integer = &H8000
        Public Const TPM_WORKAREA As Integer = &H10000
    
        Public Const SEE_MASK_ASYNCOK = &H100000
        Public Const CMIC_MASK_ASYNCOK = SEE_MASK_ASYNCOK
    
        Public Const SEE_MASK_UNICODE = &H4000
        Public Const CMIC_MASK_UNICODE = SEE_MASK_UNICODE
    
        Public Const WM_INITMENUPOPUP As Integer = &H117
        Public Const WM_DRAWITEM As Integer = &H2B
        Public Const WM_MEASUREITEM As Integer = &H2C
    
        Private Function SHGetUIObjectFromFullPIDL(pidl As IntPtr, hwnd As IntPtr, ByRef riid As Guid, ByRef ppv As IntPtr) As HRESULT
            Dim pidlChild As IntPtr = IntPtr.Zero
            Dim psf As IShellFolder = Nothing
            ppv = IntPtr.Zero
            Dim hr As HRESULT = SHBindToParent(pidl, GetType(IShellFolder).GUID, psf, pidlChild)
            If hr = HRESULT.S_OK Then
                Dim rgfReserved As UInteger = 0
                hr = psf.GetUIObjectOf(hwnd, 1, pidlChild, riid, rgfReserved, ppv)
                Marshal.ReleaseComObject(psf)
            End If
    
            Return hr
        End Function
    
        Public m_pContextMenu2 As IContextMenu2 = Nothing
    
        Protected Overrides Sub WndProc(ByRef m As Message)
            If m.Msg = WM_INITMENUPOPUP Or m.Msg = WM_DRAWITEM Or m.Msg = WM_MEASUREITEM Then
                If m_pContextMenu2 IsNot Nothing Then
                    If m_pContextMenu2 IsNot Nothing Then m_pContextMenu2.HandleMenuMsg(CUInt(m.Msg), CInt(m.WParam), m.LParam)
                    Return
                End If
            Else
                MyBase.WndProc(m)
            End If
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim sFilePath = "c:\Windows\System32\notepad.exe"
            Dim pItemIDL As IntPtr = ILCreateFromPath(sFilePath)
            If (pItemIDL <> IntPtr.Zero) Then
                Dim pContextMenuPtr As IntPtr = IntPtr.Zero
                Dim hr As HRESULT = SHGetUIObjectFromFullPIDL(pItemIDL, IntPtr.Zero, GetType(IContextMenu).GUID, pContextMenuPtr)
                If (hr = HRESULT.S_OK) Then
                    Dim pContextMenu As IContextMenu = TryCast(Marshal.GetObjectForIUnknown(pContextMenuPtr), IContextMenu)
                    If (pContextMenu IsNot Nothing) Then
                        Dim hMenu As IntPtr = CreatePopupMenu()
                        hr = pContextMenu.QueryContextMenu(hMenu, 0, 1, &H7FFF, CMF_EXPLORE Or CMF_EXTENDEDVERBS)
                        If (hr = HRESULT.S_OK) Then
                            m_pContextMenu2 = CType(pContextMenu, IContextMenu2)
                            Dim nX As Integer = Cursor.Position.X, nY = Cursor.Position.Y
                            Dim nCmd As UInteger = TrackPopupMenu(hMenu, TPM_LEFTALIGN Or TPM_LEFTBUTTON Or TPM_RIGHTBUTTON Or TPM_RETURNCMD, nX, nY, 0, Me.Handle, IntPtr.Zero)
                            If (nCmd <> 0) Then
                                Dim cmi As CMINVOKECOMMANDINFO = New CMINVOKECOMMANDINFO()
                                cmi.cbSize = Marshal.SizeOf(GetType(CMINVOKECOMMANDINFO))
                                cmi.fMask = 0
                                cmi.fMask = CMIC_MASK_ASYNCOK Or CMIC_MASK_UNICODE
                                cmi.hwnd = Me.Handle
                                'cmi.hwnd = GetDesktopWindow()
                                cmi.lpVerb = CType((nCmd - 1), IntPtr)
                                cmi.lpParameters = IntPtr.Zero
                                cmi.lpDirectory = IntPtr.Zero
                                cmi.nShow = SW_SHOWNORMAL
                                cmi.dwHotKey = 0
                                cmi.hIcon = IntPtr.Zero
                                hr = pContextMenu.InvokeCommand(cmi)
                            End If
                            m_pContextMenu2 = Nothing
                        End If
                        Marshal.ReleaseComObject(pContextMenu)
                        DestroyMenu(hMenu)
                    End If
                End If
                ILFree(pItemIDL)
            End If
        End Sub
    End Class
    
    

1 additional answer

Sort by: Most helpful
  1. Jiachen Li-MSFT 33,701 Reputation points Microsoft External Staff
    2025-03-06T13:14:58.95+00:00

    Hi @Mansour_Dalir ,

    The best approach is to use the IContextMenu.

    You can follow the ideas in the following article and then implement it in VB.NET WinForm.

    1. Initial foray
    2. Displaying the context menu
    3. Invocation location
    4. Key context
    5. Handling menu messages
    6. Displaying menu help
    7. Invoking the default verb
    8. Optimizing for the default command
    9. Adding custom commands
    10. Composite extensions - groundwork
    11. Composite extensions - composition

    Best Regards.

    Jiachen Li


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


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.