Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,797
Office Version
  1. 2016
Platform
  1. Windows
Hi forum,

Workbook Example

I have been playing around with this little vba project and thought I would post it here.

Basically, the code creates a full menu system for (MODAL) userforms (up to 10 entries per menu) based on a table in a worksheet.

PREVIEW






The menus are indexed in the first column of the worksheet table and follow the the following format logic (See workbook example):
1
1.1
1.2
1.2.1
1.2.2
1.2.3
2
2.1
2.2
2.3
and so on.

The second column in the worksheet table holds the menu item caption, while the third column holds an optional icon (ICO,ANI,BMP or a FACE_ID #) and the last column is for the icon size in pixels.


Limitations:
Only works with MODAL userforms.... and only with one userform at a time.
Normally, in order to be able to respond to menu mouse clicks, the userform must be subclassed so we can intercept the WM_COMMAND msg. I have taken a different approach by using a WH_GETMESSAGE hook combined with a WH_CALLWNDPROC hook. This is so that we minimize the chances of crashing should an unhandled error occur.
I added an intentional raise error test button in the test- userform to verify that excel doesn't crash... The only exception is a compiled error inside the MouseMove event.
Still, I would advise to have propper error handling.



1- Code in a Standard Module
VBA Code:
Option Explicit

Private Enum IMAGE_TYPE
    ICO
    Bmp
    ANI
    'https://bettersolutions.com/vba/ribbon/face-ids-2003.htm
    FACE_ID
End Enum

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Type POINTAPI
    X As Long
    Y As Long
End Type

Private Type MENUITEMINFO
    cbSize As Long
    fMask As Long
    fType As Long
    fState As Long
    wID As Long
    #If Win64 Then
        hSubMenu As LongLong
        hbmpChecked As LongLong
        hbmpUnchecked As LongLong
        dwItemData As LongLong
        dwTypeData As String
        cch As Long
        '#if(WINVER >= 0x0500)
        hbmpItem As LongLong
        '#endif /* WINVER >= 0x0500 */
    #Else
        hSubMenu As Long
        hbmpChecked As Long
        hbmpUnchecked As Long
        dwItemData As Long
        dwTypeData As String
        cch As Long
        '#if(WINVER >= 0x0500)
        hbmpItem As Long
        '#endif /* WINVER >= 0x0500 */
    #End If
End Type

Private Type uPicDesc
    Size As Long
    Type As Long
    #If Win64 Then
        hPic As LongLong
        hPal As LongLong
    #Else
        hPic As Long
        hPal As Long
    #End If
End Type

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Type Msg
    #If Win64 Then
        hWnd As LongLong
        Message As Long
        wParam As LongLong
        lParam As LongLong
    #Else
        hWnd As Long
        Message As Long
        wParam As Long
        lParam As Long
    #End If
    time As Long
    pt As POINTAPI
End Type

Private Type CWPSTRUCT
    #If Win64 Then
        lParam As LongLong
        wParam As LongLong
        Message As Long
        hWnd As LongLong
    #Else
        lParam As Long
        wParam As Long
        Message As Long
        hWnd As Long
    #End If
End Type

'GDI+
Private Type GDIP_STARTUPINPUT
   GdiplusVersion As Long
  #If Win64 Then
        DebugEventCallback As LongLong
        SuppressBackgroundThread As LongLong
  #Else
        DebugEventCallback As Long
        SuppressBackgroundThread As Long
  #End If
   SuppressExternalCodecs As Long
End Type

#If VBA7 Then

    #If Win64 Then
        Private Declare PtrSafe Function MenuItemFromPoint Lib "user32" (ByVal hWnd As LongPtr, ByVal hMenu As LongPtr, ByVal ptScreen As LongLong) As Long
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongLong, ppacc As Any, pvarChild As Variant) As Long
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal Point As LongPtr) As LongPtr
    #Else
        Private Declare PtrSafe Function MenuItemFromPoint Lib "user32" (ByVal hWnd As LongPtr, ByVal hMenu As LongPtr, ByVal ptScreenX As Long, ByVal ptScreenY As Long) As Long
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As Any, pvarChild As Variant) As Long
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As LongPtr
    #End If
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUF As LongPtr) As Long
    Private Declare PtrSafe Function GetMenu Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetMenu Lib "user32" (ByVal hWnd As LongPtr, ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function CreateMenu Lib "user32" () As LongPtr
    Private Declare PtrSafe Function CreatePopupMenu Lib "user32" () As LongPtr
    Private Declare PtrSafe Function AppendMenu Lib "user32" Alias "AppendMenuA" (ByVal hMenu As LongPtr, ByVal wFlags As Long, ByVal wIDNewItem As LongPtr, ByVal lpNewItem As Any) As Long
    Private Declare PtrSafe Function DestroyMenu Lib "user32" (ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function DeleteMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function DrawMenuBar Lib "user32" (ByVal hWnd As LongPtr) As Long
    Private Declare PtrSafe Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As LongPtr, ByVal un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
    Private Declare PtrSafe Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" (ByVal hMenu As LongPtr, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long
    Private Declare PtrSafe Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As LongPtr, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
    Private Declare PtrSafe Function EnableMenuItemAPI Lib "user32" Alias "EnableMenuItem" (ByVal hMenu As LongPtr, ByVal wIDEnableItem As Long, ByVal wEnable As Long) As Long
    Private Declare PtrSafe Function GetMenuItemIDAPI Lib "user32" Alias "GetMenuItemID" (ByVal hMenu As LongPtr, ByVal nPos As Long) As Long
    Private Declare PtrSafe Function GetMenuItemCountAPI Lib "user32" Alias "GetMenuItemCount" (ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr
    Private Declare PtrSafe Function CopyImage Lib "user32" (ByVal handle As LongPtr, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr
    Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hWnd As LongPtr) As Long
    Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
    Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleAut32.dll" (PicDesc As uPicDesc, RefIID As Any, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
    Private Declare PtrSafe Function GlobalAddAtom Lib "kernel32" Alias "GlobalAddAtomA" (ByVal lpString As String) As Integer
    Private Declare PtrSafe Function GlobalGetAtomName Lib "kernel32" Alias "GlobalGetAtomNameA" (ByVal nAtom As Integer, ByVal lpBuffer As String, ByVal nSize As Long) As Long
    Private Declare PtrSafe Function GlobalDeleteAtom Lib "kernel32" (ByVal nAtom As Integer) As Integer
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnd As LongPtr, ByVal hDC As LongPtr) As Long
    Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As LongPtr) As LongPtr
    Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
    Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hDC As LongPtr, ByVal hObject As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetBkMode Lib "gdi32" (ByVal hDC As LongPtr, ByVal nBkMode As Long) As Long
    Private Declare PtrSafe Function SetBkColor Lib "gdi32" (ByVal hDC As LongPtr, ByVal crColor As Long) As Long
    Private Declare PtrSafe Function ExtTextOut Lib "gdi32" Alias "ExtTextOutA" (ByVal hDC As LongPtr, ByVal X As Long, ByVal Y As Long, ByVal wOptions As Long, lpRect As RECT, ByVal lpString As String, ByVal nCount As Long, lpDx As Long) As Long
    Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function DrawIconEx Lib "user32" (ByVal hDC As LongPtr, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As LongPtr, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As LongPtr, ByVal diFlags As Long) As Long
    Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDC As LongPtr) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
    Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
    Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
    Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As Long
    Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
    Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As LongPtr, lpdwProcessId As Long) As Long
    Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hWnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
    Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hWnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hWnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function GdiplusStartup Lib "gdiplus" (token As Long, inputbuf As GDIP_STARTUPINPUT, Optional ByVal outputbuf As Long = 0) As Long
    Private Declare PtrSafe Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Long
    Private Declare PtrSafe Function GdipCreateBitmapFromHBITMAP Lib "gdiplus" (ByVal hbm As LongPtr, ByVal hPal As LongPtr, BITMAP As LongPtr) As Long
    Private Declare PtrSafe Function GdipCreateHICONFromBitmap Lib "gdiplus" (ByVal BITMAP As LongPtr, ByRef hbmReturn As LongPtr) As Long
    Private Declare PtrSafe Function GdipDisposeImage Lib "gdiplus" (ByVal Image As LongPtr) As LongPtr
    Private Declare PtrSafe Function GdipGetImageThumbnail Lib "gdiplus" (ByVal Image As LongPtr, ByVal thumbWidth As Long, ByVal thumbHeight As Long, thumbImage As LongPtr, ByVal Callback As LongPtr, ByVal callbackData As LongPtr) As Long
    Private Declare PtrSafe Function GdipCreateBitmapFromHICON Lib "gdiplus" (ByVal hIcon As LongPtr, ByRef hBitmap As LongPtr) As Long
    Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hUF As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
    Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hUF As LongPtr, ByVal nIDEvent As LongPtr) As Long
   
    Private hForm As LongPtr, hFormMenu As LongPtr, hMen As LongPtr

