I’m trying to convert the HMENU I get from IContextMenu.QueryContextMenu to a WPF ContextMenu, so I can have a context menu with the styles I defined in my application instead of the plain old Windows menu style.
Most of it works, except for two items “Scan with Windows Defender…” and the Google Drive “Synchronize or backup this folder”, which don’t return their icons in the MENUITEMINFO structure I get from GetMenuItemInfo. Icons do work for the other items.
I tried looking at the MIIM_DATA too but found nothing.
This is my code:
Public Function GetContextMenu(items As IEnumerable(Of Item)) As ContextMenu
Dim pidls(items.Count - 1) As IntPtr
Dim lastpidls(items.Count - 1) As IntPtr
For i = 0 To items.Count - 1
Functions.SHGetIDListFromObject(Marshal.GetIUnknownForObject(items(i).ShellItem2), pidls(i))
lastpidls(i) = Functions.ILFindLastID(pidls(i))
Next
Dim ptr As IntPtr
Me.ShellFolder.GetUIObjectOf(IntPtr.Zero, lastpidls.Length, lastpidls, GetType(IContextMenu).GUID, 0, ptr)
Dim contextMenu As IContextMenu = Marshal.GetTypedObjectForIUnknown(ptr, GetType(IContextMenu))
Marshal.QueryInterface(ptr, GetType(IContextMenu2).GUID, ptr)
Dim contextMenu2 As IContextMenu2, contextMenu3 As IContextMenu3
If Not ptr = IntPtr.Zero Then
contextMenu2 = CType(Marshal.GetObjectForIUnknown(ptr), IContextMenu2)
End If
Marshal.QueryInterface(ptr, GetType(IContextMenu3).GUID, ptr)
If Not ptr = IntPtr.Zero Then
contextMenu3 = CType(Marshal.GetObjectForIUnknown(ptr), IContextMenu3)
End If
Dim hMenu As IntPtr = Functions.CreatePopupMenu()
contextMenu.QueryContextMenu(hMenu, 0, 0, UInt32.MaxValue, CMF.CMF_NORMAL Or CMF.CMF_EXTENDEDVERBS)
If _firstContextMenuCall Then
' somehow very first call doesn't return all items
Functions.DestroyMenu(hMenu)
hMenu = Functions.CreatePopupMenu()
contextMenu.QueryContextMenu(hMenu, 0, 0, UInt32.MaxValue, CMF.CMF_NORMAL Or CMF.CMF_EXTENDEDVERBS)
End If
Dim getMenu As Func(Of IntPtr, List(Of Control)) =
Function(hMenu2 As IntPtr) As List(Of Control)
Shell.cm3.HandleMenuMsg(WM.INITMENUPOPUP, hMenu2, IntPtr.Zero)
Shell.cm3.HandleMenuMsg2(WM.INITMENUPOPUP, hMenu2, IntPtr.Zero, ptr)
Dim result As List(Of Control) = New List(Of Control)()
For i = 0 To Functions.GetMenuItemCount(hMenu2) - 1
Dim mii As MENUITEMINFO
mii.cbSize = CUInt(Marshal.SizeOf(mii))
mii.fMask = MIIM.MIIM_STRING
mii.dwTypeData = New String(" "c, 2048)
mii.cch = mii.dwTypeData.Length
Functions.GetMenuItemInfo(hMenu2, i, True, mii)
Dim header As String = mii.dwTypeData.Substring(0, mii.cch)
mii.fMask = MIIM.MIIM_BITMAP Or MIIM.MIIM_SUBMENU Or MIIM.MIIM_FTYPE
Functions.GetMenuItemInfo(hMenu2, i, True, mii)
Dim bitmapSource As BitmapSource
If Not IntPtr.Zero.Equals(mii.hbmpItem) Then
bitmapSource = Interop.Imaging.CreateBitmapSourceFromHBitmap(mii.hbmpItem, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions())
Else
bitmapSource = Nothing
End If
If mii.fType = MFT.SEPARATOR Then
result.Add(New Separator())
Else
Dim menuItem As MenuItem = New MenuItem() With {
.Header = header.Replace("&", "_"),
.Icon = New Image() With {.Source = bitmapSource}
}
If mii.hSubMenu Then
Dim subMenu As List(Of Control) = getMenu(mii.hSubMenu)
For Each subMenuItem In subMenu
menuItem.Items.Add(subMenuItem)
Next
End If
result.Add(menuItem)
End If
Next
Return result
End Function
Dim result2 As List(Of Control) = getMenu(hMenu)
Dim menuResult As ContextMenu = New ContextMenu()
For Each item In result2
menuResult.Items.Add(item)
Next
Functions.DestroyMenu(hMenu)
Return menuResult
End Function
miss programmer is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.