Option Explicit
#If VBA7 Then
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 CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As Long
Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () 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 RegisterHotKey Lib "user32" (ByVal hwnd As LongPtr, ByVal id As Long, ByVal fsModifiers As Long, ByVal vk As Long) As Long
Private Declare PtrSafe Function UnregisterHotKey Lib "user32" (ByVal hwnd As LongPtr, ByVal id As Long) As Long
Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function IsWindowEnabled Lib "user32" (ByVal hwnd As LongPtr) As Long
Private hHook As LongPtr
#Else
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 CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () 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 RegisterHotKey Lib "user32" (ByVal hwnd As Long, ByVal id As Long, ByVal fsModifiers As Long, ByVal vk As Long) As Long
Private Declare Function UnregisterHotKey Lib "user32" (ByVal hwnd As Long, ByVal id As Long) As Long
Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hwnd As Long) As Long
Private Declare Function IsWindowEnabled Lib "user32" (ByVal hwnd As Long) As Long
Private hHook As Long
#End If
Private oShortCutKeysCollection As Collection
Private Const sPopUpName As String = "MyPopUpMenu"
Public Sub DisplayPopUp(ByVal UF As Object)
'Disable ESC key when UserForm is Modeless to avoid potential crashing.
If IsWindowEnabled(Application.hwnd) Then
EnableESCKey(UF) = False
End If
Call SetKeyboardHook
Call CreateDisplayPopUpMenu
End Sub
Public Sub CleanUp(ByVal UF As Object)
If IsWindowEnabled(Application.hwnd) = 0 Then
EnableESCKey(UF) = True
End If
Call SetKeyboardHook(False)
Call RemoveProp(Application.hwnd, "hHook")
Set oShortCutKeysCollection = Nothing
End Sub
'_____________________________________Private Routines_________________________________________________
Private Property Let EnableESCKey(ByVal UF As Object, ByVal bEnable As Boolean)
Const VK_ESCAPE = &H1B
#If Win64 Then
Dim hwnd As LongLong
#Else
Dim hwnd As Long
#End If
Call IUnknown_GetWindow(UF, VarPtr(hwnd))
If bEnable = False Then
Call RegisterHotKey(hwnd, &HBFFF&, 0, VK_ESCAPE)
Else
Call UnregisterHotKey(hwnd, &HBFFF&)
End If
End Property
Private Sub SetKeyboardHook(Optional ByVal bSet As Boolean = True)
Const WH_KEYBOARD = 2
Call UnhookWindowsHookEx(GetProp(Application.hwnd, "hHook")): hHook = 0
If bSet Then
If hHook = 0 Then
hHook = SetWindowsHookEx(WH_KEYBOARD, AddressOf KeyboardProc, 0, GetCurrentThreadId)
Call SetProp(Application.hwnd, "hHook", hHook)
End If
End If
End Sub
#If Win64 Then
Private Function KeyboardProc(ByVal ncode As Long, ByVal wParam As LongLong, ByVal lParam As LongLong) As LongLong
Dim lKeyVCode As LongLong
#Else
Private Function KeyboardProc(ByVal ncode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Dim lKeyVCode As Long
#End If
Const HC_ACTION = 0
Const VK_MENU = &H12
Const VK_ESCAPE = &H1B
Const VK_SPACE = &H20
Const VK_PRIOR = &H21
Const VK_NEXT = &H22
Const VK_END = &H23
Const VK_HOME = &H24
Const VK_UP = &H26
Const VK_DOWN = &H28
Const VK_LEFT = &H25
Const VK_RIGHT = &H27
Const VK_RETURN = &HD
Dim vAllowedKeys As Variant, oTemp As CommandBarButton
If hHook = 0 Then
'Important safety mesure !! :-
'Remove keyboard hook here and get out in case of an unhandled error.
Call SetKeyboardHook(False): Exit Function
End If
vAllowedKeys = Array(VK_ESCAPE, VK_PRIOR, VK_NEXT, VK_END, VK_HOME, _
VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_RETURN)
If ncode = HC_ACTION Then
lKeyVCode = wParam
If lParam And (2 ^ 29) Then '29 'alt key' bit of lParam set.
On Error Resume Next
Set oTemp = oShortCutKeysCollection(VK_MENU & "||" & lKeyVCode)
On Error GoTo 0
If Not oTemp Is Nothing Then
Call SetKeyboardHook(False)
Exit Function
End If
End If
If Not IsError((Application.Match(wParam, vAllowedKeys, 0))) Then
Call SetKeyboardHook
Exit Function
End If
KeyboardProc = 1
Exit Function
End If
KeyboardProc = CallNextHookEx(hHook, ncode, wParam, lParam)
End Function
Private Sub SetShortCutKeys(ByVal MenuItem As Object, ByVal ModifierKey As Long, ByVal AcceleratorKey As Long)
If oShortCutKeysCollection Is Nothing Then
Set oShortCutKeysCollection = New Collection
End If
oShortCutKeysCollection.Add MenuItem, ModifierKey & "||" & AcceleratorKey
End Sub
Private Sub CreateDisplayPopUpMenu()
Dim oButton As CommandBarButton, oPopUpMenu As CommandBarPopup
Set oShortCutKeysCollection = Nothing
On Error Resume Next
Application.CommandBars(sPopUpName).Delete
On Error GoTo 0
With Application.CommandBars.Add(Name:=sPopUpName, Position:=msoBarPopup, MenuBar:=False, Temporary:=True)
Set oButton = .Controls.Add(Type:=msoControlButton)
With oButton
.BeginGroup = True
.Caption = "&Button1 (Alt+B)"
.Tag = .Caption
Call SetShortCutKeys(oButton, vbKeyMenu, vbKeyB) '<== Assign SortCutKeys here.
.FaceId = 71
.Style = msoButtonIconAndCaption
.OnAction = "'OnActionMacro " & Chr(34) & .Caption & Chr(34) & "'"
End With
Set oButton = .Controls.Add(Type:=msoControlButton)
With oButton
.BeginGroup = True
.Caption = "B&utton2 (Alt+U)"
.Tag = .Caption
Call SetShortCutKeys(oButton, vbKeyMenu, vbKeyU) '<== Assign SortCutKeys here.
.FaceId = 72
.Style = msoButtonIconAndCaption
.OnAction = "'OnActionMacro " & Chr(34) & .Caption & Chr(34) & "'"
End With
Set oButton = .Controls.Add(Type:=msoControlButton)
With oButton
.BeginGroup = True
.Caption = "Bu&tton3 (Alt+T)"
.Tag = .Caption
SetShortCutKeys oButton, vbKeyMenu, vbKeyT '<== Assign SortCutKeys here.
.FaceId = 73
.Style = msoButtonIconAndCaption
.OnAction = "'OnActionMacro " & Chr(34) & .Caption & Chr(34) & "'"
End With
Set oPopUpMenu = .Controls.Add(Type:=msoControlPopup)
With oPopUpMenu
.Caption = "My Special Menu"
.Tag = .Caption
With .Controls.Add(Type:=msoControlButton)
.Caption = "Butt&on1 in menu (Alt+O)"
.Tag = .Caption
SetShortCutKeys oButton, vbKeyMenu, vbKeyO '<== Assign SortCutKeys here.
.FaceId = 71
.OnAction = "'OnActionMacro " & Chr(34) & .Caption & Chr(34) & "'"
End With
End With
End With
On Error Resume Next
Application.CommandBars(sPopUpName).ShowPopup
On Error GoTo 0
End Sub
Sub OnActionMacro(Optional ByVal ButtonCaption As String)
Dim sTrimedCaption As String
sTrimedCaption = CommandBars.FindControl(Tag:=ButtonCaption).Caption
sTrimedCaption = Application.Trim(Replace(Left(sTrimedCaption, InStr(1, sTrimedCaption, "(") - 1), "&", ""))
MsgBox "You invoked: '" & sTrimedCaption & "'", vbInformation
End Sub