#Else

    Private Declare Function MenuItemFromPoint Lib "user32" (ByVal hWnd As Long, ByVal hMenu As Long, ByVal ptScreenX As Long, ByVal ptScreenY As Long) As Long
    Private Declare Function AccessibleObjectFromPoint Lib "Oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
    Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUF As Long) As Long
    Private Declare Function GetMenu Lib "user32" (ByVal hWnd As Long) As Long
    Private Declare Function SetMenu Lib "user32" (ByVal hWnd As Long, ByVal hMenu As Long) As Long
    Private Declare Function CreateMenu Lib "user32" () As Long
    Private Declare Function CreatePopupMenu Lib "user32" () As Long
    Private Declare Function AppendMenu Lib "user32" Alias "AppendMenuA" (ByVal hMenu As Long, ByVal wFlags As Long, ByVal wIDNewItem As Long, ByVal lpNewItem As Any) As Long
    Private Declare Function DestroyMenu Lib "user32" (ByVal hMenu As Long) As Long
    Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long
    Private Declare Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
    Private Declare Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long
    Private Declare Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As Long, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
    Private Declare Function EnableMenuItemAPI Lib "user32" Alias "EnableMenuItem" (ByVal hMenu As Long, ByVal wIDEnableItem As Long, ByVal wEnable As Long) As Long
    Private Declare Function GetMenuItemIDAPI Lib "user32" Alias "GetMenuItemID" (ByVal hMenu As Long, ByVal nPos As Long) As Long
    Private Declare Function GetMenuItemCountAPI Lib "user32" Alias "GetMenuItemCount" (ByVal hMenu As Long) As Long
    Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
    Private Declare Function CopyImage Lib "user32" (ByVal handle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
    Private Declare Function OpenClipboard Lib "user32" (ByVal hWnd As Long) As Long
    Private Declare Function CloseClipboard Lib "user32" () As Long
    Private Declare Function EmptyClipboard Lib "user32" () As Long
    Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function OleCreatePictureIndirect Lib "oleAut32.dll" (PicDesc As uPicDesc, RefIID As Any, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
    Private Declare Function GlobalAddAtom Lib "kernel32" Alias "GlobalAddAtomA" (ByVal lpString As String) As Integer
    Private Declare Function GlobalGetAtomName Lib "kernel32" Alias "GlobalGetAtomNameA" (ByVal nAtom As Integer, ByVal lpBuffer As String, ByVal nSize As Long) As Long
    Private Declare Function GlobalDeleteAtom Lib "kernel32" (ByVal nAtom As Integer) As Integer
    Private Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
    Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hdc As Long) As Long
    Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
    Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
    Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
    Private Declare Function SetBkMode Lib "gdi32" (ByVal hdc As Long, ByVal nBkMode As Long) As Long
    Private Declare Function SetBkColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long
    Private Declare Function ExtTextOut Lib "gdi32" Alias "ExtTextOutA" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long, ByVal wOptions As Long, lpRect As RECT, ByVal lpString As String, ByVal nCount As Long, lpDx As Long) As Long
    Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function DrawIconEx Lib "user32" (ByVal hdc As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long
    Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
    Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
    Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long
    Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
    Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hWnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
    Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hWnd As Long, ByVal lpString As String) As Long
    Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hWnd As Long, ByVal lpString As String) As Long
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
    Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare Function GdiplusStartup Lib "gdiplus" (token As Long, inputbuf As GDIP_STARTUPINPUT, Optional ByVal outputbuf As Long = 0) As Long
    Private Declare Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Long
    Private Declare Function GdipCreateBitmapFromHBITMAP Lib "gdiplus" (ByVal hbm As Long, ByVal hPal As Long, BITMAP As Long) As Long
    Private Declare Function GdipCreateHICONFromBitmap Lib "gdiplus" (ByVal BITMAP As Long, ByRef hbmReturn As Long) As Long
    Private Declare Function GdipDisposeImage Lib "gdiplus" (ByVal Image As Long) As Long
    Private Declare Function GdipGetImageThumbnail Lib "gdiplus" (ByVal Image As Long, ByVal thumbWidth As Long, ByVal thumbHeight As Long, thumbImage As Long, ByVal Callback As Long, ByVal callbackData As Long) As Long
    Private Declare Function GdipCreateBitmapFromHICON Lib "gdiplus" (ByVal hIcon As Long, ByRef hBitmap As Long) As Long
    Private Declare Function SetTimer Lib "user32" (ByVal hUF As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
    Private Declare Function KillTimer Lib "user32" (ByVal hUF As Long, ByVal nIDEvent As Long) As Long
   
    Private hForm As Long, hFormMenu As Long, hMen As Long

#End If

Private BMPsCollection As Collection, MenusCollection As Collection
Private bMenuExpanded As Boolean, TotalMenuItems As Long
Private oForm As Object
Private MenItemID As String, MenItemCaption As String, MenItemPos As Long
Private ClickMacroName As String, MouseMoveMacroName As String



Public Sub AddMenu( _
    ByVal Form As Object, _
    ByVal SourceData As Range, _
    ByVal MouseClickEvent As String, _
    ByVal MouseMoveEvent As String _
   )

    Const MFS_POPUP = &H10&
    Const MFS_STRING = &H0&
    Const MIIM_ID = &H2
    Const MIIM_DATA = &H20
    Const MIIM_BITMAP = &H80
    Const MF_BYCOMMAND = &H0&
    Const MF_BYPOSITION = &H400&
    Const IMAGE_ICON = 1
    Const IMAGE_BITMAP = 0
    Const LR_LOADFROMFILE = &H10

    #If Win64 Then
        Dim hParentMenu As LongLong
        Dim hNewMenu As LongLong
        Dim hTmpImgPtr As LongLong
        Dim hImage As LongLong
    #Else
        Dim hParentMenu As Long
        Dim hNewMenu As Long
        Dim hTmpImgPtr As Long
        Dim hImage As Long
    #End If

    Dim ImgType As IMAGE_TYPE
    Dim MII As MENUITEMINFO
    Dim oStdPic As StdPicture
    Dim iAtom_ID As Integer
    Dim Cell As Range
    Dim MenuLevel As Long, NextLevel As Long
    Dim Caption As String
    Dim ImagePathOrFaceID As String
    Dim ImgSize As Long
    Dim TmpImagePathName As String
    Dim lCount As Long
    Dim StateFlags As Long
         
    Set MenusCollection = Nothing
    TotalMenuItems = 0
    bMenuExpanded = False
   
    Set oForm = Form
    ClickMacroName = MouseClickEvent
    MouseMoveMacroName = MouseMoveEvent
   
    Call IUnknown_GetWindow(Form, VarPtr(hForm))
    hFormMenu = CreateMenu
    Call SetMenu(hForm, hFormMenu)
    StateFlags = MIIM_ID Or MIIM_DATA

    For Each Cell In SourceData.Columns(1).Cells
   
        If MenusCollection Is Nothing Then
            Set MenusCollection = New Collection
        End If

        MenuLevel = Len(Replace(Cell.Text, ".", ""))
        NextLevel = Len(Replace(Cell.Offset(1).Text, ".", ""))
       
        If Not IsEmpty(Cell) Then
            Caption = Cell.Offset(, 1).Text
            ImagePathOrFaceID = FindImagePath(Cell.Offset(, 2).Text)
            ImgSize = Val(Cell.Offset(, 3).Text)
            If ImgSize < 16 Then ImgSize = 16
           
            If Len(ImagePathOrFaceID) Then
           
                If UCase(Right(ImagePathOrFaceID, 4)) = ".BMP" Then
                    ImgType = Bmp
                ElseIf UCase(Right(ImagePathOrFaceID, 4)) = ".ANI" Then
                    ImgType = ANI
                ElseIf UCase(Right(ImagePathOrFaceID, 4)) = ".ICO" Then
                    ImgType = ICO
                ElseIf Val(ImagePathOrFaceID) Then
                    ImgType = FACE_ID
                End If
               
                Select Case ImgType
                    Case Bmp
                        hImage = LoadImage(0, ImagePathOrFaceID, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
                        hTmpImgPtr = BitmapToIcon(hImage, ImgSize)
                        hTmpImgPtr = IconToBitmap(hTmpImgPtr, ImgType, ImgSize)
                    Case ICO, ANI
                        hImage = LoadImage(0, ImagePathOrFaceID, IMAGE_ICON, 0, 0, LR_LOADFROMFILE)
                        hTmpImgPtr = IconToBitmap(hImage, ImgType, ImgSize)
                    Case FACE_ID
                        If Len(ImagePathOrFaceID) Then
                            Set oStdPic = PicFromBmp(CLng(ImagePathOrFaceID))
                            If Not oStdPic Is Nothing Then
                                TmpImagePathName = Environ("TEMP") & Application.PathSeparator & "TempFaceID.bmp"
                                Call stdole.SavePicture(oStdPic, TmpImagePathName)
                                hImage = LoadImage(0, TmpImagePathName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
                                hTmpImgPtr = BitmapToIcon(hImage, ImgSize)
                                hTmpImgPtr = IconToBitmap(hTmpImgPtr, ImgType, ImgSize)
                                Call Kill(TmpImagePathName)
                            End If
                        End If
                End Select
                Call DeleteObject(hImage)
                Set oStdPic = Nothing
            Else
                    hTmpImgPtr = 0
            End If
           
            If hTmpImgPtr Then
                StateFlags = StateFlags Or MIIM_BITMAP
            End If
            If MenuLevel < NextLevel Then
                hNewMenu = CreateMenu
                MenusCollection.Add hNewMenu, CStr(Replace(Cell.Text, ".", ""))
                hParentMenu = GetParentMenu(Replace(Cell.Text, ".", ""))
                Call AppendMenu(hParentMenu, MFS_POPUP, hNewMenu, Caption)
                With MII
                    .cbSize = LenB(MII)
                    .fMask = StateFlags
                    .wID = lCount
                    iAtom_ID = GlobalAddAtom(hNewMenu & "||" & Replace(Caption, "&", "") & "||" & Cell.Text & "||" & lCount)
                    Call SetProp(hForm, "Atom", CLng(iAtom_ID))
                    .dwItemData = iAtom_ID
                    .hbmpItem = hTmpImgPtr
                    Call SetMenuItemInfo(hParentMenu, CLng(hNewMenu), MF_BYCOMMAND, MII)
                End With
            Else
                hParentMenu = GetParentMenu(Replace(Cell.Text, ".", ""))
                Call AppendMenu(hParentMenu, MFS_STRING, lCount, Caption)
                With MII
                    .cbSize = LenB(MII)
                    .fMask = StateFlags
                    .wID = lCount
                    iAtom_ID = GlobalAddAtom(hNewMenu & "||" & Replace(Caption, "&", "") & "||" & Cell.Text & "||" & lCount)
                    Call SetProp(hForm, "Atom", CLng(iAtom_ID))
                    .dwItemData = iAtom_ID
                    .hbmpItem = hTmpImgPtr
                    Call SetMenuItemInfo(hParentMenu, lCount, MF_BYCOMMAND, MII)
                End With
            End If
            lCount = lCount + 1
        End If
    Next Cell

    TotalMenuItems = lCount
    Call DrawMenuBar(hForm)
    Call SetHooks(hForm)

End Sub

Public Sub CleanUp(Optional ByVal Dummy As Boolean)

    Dim i As Long
   
    Call GlobalDeleteAtom(CInt(GetProp(hForm, "Atom")))
    Call RemoveProp(hForm, "Atom")
    Call RemoveHooks
   
    If Not BMPsCollection Is Nothing Then
        With BMPsCollection
            For i = .Count To 1 Step -1
                Call DeleteObject(.Item(i))
                .Remove i
            Next i
        End With
    End If
   
    Set BMPsCollection = Nothing
    Call DestroyMenu(hFormMenu)
   
End Sub

Public Sub EnableMenuItem(ByVal MenuItemPos As Long, ByVal bEnable As Boolean)
    Const MF_BYCOMMAND = &H0&
    Const MF_DISABLED = &H2
    Const MF_ENABLED = &H0
    Call EnableMenuItemAPI(hFormMenu, MenuItemPos, MF_BYCOMMAND + IIf(bEnable, MF_ENABLED, MF_DISABLED))
    Call DrawMenuBar(hForm)
End Sub

Public Sub DeleteMenuItem(ByVal MenuItemPos As Long)
    Const MF_BYCOMMAND = &H0&
    Call DeleteMenu(hFormMenu, MenuItemPos, MF_BYCOMMAND)
    Call DrawMenuBar(hForm)
End Sub

Public Function GetMenuItemCaptionFromItemPos(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Dim sBuffer  As String * 256, lRet As Long
    lRet = GetMenuString(hFormMenu, MenuItemPos, sBuffer, Len(sBuffer), MF_BYCOMMAND)
    GetMenuItemCaptionFromItemPos = Left(sBuffer, lRet)
End Function

Public Function GetMenuItemIDFromItemPos(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Const MIIM_DATA = &H20
    Dim MII As MENUITEMINFO
    Dim sBuffer  As String * 256, lRet As Long
    With MII
        .cbSize = LenB(MII)
        .fMask = MIIM_DATA
    End With
    Call GetMenuItemInfo(hFormMenu, MenuItemPos, MF_BYCOMMAND, MII)
    lRet = GlobalGetAtomName(CInt(MII.dwItemData), sBuffer, Len(sBuffer))
    GetMenuItemIDFromItemPos = Split(Left(sBuffer, lRet), "||")(2)
End Function

Public Function GetMenuItemsTotalCount() As Long
    GetMenuItemsTotalCount = TotalMenuItems
End Function

Public Function GetMenusCount() As Long
    GetMenusCount = MenusCollection.Count
End Function

Public Function GetMainMenusCount() As Long
    GetMainMenusCount = GetMenuItemCountAPI(GetMenu(hForm))    '
End Function

Public Function CAPTION_OF_MenuItemUnderMousePointer() As String

    Const MF_BYPOSITION = &H400&
    #If Win64 Then
        Dim hMenu As LongLong, hWinUnderMouse As LongLong
    #Else
        Dim hMenu As Long, hWinUnderMouse As Long
    #End If
    Dim sBuffer2  As String * 256, lRet2 As Long
    Dim tCurPos As POINTAPI, vKid As Variant, oIA As IAccessible
    Dim MenuPos As Long

    On Error Resume Next
   
    MenuPos = POS_OF_MenuItemFromPoint(hMenu)
    lRet2 = GetMenuString(hMenu, MenuPos, sBuffer2, Len(sBuffer2), MF_BYPOSITION)
   
    If Len(Replace(Left(sBuffer2, lRet2), "&", "")) Then
        CAPTION_OF_MenuItemUnderMousePointer = Replace(Left(sBuffer2, lRet2), "&", "")
    Else
        Call GetCursorPos(tCurPos)
        #If Win64 Then
            Dim Ptr As LongLong
            Call CopyMemory(Ptr, tCurPos, LenB(tCurPos))
            Call AccessibleObjectFromPoint(Ptr, oIA, vKid)
             hWinUnderMouse = WindowFromPoint(Ptr)
        #Else
            Call AccessibleObjectFromPoint(tCurPos.X, tCurPos.Y, oIA, vKid)
             hWinUnderMouse = WindowFromPoint(tCurPos.X, tCurPos.Y)
        #End If
        If hWinUnderMouse <> hForm Then
            CAPTION_OF_MenuItemUnderMousePointer = oIA.accName(0&)
        End If
    End If

End Function

'
Public Function ID_OF_MenuItemUnderMousePointer() As String

  Const MIIM_DATA = &H20
  Const MF_BYPOSITION = &H400&

    #If Win64 Then
        Dim hMenu As LongLong
    #Else
        Dim hMenu As Long
    #End If
   
    Dim MII As MENUITEMINFO
    Dim MenuPos As Long
    Dim sBuffer  As String * 256, lRet As Long

    MenuPos = POS_OF_MenuItemFromPoint(hMenu)
    With MII
        .cbSize = LenB(MII)
        .fMask = MIIM_DATA
    End With
    Call GetMenuItemInfo(hMenu, MenuPos, MF_BYPOSITION, MII)
    lRet = GlobalGetAtomName(CInt(MII.dwItemData), sBuffer, Len(sBuffer))
    ID_OF_MenuItemUnderMousePointer = Split(Left(sBuffer, lRet), "||")(2)

End Function


Public Function POS_OF_MenuItemUnderMousePointer() As Long
   
    Dim tCurPos As POINTAPI
       Dim L  As Long, T As Long
 
    On Error Resume Next
 
    Dim vKid As Variant
    Dim oIA As IAccessible
    Dim lResult As Long
 
    Call GetCursorPos(tCurPos)
    #If Win64 Then
        Dim Ptr As LongLong
        Call CopyMemory(Ptr, tCurPos, LenB(tCurPos))
        Call AccessibleObjectFromPoint(Ptr, oIA, vKid)
    #Else
        Call AccessibleObjectFromPoint(tCurPos.X, tCurPos.Y, oIA, vKid)
    #End If

    POS_OF_MenuItemUnderMousePointer = vKid

End Function


Public Function IsMenuExpanded() As Boolean
    IsMenuExpanded = bMenuExpanded
End Function




'_______________________________________PRIVATE ROUTINES___________________________________________


#If Win64 Then
    Private Function GetParentMenu(ByVal CellText As String) As LongLong
#Else
    Private Function GetParentMenu(ByVal CellText As String) As Long
#End If

    On Error Resume Next
   
    Dim sParent As String
   
    If Len(CellText) Then
        If Len(CellText) = 1 Then
            GetParentMenu = hFormMenu
        Else
            sParent = Left(CellText, Len(CellText) - 1)
        End If
        GetParentMenu = MenusCollection(sParent)
    End If

End Function

#If Win64 Then
    Private Function POS_OF_MenuItemFromPoint(ByRef hMenu As LongLong) As Long
        Dim hWndMenu As LongLong, Ptr As LongLong
#Else
    Private Function POS_OF_MenuItemFromPoint(ByRef hMenu As Long) As Long
        Dim hWndMenu As Long
#End If

    Const MN_GETHMENU = &H1E1
    Dim tCurPos As POINTAPI
   
    hWndMenu = FindWindow("#32768", vbNullString)
    If hWndMenu Then
        Call GetCursorPos(tCurPos)
        hMenu = SendMessage(hWndMenu, MN_GETHMENU, 0, 0)
        #If Win64 Then
            Call CopyMemory(Ptr, tCurPos, LenB(tCurPos))
            POS_OF_MenuItemFromPoint = MenuItemFromPoint(0, hMenu, Ptr)
        #Else
            POS_OF_MenuItemFromPoint = MenuItemFromPoint(0, hMenu, tCurPos.X, tCurPos.Y)
        #End If
    End If

End Function

#If Win64 Then
    Private Sub SetHooks(ByVal hWnd As LongLong)
        Dim hHook1 As LongLong, hHook2 As LongLong
#Else
    Private Sub SetHooks(ByVal hWnd As Long)
        Dim hHook1 As Long, hHook2 As Long
#End If
 
    Const WH_GETMESSAGE = 3
    Const WH_CALLWNDPROC = 4
   
    Call RemoveHooks
   
    hHook1 = SetWindowsHookEx(WH_GETMESSAGE, AddressOf MenuProc, GetModuleHandle(vbNullString), _
        GetWindowThreadProcessId(Application.hWnd, 0))
   
    hHook2 = SetWindowsHookEx(WH_CALLWNDPROC, AddressOf SafeExitHook, _
        GetModuleHandle(vbNullString), GetWindowThreadProcessId(Application.hWnd, 0))
   
    Call SetProp(hWnd, "Hook1", hHook1)
    Call SetProp(hWnd, "Hook2", hHook2)

End Sub

Private Sub RemoveHooks()
    Call UnhookWindowsHookEx(GetProp(hForm, "Hook1"))
    Call UnhookWindowsHookEx(GetProp(hForm, "Hook2"))
    Call RemoveProp(hForm, "Hook1")
    Call RemoveProp(hForm, "Hook2")
    Call KillTimer(hForm, 0)
End Sub


#If Win64 Then
    Private Function SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        lParam As CWPSTRUCT _
    ) As LongLong

#Else
    Private Function SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        lParam As CWPSTRUCT _
    ) As Long
#End If

    Const WM_CREATE = &H1
    Dim strClass As String * 256

    If lParam.Message = WM_CREATE Then
        strClass = Left(strClass, GetWindowText(lParam.hWnd, ByVal strClass, 256))
        If InStr(1, strClass, "Microsoft Visual Basic") Then
            Call RemoveHooks
            Debug.Print "hooks removed !!!"
        End If
    End If
 
    SafeExitHook = CallNextHookEx(GetProp(hForm, "Hook2"), ncode, wParam, ByVal lParam)
 
End Function


#If Win64 Then
    Private Function MenuProc( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        lParam As Msg _
    ) As LongLong

        Dim hWndMenu As LongLong, hMnu As LongLong, hMenu As LongLong, Ptr As LongLong
#Else
    Private Function MenuProc( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        lParam As Msg _
    ) As Long

        Dim hWndMenu As Long, hMnu As Long, hMenu As Long
#End If

    Const HC_ACTION = 0&
    Const WM_COMMAND = &H111
    Const WM_MOUSEMOVE = &H200
    Const MF_BYCOMMAND = &H0&
    Const MF_BYPOSITION = &H400&
    Const MIIM_ID = &H2
    Const MIIM_DATA = &H20
    Const MN_GETHMENU = &H1E1
   
    Static sPrevCaption  As String
    Static sCaption2 As String
    Static ID As Long

    Dim MII1 As MENUITEMINFO, MII2 As MENUITEMINFO
    Dim sBuffer  As String * 256, lRet As Long
    Dim sBuffer2  As String * 256, lRet2 As Long
    Dim sCaption As String, sMenItemID As String
    Dim MenuPos As Long
    Dim MaskFlags As Long
 
    On Error Resume Next
   
    If (ncode = HC_ACTION) Then
   
        MaskFlags = MIIM_ID Or 0 Or MIIM_DATA
        With MII1
            .cbSize = LenB(MII1)
            .fMask = MaskFlags
        End With
        Call GetMenuItemInfo(GetMenu(hForm), CLng(lParam.wParam), MF_BYCOMMAND, MII1)
        lRet = GlobalGetAtomName(CInt(MII1.dwItemData), sBuffer, Len(sBuffer))
        hMenu = Split(Left(sBuffer, lRet), "||")(0)
        sCaption = Split(Left(sBuffer, lRet), "||")(1)
        sMenItemID = Split(Left(sBuffer, lRet), "||")(2)
       
        If lParam.Message = WM_MOUSEMOVE Then
            hWndMenu = FindWindow("#32768", vbNullString)
            If hWndMenu Then
                bMenuExpanded = True
                hMnu = SendMessage(hWndMenu, MN_GETHMENU, 0, 0)
                #If Win64 Then
                    Call CopyMemory(Ptr, lParam.pt, LenB(lParam.pt))
                    MenuPos = MenuItemFromPoint(0, hMnu, Ptr)
               #Else
                    MenuPos = MenuItemFromPoint(0, hMnu, lParam.pt.X, lParam.pt.Y)
               #End If
                With MII2
                    .cbSize = LenB(MII2)
                    .fMask = MaskFlags
                End With
                Call GetMenuItemInfo(hMnu, MenuPos, MF_BYPOSITION, MII2)
                lRet2 = GetMenuString(hMnu, MenuPos, sBuffer2, Len(sBuffer2), MF_BYPOSITION)
                If sPrevCaption <> Replace(Left(sBuffer2, lRet2), "&", "") Then
                    Call RemoveHooks
                    sCaption2 = Replace(Left(sBuffer2, lRet2), "&", "")
                    ID = MII2.wID
                    CallByName oForm, MouseMoveMacroName, VbMethod, Replace(Left(sBuffer2, lRet2), "&", ""), MII2.wID
                    Call SetHooks(hForm)
                End If
            Else
                bMenuExpanded = False
            End If
        End If
        sPrevCaption = sCaption2
       
        If lParam.Message = WM_COMMAND Then
            Call RemoveHooks
            hMen = hMenu
            MenItemID = sMenItemID
            MenItemCaption = sCaption
            MenItemPos = MII1.wID
            Call SetTimer(hForm, 0, 0, AddressOf TimerProc)
        End If

    End If
   
    MenuProc = CallNextHookEx(GetProp(hForm, "Hook1"), ncode, wParam, ByVal lParam)
 
End Function

Private Sub TimerProc()
    Call KillTimer(hForm, 0)
    Call Application.OnTime(Now, "MouseClickEvent")
End Sub

Private Sub MouseClickEvent()
    CallByName oForm, ClickMacroName, VbMethod, hMen, MenItemID, MenItemCaption, MenItemPos
    Call SetHooks(hForm)
End Sub

Private Function FindImagePath(ByVal ImgPath As String) As String
    Dim sTemp As String
    Select Case True
        Case IsValidFaceID(ImgPath)
            FindImagePath = ImgPath
        Case Len(Dir(ImgPath))
            FindImagePath = ImgPath
        Case Len(ImgPath)
            sTemp = ThisWorkbook.Path & Application.PathSeparator & ImgPath
            On Error Resume Next
                If Len(Dir(sTemp)) Then
                    If Err.Number = 0 Then
                        FindImagePath = sTemp
                    End If
            On Error GoTo 0
            End If
    End Select
End Function

Private Function IsValidFaceID(ByVal FaceID As String) As Boolean
    On Error Resume Next
    IsValidFaceID = Not (Application.CommandBars.FindControl(ID:=CLng(FaceID)) Is Nothing)
End Function

#If Win64 Then
    Private Function PicFromBmp(ByVal FaceID As Long) As StdPicture
        Dim hBmpPtr As LongLong
#Else
    Private Function PicFromBmp(ByVal FaceID As Long) As StdPicture
        Dim hBmpPtr As Long
#End If

    Const IMAGE_BITMAP = 0
    Const PICTYPE_BITMAP = 1
    Const LR_COPYRETURNORG = &H4
    Const CF_BITMAP = 2
    Const S_OK = &H0&

    Dim IID_IDispatch As GUID, uPicInfo As uPicDesc
    Dim IPic As Object, lRet As Long
   
    On Error GoTo errHandler
   
    Application.CommandBars.FindControl(ID:=FaceID).CopyFace
    Call OpenClipboard(0)
    hBmpPtr = GetClipboardData(CF_BITMAP)
    hBmpPtr = CopyImage(hBmpPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)
   
    If hBmpPtr Then
        With IID_IDispatch
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        With uPicInfo
            .Size = Len(uPicInfo)
            .Type = PICTYPE_BITMAP
            .hPic = hBmpPtr
            .hPal = 0
        End With
        lRet = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, IPic)
        If lRet = S_OK Then
            Set PicFromBmp = IPic
        End If
    End If
   
errHandler:
    Call EmptyClipboard
    Call CloseClipboard
   
    If BMPsCollection Is Nothing Then _
            Set BMPsCollection = New Collection
            Call BMPsCollection.Add(hBmpPtr)
 
End Function


#If Win64 Then
    Private Function BitmapToIcon(ByVal Bmp As LongLong, ImageSize As Long) As LongLong
        Dim lBitmap As LongLong, lThumb As LongLong
#Else
    Private Function BitmapToIcon(ByVal Bmp As Long, ImageSize As Long) As Long
        Dim lBitmap As Long, lThumb As Long
#End If

    Const S_OK = 0&
    Dim tSI As GDIP_STARTUPINPUT
    Dim lRes As Long
    Dim lGDIP As Long
   
    On Error Resume Next
    tSI.GdiplusVersion = 1
    lRes = GdiplusStartup(lGDIP, tSI)
    If lRes = S_OK Then
        lRes = GdipCreateBitmapFromHBITMAP(Bmp, 0, lBitmap)
        If lRes = S_OK Then
            lRes = GdipGetImageThumbnail(lBitmap, ImageSize, ImageSize, lThumb, 0, 0)
            If lRes = S_OK Then
                lRes = GdipCreateHICONFromBitmap(lThumb, BitmapToIcon)
                Call GdipDisposeImage(lThumb)
            End If
            Call GdipDisposeImage(lBitmap)
        End If
        Call GdiplusShutdown(lGDIP)
    End If

End Function


#If Win64 Then
    Private Function IconToBitmap(ByVal hImage As LongLong, ByVal ImageType As IMAGE_TYPE, ByVal ImageSize As Long) As LongLong
        Dim hDC As LongLong, hCompatDc As LongLong
#Else
    Private Function IconToBitmap(ByVal hImage As Long, ByVal ImageType As IMAGE_TYPE, ByVal ImageSize As Long) As Long
        Dim hDC As Long, hCompatDc As Long
#End If

    Const TRANSPARENT = 1
    Const SM_CXMENUCHECK = 71
    Const ETO_OPAQUE = 2
    Const DI_NORMAL = &H3&
    Const COLOR_MENU = 4
   
    Dim RECT As RECT
   
    If BMPsCollection Is Nothing Then _
            Set BMPsCollection = New Collection
   
    If ImageSize = 0 Then _
            ImageSize = GetSystemMetrics(SM_CXMENUCHECK)
   
    hDC = GetDC(hForm)
    With RECT
        .Right = ImageSize
        .Bottom = ImageSize
        hCompatDc = CreateCompatibleDC(0)
        IconToBitmap = CreateCompatibleBitmap(hDC, .Right, .Bottom)
        Call SelectObject(hCompatDc, IconToBitmap)
        Call SetBkMode(hCompatDc, TRANSPARENT)
        Call SetBkColor(hCompatDc, GetSysColor(COLOR_MENU))
        Call ExtTextOut(hCompatDc, 0, 0, ETO_OPAQUE, RECT, vbNullString, 0, 0)
        Call DrawIconEx(hCompatDc, 0, 0, hImage, .Right, .Bottom, 0, 0, DI_NORMAL)
        Call DeleteDC(hCompatDc)
    End With
    Call ReleaseDC(hForm, hDC)
   
    Call BMPsCollection.Add(IconToBitmap)
   
End Function



2- UserForm Code
VBA Code:
Option Explicit


Private Sub UserForm_Activate()
    AddMenu Me, MenuSourceData.Range("A4:D36"), "MouseClickEvent", "MouseMoveEvent"
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Call CleanUp
End Sub

Private Sub RaiseError_Test_Click()
    Err.Raise Number:=vbObjectError + 513, _
            Description:="Wooh !!!" & vbNewLine & "No GPF, No Application shut down !!" & _
            vbNewLine & vbNewLine & "Hooks safely released."
End Sub



'_________________________________ PUBLIC PSEUDO-EVENTS _________________________________________

#If Win64 Then
        Public Sub MouseClickEvent( _
        ByVal Menu As LongLong, _
        ByVal MenuItemID As String, _
        ByVal MenuItemCaption As String, _
        ByVal GlobalMenuItemPos As Long _
    )
#Else
        Public Sub MouseClickEvent( _
        ByVal Menu As Long, _
        ByVal MenuItemID As String, _
        ByVal MenuItemCaption As String, _
        ByVal GlobalMenuItemPos As Long _
    )
#End If

    Me.LblMenuItemCaption = ""
    Me.LblMenuItemID = ""
    Me.LblMenuItemPos = ""

    MsgBox "You clicked : " & vbNewLine & vbNewLine & _
                "Menu Handle : " & Menu & vbNewLine & _
                "Menu Item ID : " & MenuItemID & vbNewLine & _
                "Menu Item Caption : " & MenuItemCaption & vbNewLine & _
                "Menu Item Global Position : " & GlobalMenuItemPos

End Sub


Public Sub MouseMoveEvent( _
        ByVal MenuItemCaption As String, _
        ByVal GlobalMenuItemPos As Long _
    )
 
    '=================================================
    ' IMPORTANT NOTE:
    '
       'Compilation errors in this specific event routine will crash excel !!!!
       'Run-Time errors however are OK.
    '==================================================

        Me.LblMenuItemCaption = CAPTION_OF_MenuItemUnderMousePointer
        Me.LblMenuItemID = ID_OF_MenuItemUnderMousePointer
        Me.LblMenuItemPos = POS_OF_MenuItemUnderMousePointer
       
    ''You can also use the event arguments.

End Sub
 
Ok- Here is the entire project for Worksheet-Based Menus that can be added to Multiple Modal UserForms.

Download Workbook Example Here

The code uses a single Class (CMenu) which has some handy Properties and Methods as shown in the picture below and it also provides two Events (MenuItemSelect and MenuItemHighlight)

Sans titre.png


The code is now supposed to be working with multiple modal forms because it resides in a class module. Each new menu instance corresponds to a new class instance.

I have also added code to properly allow menu keyboard navigation.

Again, as I mentioned in my previous post, no erros are allowed inside the two event handler routines. ( Errors elsewhere in the code are hopefully taken care of )... So please, don't forget to apply proper defensive error handling or use correct code or else, you will see a nasty crashing before your eyes.






Proceedings:

Creating the menu is very simple as shown in the following example:

In the UserForm Module :
VBA Code:
Option Explicit

Private WithEvents MenuInstance As CMenu

Public Sub UserForm_Activate()
    Call CreateMenu
End Sub

Private Sub CreateMenu()
    Set MenuInstance = New CMenu
    MenuInstance.AddMenu Me, Sheet1.Range("H5:K18")
End Sub


'__________________________________________ MENU EVENTS ______________________________________________________

Private Sub MenuInstance_MenuItemHighlight( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenuItemPos As Long, _
    ByVal MenuItemEnabled As Boolean _
)

'    '=================================================
'    ' IMPORTANT NOTE!!:
'      Errors not allowed inside this event handler routine.
'      Propper Error Handling Is Advised.
'    '==================================================

    If MenuInstance.IsMenuExpanded Then
        LblMenuItemCaption = MenuItemCaption
        LblMenuItemID = MenuItemID
        LblMenuItemPos = IIf(MenuItemPos, MenuItemPos, "")
        LblMenuItemState = IIf(MenuItemEnabled, "Enabled", "Disabled")
    Else
        LblMenuItemCaption = ""
        LblMenuItemID = ""
        LblMenuItemPos = ""
        LblMenuItemState = ""
    End If

End Sub

Private Sub MenuInstance_MenuItemSelect( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenItemPos As Long _
)

'    '=================================================
'    ' IMPORTANT NOTE!!:
'      Errors not allowed inside this event handler routine.
'      Propper Error Handling Is Advised.
'    '==================================================

    MsgBox "You selected : " & vbNewLine & vbNewLine & _
            "Menu Item Caption : " & MenuItemCaption & vbNewLine & _
            "Menu Item ID : " & MenuItemID & vbNewLine & _
            "Menu Item Position : " & MenItemPos
End Sub




Following are the three other necessary vbproject modules. The user must leave these three modules untouched


1
- Class Module (CMenu)
VBA Code:
Option Explicit

Implements ISecret

Public Event MenuItemSelect( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenItemPos As Long _
)

Public Event MenuItemHighlight( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenuItemPos As Long, _
    ByVal MenuItemEnabled As Boolean _
)

Private Enum IMAGE_TYPE
    ICO
    Bmp
    ANI
    FACE_ID '<== https://bettersolutions.com/vba/ribbon/face-ids-2003.htm
End Enum

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Type LongToInteger
    Low As Integer
    High As Integer
End Type

Private Type MENUITEMINFO
    cbSize As Long
    fMask As Long
    fType As Long
    fState As Long
    wID As Long
    #If Win64 Then
        hSubMenu As LongLong
        hbmpChecked As LongLong
        hbmpUnchecked As LongLong
        dwItemData As LongLong
        dwTypeData As String
        cch As Long
        '#if(WINVER >= 0x0500)
        hbmpItem As LongLong
        '#endif /* WINVER >= 0x0500 */
    #Else
        hSubMenu As Long
        hbmpChecked As Long
        hbmpUnchecked As Long
        dwItemData As Long
        dwTypeData As String
        cch As Long
        '#if(WINVER >= 0x0500)
        hbmpItem As Long
        '#endif /* WINVER >= 0x0500 */
    #End If
End Type

Private Type uPicDesc
    Size As Long
    Type As Long
    #If Win64 Then
        hPic As LongLong
        hPal As LongLong
    #Else
        hPic As Long
        hPal As Long
    #End If
End Type

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Type Msg
    #If Win64 Then
        hwnd As LongLong
        Message As Long
        wParam As LongLong
        lParam As LongLong
    #Else
        hwnd As Long
        Message As Long
        wParam As Long
        lParam As Long
    #End If
    time As Long
    PT As POINTAPI
End Type

Private Type CWPSTRUCT
    #If Win64 Then
        lParam As LongLong
        wParam As LongLong
        Message As Long
        hwnd As LongLong
    #Else
        lParam As Long
        wParam As Long
        Message As Long
        hwnd As Long
    #End If
End Type

'GDI+
Private Type GDIP_STARTUPINPUT
   GdiplusVersion As Long
  #If Win64 Then
        DebugEventCallback As LongLong
        SuppressBackgroundThread As LongLong
  #Else
        DebugEventCallback As Long
        SuppressBackgroundThread As Long
  #End If
   SuppressExternalCodecs As Long
End Type

#If VBA7 Then
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal numBytes As LongPtr)
    Private Declare PtrSafe Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hwnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr, Optional ByVal dwRefData As LongPtr) As LongPtr
    Private Declare PtrSafe Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr) As LongPtr
    Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUF As LongPtr) As Long
    Private Declare PtrSafe Function GetMenu Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetMenu Lib "user32" (ByVal hwnd As LongPtr, ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function CreateMenu Lib "user32" () As LongPtr
    Private Declare PtrSafe Function CreatePopupMenu Lib "user32" () As LongPtr
    Private Declare PtrSafe Function AppendMenu Lib "user32" Alias "AppendMenuA" (ByVal hMenu As LongPtr, ByVal wFlags As Long, ByVal wIDNewItem As LongPtr, ByVal lpNewItem As Any) As Long
    Private Declare PtrSafe Function DestroyMenu Lib "user32" (ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function DeleteMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function DrawMenuBar Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function RemoveMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As LongPtr, ByVal un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
    Private Declare PtrSafe Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" (ByVal hMenu As LongPtr, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long
    Private Declare PtrSafe Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As LongPtr, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
    Private Declare PtrSafe Function GetMenuState Lib "user32" (ByVal hMenu As LongPtr, ByVal wID As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function GetSystemMenu Lib "user32" (ByVal hwnd As LongPtr, ByVal bRevert As Long) As LongPtr
    Private Declare PtrSafe Function EnableMenuItemAPI Lib "user32" Alias "EnableMenuItem" (ByVal hMenu As LongPtr, ByVal wIDEnableItem As Long, ByVal wEnable As Long) As Long
    Private Declare PtrSafe Function GetMenuItemCountAPI Lib "user32" Alias "GetMenuItemCount" (ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr
    Private Declare PtrSafe Function CopyImage Lib "user32" (ByVal handle As LongPtr, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr
    Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
    Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleAut32.dll" (PicDesc As uPicDesc, RefIID As Any, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDC As LongPtr) As Long
    Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As LongPtr) As LongPtr
    Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
    Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hDC As LongPtr, ByVal hObject As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetBkMode Lib "gdi32" (ByVal hDC As LongPtr, ByVal nBkMode As Long) As Long
    Private Declare PtrSafe Function SetBkColor Lib "gdi32" (ByVal hDC As LongPtr, ByVal crColor As Long) As Long
    Private Declare PtrSafe Function ExtTextOut Lib "gdi32" Alias "ExtTextOutA" (ByVal hDC As LongPtr, ByVal x As Long, ByVal y As Long, ByVal wOptions As Long, lpRect As RECT, ByVal lpString As String, ByVal nCount As Long, lpDx As Long) As Long
    Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function DrawIconEx Lib "user32" (ByVal hDC As LongPtr, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As LongPtr, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As LongPtr, ByVal diFlags As Long) As Long
    Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDC As LongPtr) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
    Private Declare PtrSafe Function DestroyIcon Lib "user32" (ByVal hIcon As LongPtr) As Long
    Private Declare PtrSafe Function DestroyCursor Lib "user32" (ByVal hCursor As LongPtr) As Long
    Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
    Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
    Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As Long
    Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
    Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long
    Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
    Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function GdiplusStartup Lib "gdiplus" (token As Long, inputbuf As GDIP_STARTUPINPUT, Optional ByVal outputbuf As Long = 0) As Long
    Private Declare PtrSafe Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Long
    Private Declare PtrSafe Function GdipCreateBitmapFromHBITMAP Lib "gdiplus" (ByVal hbm As LongPtr, ByVal hPal As LongPtr, BITMAP As LongPtr) As Long
    Private Declare PtrSafe Function GdipCreateHICONFromBitmap Lib "gdiplus" (ByVal BITMAP As LongPtr, ByRef hbmReturn As LongPtr) As Long
    Private Declare PtrSafe Function GdipDisposeImage Lib "gdiplus" (ByVal Image As LongPtr) As LongPtr
    Private Declare PtrSafe Function GdipGetImageThumbnail Lib "gdiplus" (ByVal Image As LongPtr, ByVal thumbWidth As Long, ByVal thumbHeight As Long, thumbImage As LongPtr, ByVal CallBack As LongPtr, ByVal callbackData As LongPtr) As Long
  
    Private hForm As LongPtr, hFormMenu As LongPtr

#Else
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal numBytes As Long)
    Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUF As Long) As Long
    Private Declare Function GetMenu Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function SetMenu Lib "user32" (ByVal hwnd As Long, ByVal hMenu As Long) As Long
    Private Declare Function CreateMenu Lib "user32" () As Long
    Private Declare Function CreatePopupMenu Lib "user32" () As Long
    Private Declare Function AppendMenu Lib "user32" Alias "AppendMenuA" (ByVal hMenu As Long, ByVal wFlags As Long, ByVal wIDNewItem As Long, ByVal lpNewItem As Any) As Long
    Private Declare Function DestroyMenu Lib "user32" (ByVal hMenu As Long) As Long
    Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function RemoveMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
    Private Declare Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long
    Private Declare Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As Long, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
    Private Declare Function GetMenuState Lib "user32" (ByVal hMenu As Long, ByVal wID As Long, ByVal wFlags As Long) As Long
    Private Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Long, ByVal bRevert As Long) As Long
    Private Declare Function EnableMenuItemAPI Lib "user32" Alias "EnableMenuItem" (ByVal hMenu As Long, ByVal wIDEnableItem As Long, ByVal wEnable As Long) As Long
    Private Declare Function GetMenuItemCountAPI Lib "user32" Alias "GetMenuItemCount" (ByVal hMenu As Long) As Long
    Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
    Private Declare Function CopyImage Lib "user32" (ByVal handle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
    Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function CloseClipboard Lib "user32" () As Long
    Private Declare Function EmptyClipboard Lib "user32" () As Long
    Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function OleCreatePictureIndirect Lib "oleAut32.dll" (PicDesc As uPicDesc, RefIID As Any, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
    Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDC As Long) As Long
    Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As Long) As Long
    Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
    Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
    Private Declare Function SetBkMode Lib "gdi32" (ByVal hDC As Long, ByVal nBkMode As Long) As Long
    Private Declare Function SetBkColor Lib "gdi32" (ByVal hDC As Long, ByVal crColor As Long) As Long
    Private Declare Function ExtTextOut Lib "gdi32" Alias "ExtTextOutA" (ByVal hDC As Long, ByVal x As Long, ByVal y As Long, ByVal wOptions As Long, lpRect As RECT, ByVal lpString As String, ByVal nCount As Long, lpDx As Long) As Long
    Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function DrawIconEx Lib "user32" (ByVal hDC As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long
    Private Declare Function DeleteDC Lib "gdi32" (ByVal hDC As Long) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    Private Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
    Private Declare Function DestroyCursor Lib "user32" (ByVal hCursor As Long) As Long
    Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
    Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
    Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long
    Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
    Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
    Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
    Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
    Private Declare Function GdiplusStartup Lib "gdiplus" (token As Long, inputbuf As GDIP_STARTUPINPUT, Optional ByVal outputbuf As Long = 0) As Long
    Private Declare Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Long
    Private Declare Function GdipCreateBitmapFromHBITMAP Lib "gdiplus" (ByVal hbm As Long, ByVal hPal As Long, BITMAP As Long) As Long
    Private Declare Function GdipCreateHICONFromBitmap Lib "gdiplus" (ByVal BITMAP As Long, ByRef hbmReturn As Long) As Long
    Private Declare Function GdipDisposeImage Lib "gdiplus" (ByVal Image As Long) As Long
    Private Declare Function GdipGetImageThumbnail Lib "gdiplus" (ByVal Image As Long, ByVal thumbWidth As Long, ByVal thumbHeight As Long, thumbImage As Long, ByVal CallBack As Long, ByVal callbackData As Long) As Long
  
    Private hForm As Long, hFormMenu As Long
#End If

Private oItemDataCollection As Collection, BMPsCollection As Collection, MenusCollection As Collection
Private oForm As Object, oSrcDataRange As Range
Private bMenuExpanded As Boolean, lTotalMenuItems As Long




'___________________________________________CLASS PUBLIC MEMBERS_________________________________________________________

Public Sub AddMenu( _
    ByVal Form As Object, _
    ByVal SourceData As Range _
    )

    Const MFS_POPUP = &H10&
    Const MFS_STRING = &H0&
    Const MIIM_ID = &H2
    Const MIIM_DATA = &H20
    Const MIIM_BITMAP = &H80
    Const MF_BYCOMMAND = &H0&
    Const IMAGE_ICON = 1
    Const IMAGE_BITMAP = 0
    Const LR_LOADFROMFILE = &H10

    #If Win64 Then
        Dim hParentMenu As LongLong
        Dim hNewMenu As LongLong
        Dim hTmpImgPtr As LongLong
        Dim hTmpPtr As LongLong
        Dim hImage As LongLong
    #Else
        Dim hParentMenu As Long
        Dim hNewMenu As Long
        Dim hTmpImgPtr As Long
        Dim hTmpPtr As Long
        Dim hImage As Long
    #End If

    Dim ImgType As IMAGE_TYPE
    Dim MII As MENUITEMINFO
    Dim oStdPic As StdPicture
    Dim oCell As Range
    Dim MenuLevel As Long, NextLevel As Long
    Dim sCaption As String
    Dim sMenuItemInfo As String
    Dim ImagePathOrFaceID As String
    Dim ImgSize As Long
    Dim TmpImagePathName As String
    Dim lCount As Long
    Dim StateFlags As Long
    Dim bItem As Boolean
  
  
    lTotalMenuItems = 0
    bMenuExpanded = False
    Set oForm = Form:   Set oSrcDataRange = SourceData
    lHookedFormsCount = lHookedFormsCount + 1
  
    Call IUnknown_GetWindow(Form, VarPtr(hForm))
    hFormMenu = CreateMenu
    Call SetMenu(hForm, hFormMenu)
    StateFlags = MIIM_ID Or MIIM_DATA
  
    For Each oCell In SourceData.Columns(1).Cells
  
        If MenusCollection Is Nothing Then
            Set MenusCollection = New Collection
        End If

        MenuLevel = Len(Replace(oCell.Text, "|", ""))
        NextLevel = Len(Replace(oCell.Offset(1).Text, "|", ""))
      
        If Not IsEmpty(oCell) Then
            sCaption = oCell.Offset(, 1).Text
            ImagePathOrFaceID = FindImagePath(oCell.Offset(, 2).Text)
            ImgSize = Val(oCell.Offset(, 3).Text)
            If ImgSize < 16 Then ImgSize = 16
          
            If Len(ImagePathOrFaceID) Then
                If UCase(Right(ImagePathOrFaceID, 4)) = ".BMP" Then
                    ImgType = Bmp
                ElseIf UCase(Right(ImagePathOrFaceID, 4)) = ".ANI" Then
                    ImgType = ANI
                ElseIf UCase(Right(ImagePathOrFaceID, 4)) = ".ICO" Then
                    ImgType = ICO
                ElseIf Val(ImagePathOrFaceID) Then
                    ImgType = FACE_ID
                End If
              
                Select Case ImgType
                    Case Bmp
                        hImage = LoadImage(0, ImagePathOrFaceID, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
                        hTmpPtr = BitmapToIcon(hImage, ImgSize)
                        hTmpImgPtr = IconToBitmap(hTmpPtr, ImgType, ImgSize)
                        Call DeleteObject(hImage)
                        Call DestroyIcon(hTmpPtr)
                    Case ICO, ANI
                        hImage = LoadImage(0, ImagePathOrFaceID, IMAGE_ICON, 0, 0, LR_LOADFROMFILE)
                        hTmpImgPtr = IconToBitmap(hImage, ImgType, ImgSize)
                        Call DestroyCursor(hImage)
                        Call DestroyIcon(hImage)
                    Case FACE_ID
                        If Len(ImagePathOrFaceID) Then
                            Set oStdPic = PicFromBmp(CLng(ImagePathOrFaceID))
                            If Not oStdPic Is Nothing Then
                                TmpImagePathName = Environ("TEMP") & Application.PathSeparator & "TempFaceID.bmp"
                                Call stdole.SavePicture(oStdPic, TmpImagePathName)
                                hImage = LoadImage(0, TmpImagePathName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
                                hTmpPtr = BitmapToIcon(hImage, ImgSize)
                                hTmpImgPtr = IconToBitmap(hTmpPtr, ImgType, ImgSize)
                                Call DeleteObject(hTmpPtr)
                                Call DestroyIcon(hTmpPtr)
                                Call DeleteObject(hImage)
                                Call Kill(TmpImagePathName)
                            End If
                            Set oStdPic = Nothing
                        End If
                End Select
              
            Else
                hTmpImgPtr = 0
            End If
          
            If hTmpImgPtr Then
                StateFlags = StateFlags Or MIIM_BITMAP
            End If
          
            If MenuLevel < NextLevel Then
                hNewMenu = CreateMenu
                MenusCollection.Add hNewMenu, CStr(Replace(oCell.Text, "|", ""))
                hParentMenu = GetParentMenu(Replace(oCell.Text, "|", ""))
                Call AppendMenu(hParentMenu, MFS_POPUP, hNewMenu, sCaption)
                bItem = False
            Else
                hParentMenu = GetParentMenu(Replace(oCell.Text, "|", ""))
                Call AppendMenu(hParentMenu, MFS_STRING, lCount, sCaption)
                bItem = True
            End If
          
            With MII
                .cbSize = LenB(MII)
                .fMask = StateFlags
                sMenuItemInfo = (hParentMenu & "||" & Replace(sCaption, "&", "") & "||" & oCell.Text)
                If oItemDataCollection Is Nothing Then
                    Set oItemDataCollection = New Collection
                End If
                oItemDataCollection.Add sMenuItemInfo, CStr(lCount + 1)
                .wID = lCount
                .dwItemData = lCount + 1
                .hbmpItem = hTmpImgPtr
                Call SetMenuItemInfo(hParentMenu, IIf(bItem = False, CLng(hNewMenu), lCount), MF_BYCOMMAND, MII)
            End With
            lCount = lCount + 1
        End If

    Next oCell

    lTotalMenuItems = lCount
    Call DrawMenuBar(hForm)
    Call SetHooks(hForm)

End Sub

Public Function IsMenuExpanded() As Boolean
    IsMenuExpanded = bMenuExpanded
End Function

Public Sub EnableMenuItem(ByVal MenuItemPos As Long, Optional ByVal bEnable As Boolean = True)
    Const MF_BYCOMMAND = &H0&
    Const MF_BYPOSITION = &H400&
    Const MF_ENABLED = &H0
    Const MF_GRAYED = &H1
    If oForm Is Nothing Then Exit Sub
    Call EnableMenuItemAPI(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND + IIf(bEnable, MF_ENABLED, MF_GRAYED))
    Call DrawMenuBar(hForm)
End Sub

Public Sub DeleteMenuItem(ByVal MenuItemPos As Long)
    Const MF_BYCOMMAND = &H0&
    If oForm Is Nothing Then Exit Sub
    Call DeleteMenu(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND)
    Call DrawMenuBar(hForm)
End Sub

Public Property Get TotalMenuItems() As Long
    If oForm Is Nothing Then Exit Sub
    TotalMenuItems = lTotalMenuItems
End Property

Public Property Get MenusCount() As Long
    If oForm Is Nothing Then Exit Sub
    MenusCount = MenusCollection.Count
End Property


Public Property Get MainMenusCount() As Long
    If oForm Is Nothing Then Exit Property
    MainMenusCount = GetMenuItemCountAPI(GetMenu(hForm))
End Property

Public Property Get IsMenuItemEnabled(ByVal MenuItemPos As Long) As Boolean
    Const MF_GRAYED = &H1
    Const MF_BYCOMMAND = &H0&
    If oForm Is Nothing Then Exit Property
    If (GetMenuState(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND) And MF_GRAYED) = 0 Then
        IsMenuItemEnabled = True
    End If
End Property


Public Property Get IsMenuItemMenu(ByVal MenuItemPos As Long) As Boolean
    Const MF_POPUP = &H10&
    Const MF_BYCOMMAND = &H0&
    If oForm Is Nothing Then Exit Property
    If (GetMenuState(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND) And MF_POPUP) <> 0 Then
        IsMenuItemMenu = True
    End If
End Property


Public Property Get MenuItemCaption(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Dim sBuffer  As String * 256, lRet As Long
  
    If oForm Is Nothing Then Exit Property
    lRet = GetMenuString(hFormMenu, MenuItemPos - 1, sBuffer, Len(sBuffer), MF_BYCOMMAND)
    MenuItemCaption = Left(sBuffer, lRet)
End Property



'_______________________________________CLASS PRIVATE ROUTINES___________________________________________

Private Function MenuItemCaptionByCommand(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Const MF_BYPOSITION = &H400&
    Dim sBuffer  As String * 256, lRet As Long
  
    lRet = GetMenuString(hFormMenu, MenuItemPos, sBuffer, Len(sBuffer), MF_BYCOMMAND)
    MenuItemCaptionByCommand = Left(sBuffer, lRet)
End Function

Private Function GetMenuItemIDFromItemPos(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Const MIIM_DATA = &H20
    Dim MII As MENUITEMINFO
  
    On Error Resume Next
    With MII
        .cbSize = LenB(MII)
        .fMask = MIIM_DATA
    End With
    Call GetMenuItemInfo(hFormMenu, MenuItemPos, MF_BYCOMMAND, MII)
    GetMenuItemIDFromItemPos = Split(oItemDataCollection(MII.dwItemData), "||")(2)
End Function


#If Win64 Then
    Private Function GetParentMenu(ByVal CellText As String) As LongLong
#Else
    Private Function GetParentMenu(ByVal CellText As String) As Long
#End If
    Dim sParent As String
  
    On Error Resume Next
    If Len(CellText) Then
        If Len(CellText) = 1 Then
            GetParentMenu = hFormMenu
        Else
            sParent = Left(CellText, Len(CellText) - 1)
        End If
        GetParentMenu = MenusCollection(sParent)
    End If
End Function

Private Sub ShowSystemMenu(ByVal bShow As Boolean)
    Const MF_BYPOSITION = &H400&
    Call RemoveMenu(GetSystemMenu(hForm, CLng(bShow)), 1, MF_BYPOSITION)
    Call RemoveMenu(GetSystemMenu(hForm, CLng(bShow)), 2, MF_BYPOSITION)
    Call RemoveMenu(GetSystemMenu(hForm, CLng(bShow)), 3, MF_BYPOSITION)
End Sub

#If Win64 Then
    Private Sub SetHooks(ByVal hwnd As LongLong)
        Dim hHook As LongLong
#Else
    Private Sub SetHooks(ByVal hwnd As Long)
        Dim hHook As Long
#End If
    Const WH_CALLWNDPROC = 4
    Call SetWindowSubclass(hwnd, WinProcAddr, ObjPtr(Me))
    If oAllClasses Is Nothing Then
        Set oAllClasses = New Collection
        Set oAllForms = New Collection
    End If
    oAllClasses.Add ObjPtr(Me)
    oAllForms.Add hwnd
    Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
    hHook = SetWindowsHookEx(WH_CALLWNDPROC, HookProcAddr, _
    GetModuleHandle(vbNullString), GetWindowThreadProcessId(Application.hwnd, 0))
    Call SetProp(Application.hwnd, "Hook", hHook)
End Sub

Private Function FindImagePath(ByVal ImgPath As String) As String
    Dim sTemp As String
  
    Select Case True
        Case IsValidFaceID(ImgPath)
            FindImagePath = ImgPath
        Case Len(Dir(ImgPath))
            FindImagePath = ImgPath
        Case Len(ImgPath)
            sTemp = ThisWorkbook.Path & Application.PathSeparator & ImgPath
            On Error Resume Next
                If Len(Dir(sTemp)) Then
                    If Err.Number = 0 Then
                        FindImagePath = sTemp
                    End If
            On Error GoTo 0
            End If
    End Select
End Function

Private Function IsValidFaceID(ByVal FaceID As String) As Boolean
    On Error Resume Next
    IsValidFaceID = Not (Application.CommandBars.FindControl(ID:=CLng(FaceID)) Is Nothing)
End Function

#If Win64 Then
    Private Function PicFromBmp( _
        ByVal FaceID As Long _
    ) As StdPicture
        Dim hBmpPtr As LongLong, hCopyBmpPtr As LongLong
#Else
    Private Function PicFromBmp( _
        ByVal FaceID As Long _
    ) As StdPicture
        Dim hBmpPtr As Long, hCopyBmpPtr As Long
#End If

    Const IMAGE_BITMAP = 0
    Const PICTYPE_BITMAP = 1
    Const LR_COPYRETURNORG = &H4
    Const CF_BITMAP = 2
    Const S_OK = &H0&
    Dim IID_IDISPATCH As GUID, uPicInfo As uPicDesc
    Dim IPic As Object, lRet As Long
  
    On Error GoTo errHandler
    Application.CommandBars.FindControl(ID:=FaceID).CopyFace
    Call OpenClipboard(0)
    hBmpPtr = GetClipboardData(CF_BITMAP)
    hCopyBmpPtr = CopyImage(hBmpPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)
    Call DeleteObject(hBmpPtr)

    If hBmpPtr Then
        With IID_IDISPATCH
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        With uPicInfo
            .Size = Len(uPicInfo)
            .Type = PICTYPE_BITMAP
            .hPic = hCopyBmpPtr
            .hPal = 0
        End With
        lRet = OleCreatePictureIndirect(uPicInfo, IID_IDISPATCH, True, IPic)
        If lRet = S_OK Then
            Set PicFromBmp = IPic
        End If
    End If
  
errHandler:
    Call EmptyClipboard
    Call CloseClipboard
  
    If BMPsCollection Is Nothing Then _
            Set BMPsCollection = New Collection
            Call BMPsCollection.Add(hBmpPtr)
End Function


#If Win64 Then
    Private Function BitmapToIcon( _
        ByVal Bmp As LongLong, _
        ImageSize As Long _
    ) As LongLong
        Dim lBitmap As LongLong, lThumb As LongLong
#Else
    Private Function BitmapToIcon( _
        ByVal Bmp As Long, _
        ImageSize As Long _
    ) As Long
        Dim lBitmap As Long, lThumb As Long
#End If

    Const S_OK = 0&
    Dim tSI As GDIP_STARTUPINPUT
    Dim lRes As Long
    Dim lGDIP As Long
  
    On Error GoTo xit
    tSI.GdiplusVersion = 1
    lRes = GdiplusStartup(lGDIP, tSI)
    If lRes = S_OK Then
        lRes = GdipCreateBitmapFromHBITMAP(Bmp, 0, lBitmap)
        If lRes = S_OK Then
            lRes = GdipGetImageThumbnail(lBitmap, ImageSize, ImageSize, lThumb, 0, 0)
            If lRes = S_OK Then
                lRes = GdipCreateHICONFromBitmap(lThumb, BitmapToIcon)
            End If
        End If
    End If
  
xit:
    Call GdipDisposeImage(lBitmap)
    Call GdipDisposeImage(lThumb)
    Call GdiplusShutdown(lGDIP)
  
End Function


#If Win64 Then
    Private Function IconToBitmap( _
        ByVal hImage As LongLong, _
        ByVal ImageType As IMAGE_TYPE, _
        ByVal ImageSize As Long _
    ) As LongLong
        Dim hDC As LongLong, hCompatDc As LongLong
#Else
    Private Function IconToBitmap( _
        ByVal hImage As Long, _
        ByVal ImageType As IMAGE_TYPE, _
        ByVal ImageSize As Long _
    ) As Long
        Dim hDC As Long, hCompatDc As Long
#End If

    Const TRANSPARENT = 1
    Const SM_CXMENUCHECK = 71
    Const ETO_OPAQUE = 2
    Const DI_NORMAL = &H3&
    Const COLOR_MENU = 4
  
    Dim RECT As RECT
  
    If BMPsCollection Is Nothing Then _
            Set BMPsCollection = New Collection
  
    If ImageSize = 0 Then _
            ImageSize = GetSystemMetrics(SM_CXMENUCHECK)
  
    hDC = GetDC(hForm)
    With RECT
        .Right = ImageSize
        .Bottom = ImageSize
        hCompatDc = CreateCompatibleDC(0)
        IconToBitmap = CreateCompatibleBitmap(hDC, .Right, .Bottom)
        Call SelectObject(hCompatDc, IconToBitmap)
        Call SetBkMode(hCompatDc, TRANSPARENT)
        Call SetBkColor(hCompatDc, GetSysColor(COLOR_MENU))
        Call ExtTextOut(hCompatDc, 0, 0, ETO_OPAQUE, RECT, vbNullString, 0, 0)
        Call DrawIconEx(hCompatDc, 0, 0, hImage, .Right, .Bottom, 0, 0, DI_NORMAL)
        Call DeleteDC(hCompatDc)
    End With
    Call ReleaseDC(hForm, hDC)
  
    Call BMPsCollection.Add(IconToBitmap)
  
End Function

Private Sub CleanUp()
    Dim i As Long
  
    On Error Resume Next
    If Not BMPsCollection Is Nothing Then
        With BMPsCollection
            For i = .Count To 1 Step -1
                Call DeleteObject(.Item(i))
                .Remove i
            Next i
        End With
    End If
      
    If Not MenusCollection Is Nothing Then
        With MenusCollection
            For i = .Count To 1 Step -1
                Call DestroyMenu(.Item(i))
                .Remove i
            Next i
        End With
    End If
  
    Set BMPsCollection = Nothing
    Set MenusCollection = Nothing
    Call DestroyMenu(hFormMenu)
    Set oForm = Nothing
    Set oSrcDataRange = Nothing
    Set oItemDataCollection = Nothing
    Set oInterface = Nothing
  
End Sub


#If Win64 Then
    Private Function LoWord(ByVal Word As LongLong) As Integer
#Else
    Private Function LoWord(ByVal Word As Long) As Integer
#End If
    Dim x As LongToInteger
    CopyMemory x, Word, LenB(x)
    LoWord = x.Low
End Function

#If Win64 Then
    Private Function HiWord(ByVal Word As LongLong) As Integer
#Else
    Private Function HiWord(ByVal Word As Long) As Integer
#End If
    Dim x As LongToInteger
    CopyMemory x, Word, LenB(x)
    HiWord = x.High
End Function


'___________________________________________INTERFACE ROUTINES_________________________________________________________

#If Win64 Then
    Private Function ISecret_SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong _
    ) As LongLong
#Else
    Private Function ISecret_SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long) As Long
#End If

    Const WM_CREATE = &H1
    Dim tTmpCWP As CWPSTRUCT
    Dim sBuff As String * 256

    Call CopyMemory(tTmpCWP, ByVal lParam, LenB(tTmpCWP))
    If tTmpCWP.Message = WM_CREATE Then
        sBuff = Left(sBuff, GetWindowText(tTmpCWP.hwnd, ByVal sBuff, 256))
        Call ZeroMemory(tTmpCWP, LenB(tTmpCWP))
        If InStr(1, sBuff, "Microsoft Visual Basic") Then    '<==Language dependent
            Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
            Call OnError_RemoveAllWindows_Subclass
        End If
    End If
    ISecret_SafeExitHook = CallNextHookEx(GetProp(Application.hwnd, "Hook"), ncode, wParam, ByVal lParam)

End Function

#If Win64 Then
    Private Function ISecret_WndProc( _
        ByVal hwnd As LongLong, _
        ByVal wMsg As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong, _
        ByVal uIdSubclass As Object, _
        ByVal This As LongLong _
    ) As LongLong
#Else
    Private Function ISecret_WndProc( _
        ByVal hwnd As Long, _
        ByVal wMsg As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long, _
        ByVal uIdSubclass As Object, _
        ByVal This As Long _
    ) As Long
#End If

    Const MF_BYPOSITION = &H400&
    Const MF_BYCOMMAND = &H0&
    Const MF_POPUP = &H10&
    Const MF_GRAYED = &H1
    Const MIIM_ID = &H2
    Const MIIM_STATE = &H1
    Const WM_MENUSELECT = &H11F&
    Const WM_ENTERIDLE = &H121
    Const WM_COMMAND = &H111
    Const WM_EXITMENULOOP = &H212
    Const WM_DESTROY As Long = &H2
  
    Dim MII As MENUITEMINFO
    Dim sBuff As String * 256, sCaption As String, lRet As Long
 
    Select Case wMsg
        Case WM_ENTERIDLE
            bMenuExpanded = True
            Call ShowSystemMenu(False)
        Case WM_EXITMENULOOP
            bMenuExpanded = False
            Call ShowSystemMenu(True)
            RaiseEvent MenuItemHighlight("", "", 0, False)
        Case WM_COMMAND
            lRet = GetMenuString(lParam, LoWord(wParam), sBuff, Len(sBuff), MF_BYCOMMAND)
            sCaption = Replace(Left(MenuItemCaptionByCommand(LoWord(wParam)), _
                Len(MenuItemCaptionByCommand(LoWord(wParam)))), "&", "")
            RaiseEvent MenuItemSelect(sCaption, GetMenuItemIDFromItemPos(LoWord(wParam)), LoWord(wParam) + 1)
        Case WM_MENUSELECT
            bMenuExpanded = True
            lRet = GetMenuString(lParam, LoWord(wParam), sBuff, Len(sBuff), MF_BYCOMMAND)
            sCaption = Replace(Left(sBuff, lRet), "&", "")
            With MII
                .cbSize = LenB(MII)
                .fMask = MIIM_STATE Or MIIM_ID
            End With
            If Not (HiWord(wParam) And MF_POPUP) = MF_POPUP Then
                Call GetMenuItemInfo(lParam, LoWord(wParam), MF_BYCOMMAND, MII)
                RaiseEvent MenuItemHighlight(sCaption, GetMenuItemIDFromItemPos(LoWord(wParam)), _
                     LoWord(wParam) + 1, Not CBool(MII.fState And MF_GRAYED))
            End If
            If (HiWord(wParam) And MF_POPUP) = MF_POPUP Then
                Call GetMenuItemInfo(lParam, LoWord(wParam), MF_BYPOSITION, MII)
                lRet = GetMenuString(lParam, LoWord(wParam), sBuff, Len(sBuff), MF_BYPOSITION)
                sCaption = Replace(Left(sBuff, lRet), "&", "")
                RaiseEvent MenuItemHighlight(sCaption, GetMenuItemIDFromItemPos(MII.wID), _
                    MII.wID + 1, Not CBool(MII.fState And MF_GRAYED))
            End If
        Case WM_DESTROY
            Call RemoveWindowSubclass(hwnd, WinProcAddr, ObjPtr(uIdSubclass))
            Call CleanUp
    End Select
  
    ISecret_WndProc = DefSubclassProc(hwnd, wMsg, wParam, lParam)
 
End Function


'___________________________________________CLASS TERMINATE________________________________________________________

Private Sub Class_Terminate()
    lHookedFormsCount = lHookedFormsCount - 1
    If lHookedFormsCount = 0 Then
        Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
        Call RemoveProp(Application.hwnd, "Hook")
        Debug.Print "Hooks properly released."
    End If
End Sub


2- Interface Module (ISecret)
VBA Code:
Option Explicit


#If Win64 Then
    Public Function WndProc( _
            ByVal hwnd As LongLong, _
            ByVal wMsg As Long, _
            ByVal wParam As LongLong, _
            ByVal lParam As LongLong, _
            ByVal uIdSubclass As Object, _
            ByVal This As LongLong) As LongLong
#Else
    Public Function WndProc( _
            ByVal hwnd As Long, _
            ByVal wMsg As Long, _
            ByVal wParam As Long, _
            ByVal lParam As Long, _
            ByVal uIdSubclass As Object, _
            ByVal This As Long) As Long
#End If
'
End Function


#If Win64 Then
    Public Function SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong _
    ) As LongLong
#Else
    Public Function SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long _
    ) As Long
#End If
'
End Function


3- Helper Standard module (bas_Delegate)
VBA Code:
Option Explicit

Public oInterface As ISecret
Public oAllClasses As Collection
Public oAllForms As Collection
Public lHookedFormsCount As Long

#If VBA7 Then
    Private Declare PtrSafe Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr) As LongPtr
#Else
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
#End If



#If Win64 Then
    Public Function WndProcDelg( _
            ByVal hwnd As LongLong, _
            ByVal wMsg As Long, _
            ByVal wParam As LongLong, _
            ByVal lParam As LongLong, _
            ByVal uIdSubclass As Object, _
            ByVal This As LongLong) As LongLong
#Else
    Public Function WndProcDelg( _
            ByVal hwnd As Long, _
            ByVal wMsg As Long, _
            ByVal wParam As Long, _
            ByVal lParam As Long, _
            ByVal uIdSubclass As Object, _
            ByVal This As Long) As Long
#End If

    Set oInterface = uIdSubclass
    WndProcDelg = CallByName(oInterface, "WndProc", VbMethod, hwnd, wMsg, wParam, lParam, uIdSubclass, This)

End Function

#If Win64 Then
    Public Function SafeExitHookDelg( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong _
    ) As LongLong
#Else
    Public Function SafeExitHookDelg( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long _
    ) As Long
#End If

    If Not oInterface Is Nothing Then
        SafeExitHookDelg = CallByName(oInterface, "SafeExitHook", VbMethod, ncode, wParam, lParam)
    End If

End Function

Public Sub OnError_RemoveAllWindows_Subclass(Optional ByVal Dummy As Boolean)
    Dim i As Long
  
    On Error Resume Next
    With oAllClasses
        For i = .Count To 1 Step -1
            Call RemoveWindowSubclass(oAllForms.Item(i), WinProcAddr, .Item(i))
        Next i
    End With
    Set oInterface = Nothing
    Set oAllClasses = Nothing
    Set oAllForms = Nothing
End Sub

#If Win64 Then
    Public Function WinProcAddr() As LongLong
        WinProcAddr = VBA.CLngLng(AddressOf WndProcDelg)
    #Else
    Public Function WinProcAddr() As Long
        WinProcAddr = VBA.CLng(AddressOf WndProcDelg)
    #End If
End Function

#If Win64 Then
    Public Function HookProcAddr() As LongLong
        HookProcAddr = VBA.CLngLng(AddressOf SafeExitHookDelg)
    #Else
    Public Function HookProcAddr() As Long
        HookProcAddr = VBA.CLng(AddressOf SafeExitHookDelg)
    #End If
End Function


I have tested the code in x32bit and 64bit. If anyone finds any bugs or has any suggestions, please let me know.

Regards.
 
Upvote 0

Excel Facts

Quick Sum
Select a range of cells. The total appears in bottom right of Excel screen. Right-click total to add Max, Min, Count, Average.
De
Ok- Here is the entire project for Worksheet-Based Menus that can be added to Multiple Modal UserForms.

Download Workbook Example Here

The code uses a single Class (CMenu) which has some handy Properties and Methods as shown in the picture below and it also provides two Events (MenuItemSelect and MenuItemHighlight)

View attachment 64256

The code is now supposed to be working with multiple modal forms because it resides in a class module. Each new menu instance corresponds to a new class instance.

I have also added code to properly allow menu keyboard navigation.

Again, as I mentioned in my previous post, no erros are allowed inside the two event handler routines. ( Errors elsewhere in the code are hopefully taken care of )... So please, don't forget to apply proper defensive error handling or use correct code or else, you will see a nasty crashing before your eyes.






Proceedings:

Creating the menu is very simple as shown in the following example:

In the UserForm Module :
VBA Code:
Option Explicit

Private WithEvents MenuInstance As CMenu

Public Sub UserForm_Activate()
    Call CreateMenu
End Sub

Private Sub CreateMenu()
    Set MenuInstance = New CMenu
    MenuInstance.AddMenu Me, Sheet1.Range("H5:K18")
End Sub


'__________________________________________ MENU EVENTS ______________________________________________________

Private Sub MenuInstance_MenuItemHighlight( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenuItemPos As Long, _
    ByVal MenuItemEnabled As Boolean _
)

'    '=================================================
'    ' IMPORTANT NOTE!!:
'      Errors not allowed inside this event handler routine.
'      Propper Error Handling Is Advised.
'    '==================================================

    If MenuInstance.IsMenuExpanded Then
        LblMenuItemCaption = MenuItemCaption
        LblMenuItemID = MenuItemID
        LblMenuItemPos = IIf(MenuItemPos, MenuItemPos, "")
        LblMenuItemState = IIf(MenuItemEnabled, "Enabled", "Disabled")
    Else
        LblMenuItemCaption = ""
        LblMenuItemID = ""
        LblMenuItemPos = ""
        LblMenuItemState = ""
    End If

End Sub

Private Sub MenuInstance_MenuItemSelect( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenItemPos As Long _
)

'    '=================================================
'    ' IMPORTANT NOTE!!:
'      Errors not allowed inside this event handler routine.
'      Propper Error Handling Is Advised.
'    '==================================================

    MsgBox "You selected : " & vbNewLine & vbNewLine & _
            "Menu Item Caption : " & MenuItemCaption & vbNewLine & _
            "Menu Item ID : " & MenuItemID & vbNewLine & _
            "Menu Item Position : " & MenItemPos
End Sub




Following are the three other necessary vbproject modules. The user must leave these three modules untouched


1
- Class Module (CMenu)
VBA Code:
Option Explicit

Implements ISecret

Public Event MenuItemSelect( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenItemPos As Long _
)

Public Event MenuItemHighlight( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenuItemPos As Long, _
    ByVal MenuItemEnabled As Boolean _
)

Private Enum IMAGE_TYPE
    ICO
    Bmp
    ANI
    FACE_ID '<== https://bettersolutions.com/vba/ribbon/face-ids-2003.htm
End Enum

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Type LongToInteger
    Low As Integer
    High As Integer
End Type

Private Type MENUITEMINFO
    cbSize As Long
    fMask As Long
    fType As Long
    fState As Long
    wID As Long
    #If Win64 Then
        hSubMenu As LongLong
        hbmpChecked As LongLong
        hbmpUnchecked As LongLong
        dwItemData As LongLong
        dwTypeData As String
        cch As Long
        '#if(WINVER >= 0x0500)
        hbmpItem As LongLong
        '#endif /* WINVER >= 0x0500 */
    #Else
        hSubMenu As Long
        hbmpChecked As Long
        hbmpUnchecked As Long
        dwItemData As Long
        dwTypeData As String
        cch As Long
        '#if(WINVER >= 0x0500)
        hbmpItem As Long
        '#endif /* WINVER >= 0x0500 */
    #End If
End Type

Private Type uPicDesc
    Size As Long
    Type As Long
    #If Win64 Then
        hPic As LongLong
        hPal As LongLong
    #Else
        hPic As Long
        hPal As Long
    #End If
End Type

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Type Msg
    #If Win64 Then
        hwnd As LongLong
        Message As Long
        wParam As LongLong
        lParam As LongLong
    #Else
        hwnd As Long
        Message As Long
        wParam As Long
        lParam As Long
    #End If
    time As Long
    PT As POINTAPI
End Type

Private Type CWPSTRUCT
    #If Win64 Then
        lParam As LongLong
        wParam As LongLong
        Message As Long
        hwnd As LongLong
    #Else
        lParam As Long
        wParam As Long
        Message As Long
        hwnd As Long
    #End If
End Type

'GDI+
Private Type GDIP_STARTUPINPUT
   GdiplusVersion As Long
  #If Win64 Then
        DebugEventCallback As LongLong
        SuppressBackgroundThread As LongLong
  #Else
        DebugEventCallback As Long
        SuppressBackgroundThread As Long
  #End If
   SuppressExternalCodecs As Long
End Type

#If VBA7 Then
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal numBytes As LongPtr)
    Private Declare PtrSafe Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hwnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr, Optional ByVal dwRefData As LongPtr) As LongPtr
    Private Declare PtrSafe Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr) As LongPtr
    Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUF As LongPtr) As Long
    Private Declare PtrSafe Function GetMenu Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetMenu Lib "user32" (ByVal hwnd As LongPtr, ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function CreateMenu Lib "user32" () As LongPtr
    Private Declare PtrSafe Function CreatePopupMenu Lib "user32" () As LongPtr
    Private Declare PtrSafe Function AppendMenu Lib "user32" Alias "AppendMenuA" (ByVal hMenu As LongPtr, ByVal wFlags As Long, ByVal wIDNewItem As LongPtr, ByVal lpNewItem As Any) As Long
    Private Declare PtrSafe Function DestroyMenu Lib "user32" (ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function DeleteMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function DrawMenuBar Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function RemoveMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As LongPtr, ByVal un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
    Private Declare PtrSafe Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" (ByVal hMenu As LongPtr, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long
    Private Declare PtrSafe Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As LongPtr, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
    Private Declare PtrSafe Function GetMenuState Lib "user32" (ByVal hMenu As LongPtr, ByVal wID As Long, ByVal wFlags As Long) As Long
    Private Declare PtrSafe Function GetSystemMenu Lib "user32" (ByVal hwnd As LongPtr, ByVal bRevert As Long) As LongPtr
    Private Declare PtrSafe Function EnableMenuItemAPI Lib "user32" Alias "EnableMenuItem" (ByVal hMenu As LongPtr, ByVal wIDEnableItem As Long, ByVal wEnable As Long) As Long
    Private Declare PtrSafe Function GetMenuItemCountAPI Lib "user32" Alias "GetMenuItemCount" (ByVal hMenu As LongPtr) As Long
    Private Declare PtrSafe Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr
    Private Declare PtrSafe Function CopyImage Lib "user32" (ByVal handle As LongPtr, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr
    Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
    Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
    Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleAut32.dll" (PicDesc As uPicDesc, RefIID As Any, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDC As LongPtr) As Long
    Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As LongPtr) As LongPtr
    Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
    Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hDC As LongPtr, ByVal hObject As LongPtr) As LongPtr
    Private Declare PtrSafe Function SetBkMode Lib "gdi32" (ByVal hDC As LongPtr, ByVal nBkMode As Long) As Long
    Private Declare PtrSafe Function SetBkColor Lib "gdi32" (ByVal hDC As LongPtr, ByVal crColor As Long) As Long
    Private Declare PtrSafe Function ExtTextOut Lib "gdi32" Alias "ExtTextOutA" (ByVal hDC As LongPtr, ByVal x As Long, ByVal y As Long, ByVal wOptions As Long, lpRect As RECT, ByVal lpString As String, ByVal nCount As Long, lpDx As Long) As Long
    Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function DrawIconEx Lib "user32" (ByVal hDC As LongPtr, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As LongPtr, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As LongPtr, ByVal diFlags As Long) As Long
    Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDC As LongPtr) As Long
    Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
    Private Declare PtrSafe Function DestroyIcon Lib "user32" (ByVal hIcon As LongPtr) As Long
    Private Declare PtrSafe Function DestroyCursor Lib "user32" (ByVal hCursor As LongPtr) As Long
    Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
    Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
    Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As Long
    Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
    Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long
    Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
    Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
    Private Declare PtrSafe Function GdiplusStartup Lib "gdiplus" (token As Long, inputbuf As GDIP_STARTUPINPUT, Optional ByVal outputbuf As Long = 0) As Long
    Private Declare PtrSafe Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Long
    Private Declare PtrSafe Function GdipCreateBitmapFromHBITMAP Lib "gdiplus" (ByVal hbm As LongPtr, ByVal hPal As LongPtr, BITMAP As LongPtr) As Long
    Private Declare PtrSafe Function GdipCreateHICONFromBitmap Lib "gdiplus" (ByVal BITMAP As LongPtr, ByRef hbmReturn As LongPtr) As Long
    Private Declare PtrSafe Function GdipDisposeImage Lib "gdiplus" (ByVal Image As LongPtr) As LongPtr
    Private Declare PtrSafe Function GdipGetImageThumbnail Lib "gdiplus" (ByVal Image As LongPtr, ByVal thumbWidth As Long, ByVal thumbHeight As Long, thumbImage As LongPtr, ByVal CallBack As LongPtr, ByVal callbackData As LongPtr) As Long
 
    Private hForm As LongPtr, hFormMenu As LongPtr

