我正在尝试将从
HMENU
获得的 IContextMenu.QueryContextMenu()
转换为 WPF ContextMenu
,这样我就可以拥有一个具有我在应用程序中定义的样式的上下文菜单,而不是普通的旧 Windows 菜单样式。
大部分功能都有效,除了两个项目“使用 Windows Defender 扫描...”和 Google Drive“同步或备份此文件夹”,它们不会在我从
MENUITEMINFO
获得的 GetMenuItemInfo()
结构中返回其图标。图标确实适用于其他项目。
我也尝试查看
MIIM_DATA
,但一无所获。
这是我的代码:
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)
If Not contextMenu2 Is Nothing Then
contextMenu2.HandleMenuMsg(WM.INITMENUPOPUP, hMenu2, IntPtr.Zero)
End If
If Not contextMenu3 Is Nothing Then
Dim ptr2 As IntPtr
contextMenu3.HandleMenuMsg2(WM.INITMENUPOPUP, hMenu2, IntPtr.Zero, ptr2)
End If
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
编辑:它们不是所有者绘制的。我已将
TrackPopupMenuEx()
与 WndProc
连接起来,并检查了消息流,没有 WM_DRAWITEM
或 WM_MEASUREITEM
消息。
我刚刚找到了如何获取这些项目的图标。
这是
MENUITEMINFO
结构:
<StructLayout(LayoutKind.Sequential)>
Public Structure MENUITEMINFO
Public cbSize As UInteger
Public fMask As UInteger
Public fType As UInteger
Public fState As MFS
Public wID As Integer
Public hSubMenu As IntPtr
Public hbmpChecked As IntPtr
Public hbmpUnchecked As IntPtr
Public dwItemData As IntPtr
Public dwTypeData As String
Public cch As UInteger
Public hbmpItem As IntPtr
End Structure
我刚刚发现它里面有 3 个图标:
hbmpItem
(我一开始检查的唯一一个),还有 hbmpChecked
和 hbmpUnchecked
。显然,一些上下文菜单处理程序使用 hbmpUnchecked
而不是 hbmpItem
。我在 7-zip 上下文菜单处理程序的源代码中找到了这个。