#Else
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal numBytes As Long)
    Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUF As Long) As Long
    Private Declare Function GetMenu Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function SetMenu Lib "user32" (ByVal hwnd As Long, ByVal hMenu As Long) As Long
    Private Declare Function CreateMenu Lib "user32" () As Long
    Private Declare Function CreatePopupMenu Lib "user32" () As Long
    Private Declare Function AppendMenu Lib "user32" Alias "AppendMenuA" (ByVal hMenu As Long, ByVal wFlags As Long, ByVal wIDNewItem As Long, ByVal lpNewItem As Any) As Long
    Private Declare Function DestroyMenu Lib "user32" (ByVal hMenu As Long) As Long
    Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function RemoveMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
    Private Declare Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
    Private Declare Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal bool As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long
    Private Declare Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As Long, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
    Private Declare Function GetMenuState Lib "user32" (ByVal hMenu As Long, ByVal wID As Long, ByVal wFlags As Long) As Long
    Private Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Long, ByVal bRevert As Long) As Long
    Private Declare Function EnableMenuItemAPI Lib "user32" Alias "EnableMenuItem" (ByVal hMenu As Long, ByVal wIDEnableItem As Long, ByVal wEnable As Long) As Long
    Private Declare Function GetMenuItemCountAPI Lib "user32" Alias "GetMenuItemCount" (ByVal hMenu As Long) As Long
    Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
    Private Declare Function CopyImage Lib "user32" (ByVal handle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
    Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function CloseClipboard Lib "user32" () As Long
    Private Declare Function EmptyClipboard Lib "user32" () As Long
    Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
    Private Declare Function OleCreatePictureIndirect Lib "oleAut32.dll" (PicDesc As uPicDesc, RefIID As Any, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
    Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDC As Long) As Long
    Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As Long) As Long
    Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
    Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
    Private Declare Function SetBkMode Lib "gdi32" (ByVal hDC As Long, ByVal nBkMode As Long) As Long
    Private Declare Function SetBkColor Lib "gdi32" (ByVal hDC As Long, ByVal crColor As Long) As Long
    Private Declare Function ExtTextOut Lib "gdi32" Alias "ExtTextOutA" (ByVal hDC As Long, ByVal x As Long, ByVal y As Long, ByVal wOptions As Long, lpRect As RECT, ByVal lpString As String, ByVal nCount As Long, lpDx As Long) As Long
    Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function DrawIconEx Lib "user32" (ByVal hDC As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long
    Private Declare Function DeleteDC Lib "gdi32" (ByVal hDC As Long) As Long
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    Private Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
    Private Declare Function DestroyCursor Lib "user32" (ByVal hCursor As Long) As Long
    Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
    Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
    Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
    Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long
    Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
    Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
    Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
    Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
    Private Declare Function GdiplusStartup Lib "gdiplus" (token As Long, inputbuf As GDIP_STARTUPINPUT, Optional ByVal outputbuf As Long = 0) As Long
    Private Declare Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Long
    Private Declare Function GdipCreateBitmapFromHBITMAP Lib "gdiplus" (ByVal hbm As Long, ByVal hPal As Long, BITMAP As Long) As Long
    Private Declare Function GdipCreateHICONFromBitmap Lib "gdiplus" (ByVal BITMAP As Long, ByRef hbmReturn As Long) As Long
    Private Declare Function GdipDisposeImage Lib "gdiplus" (ByVal Image As Long) As Long
    Private Declare Function GdipGetImageThumbnail Lib "gdiplus" (ByVal Image As Long, ByVal thumbWidth As Long, ByVal thumbHeight As Long, thumbImage As Long, ByVal CallBack As Long, ByVal callbackData As Long) As Long
 
    Private hForm As Long, hFormMenu As Long
#End If

Private oItemDataCollection As Collection, BMPsCollection As Collection, MenusCollection As Collection
Private oForm As Object, oSrcDataRange As Range
Private bMenuExpanded As Boolean, lTotalMenuItems As Long




'___________________________________________CLASS PUBLIC MEMBERS_________________________________________________________

Public Sub AddMenu( _
    ByVal Form As Object, _
    ByVal SourceData As Range _
    )

    Const MFS_POPUP = &H10&
    Const MFS_STRING = &H0&
    Const MIIM_ID = &H2
    Const MIIM_DATA = &H20
    Const MIIM_BITMAP = &H80
    Const MF_BYCOMMAND = &H0&
    Const IMAGE_ICON = 1
    Const IMAGE_BITMAP = 0
    Const LR_LOADFROMFILE = &H10

    #If Win64 Then
        Dim hParentMenu As LongLong
        Dim hNewMenu As LongLong
        Dim hTmpImgPtr As LongLong
        Dim hTmpPtr As LongLong
        Dim hImage As LongLong
    #Else
        Dim hParentMenu As Long
        Dim hNewMenu As Long
        Dim hTmpImgPtr As Long
        Dim hTmpPtr As Long
        Dim hImage As Long
    #End If

    Dim ImgType As IMAGE_TYPE
    Dim MII As MENUITEMINFO
    Dim oStdPic As StdPicture
    Dim oCell As Range
    Dim MenuLevel As Long, NextLevel As Long
    Dim sCaption As String
    Dim sMenuItemInfo As String
    Dim ImagePathOrFaceID As String
    Dim ImgSize As Long
    Dim TmpImagePathName As String
    Dim lCount As Long
    Dim StateFlags As Long
    Dim bItem As Boolean
 
 
    lTotalMenuItems = 0
    bMenuExpanded = False
    Set oForm = Form:   Set oSrcDataRange = SourceData
    lHookedFormsCount = lHookedFormsCount + 1
 
    Call IUnknown_GetWindow(Form, VarPtr(hForm))
    hFormMenu = CreateMenu
    Call SetMenu(hForm, hFormMenu)
    StateFlags = MIIM_ID Or MIIM_DATA
 
    For Each oCell In SourceData.Columns(1).Cells
 
        If MenusCollection Is Nothing Then
            Set MenusCollection = New Collection
        End If

        MenuLevel = Len(Replace(oCell.Text, "|", ""))
        NextLevel = Len(Replace(oCell.Offset(1).Text, "|", ""))
     
        If Not IsEmpty(oCell) Then
            sCaption = oCell.Offset(, 1).Text
            ImagePathOrFaceID = FindImagePath(oCell.Offset(, 2).Text)
            ImgSize = Val(oCell.Offset(, 3).Text)
            If ImgSize < 16 Then ImgSize = 16
         
            If Len(ImagePathOrFaceID) Then
                If UCase(Right(ImagePathOrFaceID, 4)) = ".BMP" Then
                    ImgType = Bmp
                ElseIf UCase(Right(ImagePathOrFaceID, 4)) = ".ANI" Then
                    ImgType = ANI
                ElseIf UCase(Right(ImagePathOrFaceID, 4)) = ".ICO" Then
                    ImgType = ICO
                ElseIf Val(ImagePathOrFaceID) Then
                    ImgType = FACE_ID
                End If
             
                Select Case ImgType
                    Case Bmp
                        hImage = LoadImage(0, ImagePathOrFaceID, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
                        hTmpPtr = BitmapToIcon(hImage, ImgSize)
                        hTmpImgPtr = IconToBitmap(hTmpPtr, ImgType, ImgSize)
                        Call DeleteObject(hImage)
                        Call DestroyIcon(hTmpPtr)
                    Case ICO, ANI
                        hImage = LoadImage(0, ImagePathOrFaceID, IMAGE_ICON, 0, 0, LR_LOADFROMFILE)
                        hTmpImgPtr = IconToBitmap(hImage, ImgType, ImgSize)
                        Call DestroyCursor(hImage)
                        Call DestroyIcon(hImage)
                    Case FACE_ID
                        If Len(ImagePathOrFaceID) Then
                            Set oStdPic = PicFromBmp(CLng(ImagePathOrFaceID))
                            If Not oStdPic Is Nothing Then
                                TmpImagePathName = Environ("TEMP") & Application.PathSeparator & "TempFaceID.bmp"
                                Call stdole.SavePicture(oStdPic, TmpImagePathName)
                                hImage = LoadImage(0, TmpImagePathName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
                                hTmpPtr = BitmapToIcon(hImage, ImgSize)
                                hTmpImgPtr = IconToBitmap(hTmpPtr, ImgType, ImgSize)
                                Call DeleteObject(hTmpPtr)
                                Call DestroyIcon(hTmpPtr)
                                Call DeleteObject(hImage)
                                Call Kill(TmpImagePathName)
                            End If
                            Set oStdPic = Nothing
                        End If
                End Select
             
            Else
                hTmpImgPtr = 0
            End If
         
            If hTmpImgPtr Then
                StateFlags = StateFlags Or MIIM_BITMAP
            End If
         
            If MenuLevel < NextLevel Then
                hNewMenu = CreateMenu
                MenusCollection.Add hNewMenu, CStr(Replace(oCell.Text, "|", ""))
                hParentMenu = GetParentMenu(Replace(oCell.Text, "|", ""))
                Call AppendMenu(hParentMenu, MFS_POPUP, hNewMenu, sCaption)
                bItem = False
            Else
                hParentMenu = GetParentMenu(Replace(oCell.Text, "|", ""))
                Call AppendMenu(hParentMenu, MFS_STRING, lCount, sCaption)
                bItem = True
            End If
         
            With MII
                .cbSize = LenB(MII)
                .fMask = StateFlags
                sMenuItemInfo = (hParentMenu & "||" & Replace(sCaption, "&", "") & "||" & oCell.Text)
                If oItemDataCollection Is Nothing Then
                    Set oItemDataCollection = New Collection
                End If
                oItemDataCollection.Add sMenuItemInfo, CStr(lCount + 1)
                .wID = lCount
                .dwItemData = lCount + 1
                .hbmpItem = hTmpImgPtr
                Call SetMenuItemInfo(hParentMenu, IIf(bItem = False, CLng(hNewMenu), lCount), MF_BYCOMMAND, MII)
            End With
            lCount = lCount + 1
        End If

    Next oCell

    lTotalMenuItems = lCount
    Call DrawMenuBar(hForm)
    Call SetHooks(hForm)

End Sub

Public Function IsMenuExpanded() As Boolean
    IsMenuExpanded = bMenuExpanded
End Function

Public Sub EnableMenuItem(ByVal MenuItemPos As Long, Optional ByVal bEnable As Boolean = True)
    Const MF_BYCOMMAND = &H0&
    Const MF_BYPOSITION = &H400&
    Const MF_ENABLED = &H0
    Const MF_GRAYED = &H1
    If oForm Is Nothing Then Exit Sub
    Call EnableMenuItemAPI(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND + IIf(bEnable, MF_ENABLED, MF_GRAYED))
    Call DrawMenuBar(hForm)
End Sub

Public Sub DeleteMenuItem(ByVal MenuItemPos As Long)
    Const MF_BYCOMMAND = &H0&
    If oForm Is Nothing Then Exit Sub
    Call DeleteMenu(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND)
    Call DrawMenuBar(hForm)
End Sub

Public Property Get TotalMenuItems() As Long
    If oForm Is Nothing Then Exit Sub
    TotalMenuItems = lTotalMenuItems
End Property

Public Property Get MenusCount() As Long
    If oForm Is Nothing Then Exit Sub
    MenusCount = MenusCollection.Count
End Property


Public Property Get MainMenusCount() As Long
    If oForm Is Nothing Then Exit Property
    MainMenusCount = GetMenuItemCountAPI(GetMenu(hForm))
End Property

Public Property Get IsMenuItemEnabled(ByVal MenuItemPos As Long) As Boolean
    Const MF_GRAYED = &H1
    Const MF_BYCOMMAND = &H0&
    If oForm Is Nothing Then Exit Property
    If (GetMenuState(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND) And MF_GRAYED) = 0 Then
        IsMenuItemEnabled = True
    End If
End Property


Public Property Get IsMenuItemMenu(ByVal MenuItemPos As Long) As Boolean
    Const MF_POPUP = &H10&
    Const MF_BYCOMMAND = &H0&
    If oForm Is Nothing Then Exit Property
    If (GetMenuState(hFormMenu, MenuItemPos - 1, MF_BYCOMMAND) And MF_POPUP) <> 0 Then
        IsMenuItemMenu = True
    End If
End Property


Public Property Get MenuItemCaption(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Dim sBuffer  As String * 256, lRet As Long
 
    If oForm Is Nothing Then Exit Property
    lRet = GetMenuString(hFormMenu, MenuItemPos - 1, sBuffer, Len(sBuffer), MF_BYCOMMAND)
    MenuItemCaption = Left(sBuffer, lRet)
End Property



'_______________________________________CLASS PRIVATE ROUTINES___________________________________________

Private Function MenuItemCaptionByCommand(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Const MF_BYPOSITION = &H400&
    Dim sBuffer  As String * 256, lRet As Long
 
    lRet = GetMenuString(hFormMenu, MenuItemPos, sBuffer, Len(sBuffer), MF_BYCOMMAND)
    MenuItemCaptionByCommand = Left(sBuffer, lRet)
End Function

Private Function GetMenuItemIDFromItemPos(ByVal MenuItemPos As Long) As String
    Const MF_BYCOMMAND = &H0&
    Const MIIM_DATA = &H20
    Dim MII As MENUITEMINFO
 
    On Error Resume Next
    With MII
        .cbSize = LenB(MII)
        .fMask = MIIM_DATA
    End With
    Call GetMenuItemInfo(hFormMenu, MenuItemPos, MF_BYCOMMAND, MII)
    GetMenuItemIDFromItemPos = Split(oItemDataCollection(MII.dwItemData), "||")(2)
End Function


#If Win64 Then
    Private Function GetParentMenu(ByVal CellText As String) As LongLong
#Else
    Private Function GetParentMenu(ByVal CellText As String) As Long
#End If
    Dim sParent As String
 
    On Error Resume Next
    If Len(CellText) Then
        If Len(CellText) = 1 Then
            GetParentMenu = hFormMenu
        Else
            sParent = Left(CellText, Len(CellText) - 1)
        End If
        GetParentMenu = MenusCollection(sParent)
    End If
End Function

Private Sub ShowSystemMenu(ByVal bShow As Boolean)
    Const MF_BYPOSITION = &H400&
    Call RemoveMenu(GetSystemMenu(hForm, CLng(bShow)), 1, MF_BYPOSITION)
    Call RemoveMenu(GetSystemMenu(hForm, CLng(bShow)), 2, MF_BYPOSITION)
    Call RemoveMenu(GetSystemMenu(hForm, CLng(bShow)), 3, MF_BYPOSITION)
End Sub

#If Win64 Then
    Private Sub SetHooks(ByVal hwnd As LongLong)
        Dim hHook As LongLong
#Else
    Private Sub SetHooks(ByVal hwnd As Long)
        Dim hHook As Long
#End If
    Const WH_CALLWNDPROC = 4
    Call SetWindowSubclass(hwnd, WinProcAddr, ObjPtr(Me))
    If oAllClasses Is Nothing Then
        Set oAllClasses = New Collection
        Set oAllForms = New Collection
    End If
    oAllClasses.Add ObjPtr(Me)
    oAllForms.Add hwnd
    Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
    hHook = SetWindowsHookEx(WH_CALLWNDPROC, HookProcAddr, _
    GetModuleHandle(vbNullString), GetWindowThreadProcessId(Application.hwnd, 0))
    Call SetProp(Application.hwnd, "Hook", hHook)
End Sub

Private Function FindImagePath(ByVal ImgPath As String) As String
    Dim sTemp As String
 
    Select Case True
        Case IsValidFaceID(ImgPath)
            FindImagePath = ImgPath
        Case Len(Dir(ImgPath))
            FindImagePath = ImgPath
        Case Len(ImgPath)
            sTemp = ThisWorkbook.Path & Application.PathSeparator & ImgPath
            On Error Resume Next
                If Len(Dir(sTemp)) Then
                    If Err.Number = 0 Then
                        FindImagePath = sTemp
                    End If
            On Error GoTo 0
            End If
    End Select
End Function

Private Function IsValidFaceID(ByVal FaceID As String) As Boolean
    On Error Resume Next
    IsValidFaceID = Not (Application.CommandBars.FindControl(ID:=CLng(FaceID)) Is Nothing)
End Function

#If Win64 Then
    Private Function PicFromBmp( _
        ByVal FaceID As Long _
    ) As StdPicture
        Dim hBmpPtr As LongLong, hCopyBmpPtr As LongLong
#Else
    Private Function PicFromBmp( _
        ByVal FaceID As Long _
    ) As StdPicture
        Dim hBmpPtr As Long, hCopyBmpPtr As Long
#End If

    Const IMAGE_BITMAP = 0
    Const PICTYPE_BITMAP = 1
    Const LR_COPYRETURNORG = &H4
    Const CF_BITMAP = 2
    Const S_OK = &H0&
    Dim IID_IDISPATCH As GUID, uPicInfo As uPicDesc
    Dim IPic As Object, lRet As Long
 
    On Error GoTo errHandler
    Application.CommandBars.FindControl(ID:=FaceID).CopyFace
    Call OpenClipboard(0)
    hBmpPtr = GetClipboardData(CF_BITMAP)
    hCopyBmpPtr = CopyImage(hBmpPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG)
    Call DeleteObject(hBmpPtr)

    If hBmpPtr Then
        With IID_IDISPATCH
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        With uPicInfo
            .Size = Len(uPicInfo)
            .Type = PICTYPE_BITMAP
            .hPic = hCopyBmpPtr
            .hPal = 0
        End With
        lRet = OleCreatePictureIndirect(uPicInfo, IID_IDISPATCH, True, IPic)
        If lRet = S_OK Then
            Set PicFromBmp = IPic
        End If
    End If
 
errHandler:
    Call EmptyClipboard
    Call CloseClipboard
 
    If BMPsCollection Is Nothing Then _
            Set BMPsCollection = New Collection
            Call BMPsCollection.Add(hBmpPtr)
End Function


#If Win64 Then
    Private Function BitmapToIcon( _
        ByVal Bmp As LongLong, _
        ImageSize As Long _
    ) As LongLong
        Dim lBitmap As LongLong, lThumb As LongLong
#Else
    Private Function BitmapToIcon( _
        ByVal Bmp As Long, _
        ImageSize As Long _
    ) As Long
        Dim lBitmap As Long, lThumb As Long
#End If

    Const S_OK = 0&
    Dim tSI As GDIP_STARTUPINPUT
    Dim lRes As Long
    Dim lGDIP As Long
 
    On Error GoTo xit
    tSI.GdiplusVersion = 1
    lRes = GdiplusStartup(lGDIP, tSI)
    If lRes = S_OK Then
        lRes = GdipCreateBitmapFromHBITMAP(Bmp, 0, lBitmap)
        If lRes = S_OK Then
            lRes = GdipGetImageThumbnail(lBitmap, ImageSize, ImageSize, lThumb, 0, 0)
            If lRes = S_OK Then
                lRes = GdipCreateHICONFromBitmap(lThumb, BitmapToIcon)
            End If
        End If
    End If
 
xit:
    Call GdipDisposeImage(lBitmap)
    Call GdipDisposeImage(lThumb)
    Call GdiplusShutdown(lGDIP)
 
End Function


#If Win64 Then
    Private Function IconToBitmap( _
        ByVal hImage As LongLong, _
        ByVal ImageType As IMAGE_TYPE, _
        ByVal ImageSize As Long _
    ) As LongLong
        Dim hDC As LongLong, hCompatDc As LongLong
#Else
    Private Function IconToBitmap( _
        ByVal hImage As Long, _
        ByVal ImageType As IMAGE_TYPE, _
        ByVal ImageSize As Long _
    ) As Long
        Dim hDC As Long, hCompatDc As Long
#End If

    Const TRANSPARENT = 1
    Const SM_CXMENUCHECK = 71
    Const ETO_OPAQUE = 2
    Const DI_NORMAL = &H3&
    Const COLOR_MENU = 4
 
    Dim RECT As RECT
 
    If BMPsCollection Is Nothing Then _
            Set BMPsCollection = New Collection
 
    If ImageSize = 0 Then _
            ImageSize = GetSystemMetrics(SM_CXMENUCHECK)
 
    hDC = GetDC(hForm)
    With RECT
        .Right = ImageSize
        .Bottom = ImageSize
        hCompatDc = CreateCompatibleDC(0)
        IconToBitmap = CreateCompatibleBitmap(hDC, .Right, .Bottom)
        Call SelectObject(hCompatDc, IconToBitmap)
        Call SetBkMode(hCompatDc, TRANSPARENT)
        Call SetBkColor(hCompatDc, GetSysColor(COLOR_MENU))
        Call ExtTextOut(hCompatDc, 0, 0, ETO_OPAQUE, RECT, vbNullString, 0, 0)
        Call DrawIconEx(hCompatDc, 0, 0, hImage, .Right, .Bottom, 0, 0, DI_NORMAL)
        Call DeleteDC(hCompatDc)
    End With
    Call ReleaseDC(hForm, hDC)
 
    Call BMPsCollection.Add(IconToBitmap)
 
End Function

Private Sub CleanUp()
    Dim i As Long
 
    On Error Resume Next
    If Not BMPsCollection Is Nothing Then
        With BMPsCollection
            For i = .Count To 1 Step -1
                Call DeleteObject(.Item(i))
                .Remove i
            Next i
        End With
    End If
     
    If Not MenusCollection Is Nothing Then
        With MenusCollection
            For i = .Count To 1 Step -1
                Call DestroyMenu(.Item(i))
                .Remove i
            Next i
        End With
    End If
 
    Set BMPsCollection = Nothing
    Set MenusCollection = Nothing
    Call DestroyMenu(hFormMenu)
    Set oForm = Nothing
    Set oSrcDataRange = Nothing
    Set oItemDataCollection = Nothing
    Set oInterface = Nothing
 
End Sub


#If Win64 Then
    Private Function LoWord(ByVal Word As LongLong) As Integer
#Else
    Private Function LoWord(ByVal Word As Long) As Integer
#End If
    Dim x As LongToInteger
    CopyMemory x, Word, LenB(x)
    LoWord = x.Low
End Function

#If Win64 Then
    Private Function HiWord(ByVal Word As LongLong) As Integer
#Else
    Private Function HiWord(ByVal Word As Long) As Integer
#End If
    Dim x As LongToInteger
    CopyMemory x, Word, LenB(x)
    HiWord = x.High
End Function


'___________________________________________INTERFACE ROUTINES_________________________________________________________

#If Win64 Then
    Private Function ISecret_SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong _
    ) As LongLong
#Else
    Private Function ISecret_SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long) As Long
#End If

    Const WM_CREATE = &H1
    Dim tTmpCWP As CWPSTRUCT
    Dim sBuff As String * 256

    Call CopyMemory(tTmpCWP, ByVal lParam, LenB(tTmpCWP))
    If tTmpCWP.Message = WM_CREATE Then
        sBuff = Left(sBuff, GetWindowText(tTmpCWP.hwnd, ByVal sBuff, 256))
        Call ZeroMemory(tTmpCWP, LenB(tTmpCWP))
        If InStr(1, sBuff, "Microsoft Visual Basic") Then    '<==Language dependent
            Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
            Call OnError_RemoveAllWindows_Subclass
        End If
    End If
    ISecret_SafeExitHook = CallNextHookEx(GetProp(Application.hwnd, "Hook"), ncode, wParam, ByVal lParam)

End Function

#If Win64 Then
    Private Function ISecret_WndProc( _
        ByVal hwnd As LongLong, _
        ByVal wMsg As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong, _
        ByVal uIdSubclass As Object, _
        ByVal This As LongLong _
    ) As LongLong
#Else
    Private Function ISecret_WndProc( _
        ByVal hwnd As Long, _
        ByVal wMsg As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long, _
        ByVal uIdSubclass As Object, _
        ByVal This As Long _
    ) As Long
#End If

    Const MF_BYPOSITION = &H400&
    Const MF_BYCOMMAND = &H0&
    Const MF_POPUP = &H10&
    Const MF_GRAYED = &H1
    Const MIIM_ID = &H2
    Const MIIM_STATE = &H1
    Const WM_MENUSELECT = &H11F&
    Const WM_ENTERIDLE = &H121
    Const WM_COMMAND = &H111
    Const WM_EXITMENULOOP = &H212
    Const WM_DESTROY As Long = &H2
 
    Dim MII As MENUITEMINFO
    Dim sBuff As String * 256, sCaption As String, lRet As Long
 
    Select Case wMsg
        Case WM_ENTERIDLE
            bMenuExpanded = True
            Call ShowSystemMenu(False)
        Case WM_EXITMENULOOP
            bMenuExpanded = False
            Call ShowSystemMenu(True)
            RaiseEvent MenuItemHighlight("", "", 0, False)
        Case WM_COMMAND
            lRet = GetMenuString(lParam, LoWord(wParam), sBuff, Len(sBuff), MF_BYCOMMAND)
            sCaption = Replace(Left(MenuItemCaptionByCommand(LoWord(wParam)), _
                Len(MenuItemCaptionByCommand(LoWord(wParam)))), "&", "")
            RaiseEvent MenuItemSelect(sCaption, GetMenuItemIDFromItemPos(LoWord(wParam)), LoWord(wParam) + 1)
        Case WM_MENUSELECT
            bMenuExpanded = True
            lRet = GetMenuString(lParam, LoWord(wParam), sBuff, Len(sBuff), MF_BYCOMMAND)
            sCaption = Replace(Left(sBuff, lRet), "&", "")
            With MII
                .cbSize = LenB(MII)
                .fMask = MIIM_STATE Or MIIM_ID
            End With
            If Not (HiWord(wParam) And MF_POPUP) = MF_POPUP Then
                Call GetMenuItemInfo(lParam, LoWord(wParam), MF_BYCOMMAND, MII)
                RaiseEvent MenuItemHighlight(sCaption, GetMenuItemIDFromItemPos(LoWord(wParam)), _
                     LoWord(wParam) + 1, Not CBool(MII.fState And MF_GRAYED))
            End If
            If (HiWord(wParam) And MF_POPUP) = MF_POPUP Then
                Call GetMenuItemInfo(lParam, LoWord(wParam), MF_BYPOSITION, MII)
                lRet = GetMenuString(lParam, LoWord(wParam), sBuff, Len(sBuff), MF_BYPOSITION)
                sCaption = Replace(Left(sBuff, lRet), "&", "")
                RaiseEvent MenuItemHighlight(sCaption, GetMenuItemIDFromItemPos(MII.wID), _
                    MII.wID + 1, Not CBool(MII.fState And MF_GRAYED))
            End If
        Case WM_DESTROY
            Call RemoveWindowSubclass(hwnd, WinProcAddr, ObjPtr(uIdSubclass))
            Call CleanUp
    End Select
 
    ISecret_WndProc = DefSubclassProc(hwnd, wMsg, wParam, lParam)
 
End Function


'___________________________________________CLASS TERMINATE________________________________________________________

Private Sub Class_Terminate()
    lHookedFormsCount = lHookedFormsCount - 1
    If lHookedFormsCount = 0 Then
        Call UnhookWindowsHookEx(GetProp(Application.hwnd, "Hook"))
        Call RemoveProp(Application.hwnd, "Hook")
        Debug.Print "Hooks properly released."
    End If
End Sub


2- Interface Module (ISecret)
VBA Code:
Option Explicit


#If Win64 Then
    Public Function WndProc( _
            ByVal hwnd As LongLong, _
            ByVal wMsg As Long, _
            ByVal wParam As LongLong, _
            ByVal lParam As LongLong, _
            ByVal uIdSubclass As Object, _
            ByVal This As LongLong) As LongLong
#Else
    Public Function WndProc( _
            ByVal hwnd As Long, _
            ByVal wMsg As Long, _
            ByVal wParam As Long, _
            ByVal lParam As Long, _
            ByVal uIdSubclass As Object, _
            ByVal This As Long) As Long
#End If
'
End Function


#If Win64 Then
    Public Function SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong _
    ) As LongLong
#Else
    Public Function SafeExitHook( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long _
    ) As Long
#End If
'
End Function


3- Helper Standard module (bas_Delegate)
VBA Code:
Option Explicit

Public oInterface As ISecret
Public oAllClasses As Collection
Public oAllForms As Collection
Public lHookedFormsCount As Long

#If VBA7 Then
    Private Declare PtrSafe Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr) As LongPtr
#Else
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
#End If



#If Win64 Then
    Public Function WndProcDelg( _
            ByVal hwnd As LongLong, _
            ByVal wMsg As Long, _
            ByVal wParam As LongLong, _
            ByVal lParam As LongLong, _
            ByVal uIdSubclass As Object, _
            ByVal This As LongLong) As LongLong
#Else
    Public Function WndProcDelg( _
            ByVal hwnd As Long, _
            ByVal wMsg As Long, _
            ByVal wParam As Long, _
            ByVal lParam As Long, _
            ByVal uIdSubclass As Object, _
            ByVal This As Long) As Long
#End If

    Set oInterface = uIdSubclass
    WndProcDelg = CallByName(oInterface, "WndProc", VbMethod, hwnd, wMsg, wParam, lParam, uIdSubclass, This)

End Function

#If Win64 Then
    Public Function SafeExitHookDelg( _
        ByVal ncode As Long, _
        ByVal wParam As LongLong, _
        ByVal lParam As LongLong _
    ) As LongLong
#Else
    Public Function SafeExitHookDelg( _
        ByVal ncode As Long, _
        ByVal wParam As Long, _
        ByVal lParam As Long _
    ) As Long
#End If

    If Not oInterface Is Nothing Then
        SafeExitHookDelg = CallByName(oInterface, "SafeExitHook", VbMethod, ncode, wParam, lParam)
    End If

End Function

Public Sub OnError_RemoveAllWindows_Subclass(Optional ByVal Dummy As Boolean)
    Dim i As Long
 
    On Error Resume Next
    With oAllClasses
        For i = .Count To 1 Step -1
            Call RemoveWindowSubclass(oAllForms.Item(i), WinProcAddr, .Item(i))
        Next i
    End With
    Set oInterface = Nothing
    Set oAllClasses = Nothing
    Set oAllForms = Nothing
End Sub

#If Win64 Then
    Public Function WinProcAddr() As LongLong
        WinProcAddr = VBA.CLngLng(AddressOf WndProcDelg)
    #Else
    Public Function WinProcAddr() As Long
        WinProcAddr = VBA.CLng(AddressOf WndProcDelg)
    #End If
End Function

#If Win64 Then
    Public Function HookProcAddr() As LongLong
        HookProcAddr = VBA.CLngLng(AddressOf SafeExitHookDelg)
    #Else
    Public Function HookProcAddr() As Long
        HookProcAddr = VBA.CLng(AddressOf SafeExitHookDelg)
    #End If
End Function


I have tested the code in x32bit and 64bit. If anyone finds any bugs or has any suggestions, please let me know.

Regards.
Hi Jaafak Tribak,

You have created userform with submenu File>Open>New >save and exit.

Are you able to active routine the module like i click file>new>open other userform.

In 32 bit normally we use code in module
Public Sub New()
Useform2.show
End Sub

Or if you.want to click your file>exit
In 32 bit normally we use code in module

Public Sub Exit()
Unload me
End Sub

But in this template the routine macro for 64 unfunction.

Above two sample above file>new> open other userform or file >exit, still not solve the problem for 64 bit.

So you might have any idea on this about the macro in module for 64bit so that routine will function.

Kind Regards
MD Yusuf
 
Upvote 0
De

Hi Jaafak Tribak,

You have created userform with submenu File>Open>New >save and exit.

Are you able to active routine the module like i click file>new>open other userform.

In 32 bit normally we use code in module
Public Sub New()
Useform2.show
End Sub

Or if you.want to click your file>exit
In 32 bit normally we use code in module

Public Sub Exit()
Unload me
End Sub

But in this template the routine macro for 64 unfunction.

Above two sample above file>new> open other userform or file >exit, still not solve the problem for 64 bit.

So you might have any idea on this about the macro in module for 64bit so that routine will function.

Kind Regards
MD Yusuf

If I understand you correctly, you want something along these lines:
Sans titre2.png



Coding it is straightforward as follows:

In the UserForm Module:
VBA Code:
Option Explicit

Private WithEvents MenuInstance As CMenu

Private Sub UserForm_Initialize()
    Call CreateMenu
End Sub

Private Sub CreateMenu()
    Set MenuInstance = New CMenu
    MenuInstance.AddMenu Me, Sheet1.Range("B5:E9")
End Sub


Private Sub MenuInstance_MenuItemSelect( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenItemPos As Long _
)

    Select Case MenuItemID
        Case Is = "1|1|1"   'New
            UserForm2.Show
        Case Is = "1|2"     'Save
            'do something here...
        Case Is = "1|3"     'Exit
            Unload Me
    End Select
    
End Sub


The data source range in Sheet1 is B5:E9 and it looks as follows:
Sans titre3.png


As you can see from the above code in the userform module, the MenuItemSelect event was used to detect which menu item was selected. I decided to check against the MenuItemID but you could just as well check against the MenuItemCaption or MenuItempos, whichever is easier for you, as they are all unique.

And here is a workbook example you can download for testing
 
Upvote 0
If I understand you correctly, you want something along these lines:
View attachment 64359


Coding it is straightforward as follows:

In the UserForm Module:
VBA Code:
Option Explicit

Private WithEvents MenuInstance As CMenu

Private Sub UserForm_Initialize()
    Call CreateMenu
End Sub

Private Sub CreateMenu()
    Set MenuInstance = New CMenu
    MenuInstance.AddMenu Me, Sheet1.Range("B5:E9")
End Sub


Private Sub MenuInstance_MenuItemSelect( _
    ByVal MenuItemCaption As String, _
    ByVal MenuItemID As String, _
    ByVal MenItemPos As Long _
)

    Select Case MenuItemID
        Case Is = "1|1|1"   'New
            UserForm2.Show
        Case Is = "1|2"     'Save
            'do something here...
        Case Is = "1|3"     'Exit
            Unload Me
    End Select
   
End Sub


The data source range in Sheet1 is B5:E9 and it looks as follows:
View attachment 64360

As you can see from the above code in the userform module, the MenuItemSelect event was used to detect which menu item was selected. I decided to check against the MenuItemID but you could just as well check against the MenuItemCaption or MenuItempos, whichever is easier for you, as they are all unique.

And here is a workbook example you can download for testing
Hi Jaafar Tribak,

Yes, this userformmenu bar able to run on 64 bit, but how to add file1, file2 with same with file menu and submenu. I meant the menu have file, file1, file2, file3 and etc.

Could you please advise how to add file1, file2 and etc.

Again, i really appreciated your supports and time. Thank you

Kind Regards
MD Yusuf
 
Upvote 0
Hi Jaafar Tribak,

Yes, this userformmenu bar able to run on 64 bit, but how to add file1, file2 with same with file menu and submenu. I meant the menu have file, file1, file2, file3 and etc.

Could you please advise how to add file1, file2 and etc.

Again, i really appreciated your supports and time. Thank you

Kind Regards
MD Yusuf
I gave you a simple example to get you started. If you want to add more menus, you will simply need to adjust the data source range in the worksheet as required.
 
Upvote 0
I gave you a simple example to get you started. If you want to add more menus, you will simply need to adjust the data source range in the worksheet as required.
HI Jaafar Tribak,
Please ignore my previous post, i got it to make file1 and file2. except add command for how to print, like command exit you had provided.

Kind Regards
MD Yusuf
 
Upvote 0
I have still one more question how to change the menu icon not using face id but using own pictures/icon.
Two things :
1- You need to have the pictures/icon files in the same folder as that of the workbook.
2- You need to add the picture file name (without the file path) to the corresponding row in the 3rd worksheet table column titled : Menu Item Icon File Path (Or FACE_ID #)

Edit:

You can have the pictures/icon files in a different folder as long as you add the full file pathname to the table column.

Regards.
 
Upvote 0

Forum statistics

Threads
1,223,888
Messages
6,175,212
Members
452,618
Latest member
Tam84

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top