[COLOR=seagreen]'**************************************************[/COLOR]
[COLOR=seagreen]'// Code that makes a standard VBA MsgBox Modeless[/COLOR]
[COLOR=seagreen]'// enablig code to run asynchronously and the user[/COLOR]
[COLOR=seagreen]'// interaction with the worksheet.[/COLOR]
[COLOR=seagreen]'**************************************************[/COLOR]
Option Explicit
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 GetCurrentThreadId Lib "kernel32" _
() As Long
Private Declare Function SetWindowLong Lib "user32" _
Alias "SetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
Private Declare Function CallWindowProc Lib "user32" _
Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, _
ByVal MSG As Long, _
ByVal WParam As Long, _
ByVal lparam As Long) As Long
Private Declare Function GetClassName Lib "user32" _
Alias "GetClassNameA" _
(ByVal hwnd As Long, _
ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
Private Declare Function GetWindowLong Lib "user32" _
Alias "GetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long) As Long
Private Declare Function EnableWindow Lib "user32" _
(ByVal hwnd As Long, _
ByVal fEnable As Long) As Long
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Const WH_CBT As Long = 5
Private Const HCBT_CREATEWND As Long = 3
Private Const GWL_STYLE As Long = -16
Private Const DS_NOIDLEMSG As Long = &H100&
Private Const GWL_WNDPROC As Long = (-4)
Private Const WM_ENTERIDLE As Long = &H121
Private Const WM_COMMAND As Long = &H111
Private Const WM_NCDESTROY As Long = &H82
Public lRet As VbMsgBoxResult
Private lOldAppWindowProc As Long
Private lOldMsgBxWindowProc As Long
Private lXLAPPhwnd As Long
Private lhHook As Long
Private sAsyncProc As String
Private lMsgBoxhwnd As Long
Private Sub CreateHook(AsyncProc As String)
[COLOR=seagreen]'store the asyncProc arg in a module[/COLOR]
[COLOR=seagreen] 'level var for later use.[/COLOR]
sAsyncProc = AsyncProc
[COLOR=seagreen]'retrieve the excel app hwnd for later use.[/COLOR]
lXLAPPhwnd = FindWindow("XLMAIN", Application.Caption)
[COLOR=seagreen]'install a system hook to catch[/COLOR]
[COLOR=seagreen]'the creation of the MsgBox.[/COLOR]
lhHook = SetWindowsHookEx _
(WH_CBT, AddressOf HookProc, 0, GetCurrentThreadId)
End Sub
Private Function HookProc _
(ByVal idHook As Long, ByVal WParam As Long, _
ByVal lparam As Long) As Long
Dim strBuffer As String
Dim lRetVal As Long
Dim lCurrentStyle As Long
Dim lNewStyle As Long
[COLOR=seagreen]'a wnd is being created.[/COLOR]
If idHook = HCBT_CREATEWND Then
strBuffer = Space(256)
[COLOR=seagreen]'check if the wnd is our MsgBox.[/COLOR]
lRetVal = GetClassName(WParam, strBuffer, 256)
If Left(strBuffer, lRetVal) = "#32770" Then
[COLOR=seagreen]'if so,store its hwnd in a module[/COLOR]
[COLOR=seagreen] 'level var for later use.[/COLOR]
lMsgBoxhwnd = WParam
[COLOR=seagreen]'now, let's set the msgbox DS_NOIDLEMSG[/COLOR]
[COLOR=seagreen] 'style to make it modeless.[/COLOR]
lCurrentStyle = GetWindowLong(WParam, GWL_STYLE)
lNewStyle = lCurrentStyle And Not DS_NOIDLEMSG
SetWindowLong WParam, GWL_STYLE, lNewStyle
[COLOR=seagreen]'we now need to subclass the excel app[/COLOR]
[COLOR=seagreen] 'to catch the WM_ENTERIDLE message and[/COLOR]
[COLOR=seagreen] 'make the running of an async macro possible.[/COLOR]
Call SubClassApp(lXLAPPhwnd)
[COLOR=seagreen]'subclass the msgbox to catch the[/COLOR]
[COLOR=seagreen] 'WM_NCDESTROY message necessary to[/COLOR]
[COLOR=seagreen] 'cleanup and set the lRet var.[/COLOR]
Call SubClassMsgBx(WParam)
[COLOR=seagreen]'we don't need the hook anymore.[/COLOR]
UnhookWindowsHookEx lhHook
End If
End If
[COLOR=seagreen]'Call next hook[/COLOR]
HookProc = CallNextHookEx _
(lhHook, idHook, ByVal WParam, ByVal lparam)
End Function
Private Sub SubClassApp(hwnd As Long)
[COLOR=seagreen]'subclass the excel app here.[/COLOR]
lOldAppWindowProc = _
SetWindowLong(hwnd, GWL_WNDPROC, AddressOf NewAppWindowProc)
End Sub
Private Sub UnSubClassApp(hwnd As Long)
[COLOR=seagreen]'UnSubClassApp The Excel Application here.[/COLOR]
SetWindowLong hwnd, GWL_WNDPROC, lOldAppWindowProc
End Sub
Private Function NewAppWindowProc(ByVal hwnd As Long, ByVal MSG _
As Long, ByVal WParam As Long, ByVal lparam As Long) As _
Long
On Error Resume Next
Select Case MSG
Case WM_ENTERIDLE
[COLOR=seagreen]'is our msgbox being created[/COLOR]
[COLOR=seagreen] 'within the excel app ?[/COLOR]
[COLOR=seagreen] 'if so, enable back the main xl[/COLOR]
[COLOR=seagreen] 'window and run our async macro.[/COLOR]
EnableWindow hwnd, 1
Application.Run ThisWorkbook.Name & "!" & sAsyncProc
[COLOR=seagreen]'we are done with the subclassing of excel.[/COLOR]
Call UnSubClassApp(hwnd)
End Select
[COLOR=seagreen]' Pass Intercepted Messages To The Original WinProc[/COLOR]
NewAppWindowProc = _
CallWindowProc(lOldAppWindowProc, hwnd, MSG, WParam, lparam)
End Function
Private Sub SubClassMsgBx(hwnd As Long)
[COLOR=seagreen]'reset the msgbox lRet and subclass our msgbox here.[/COLOR]
lRet = 0
lOldMsgBxWindowProc = _
SetWindowLong(hwnd, GWL_WNDPROC, AddressOf NewMsgBxWindowProc)
End Sub
Private Sub UnSubClassMsgBx(hwnd As Long)
[COLOR=seagreen]'UnSubClassApp our msgbox here.[/COLOR]
SetWindowLong hwnd, GWL_WNDPROC, lOldMsgBxWindowProc
End Sub
Private Function NewMsgBxWindowProc(ByVal hwnd As Long, ByVal MSG _
As Long, ByVal WParam As Long, ByVal lparam As Long) As _
Long
On Error Resume Next
Select Case MSG
[COLOR=seagreen]'unsubclass our msgbox upon[/COLOR]
[COLOR=seagreen] 'closing it and set the lRet var[/COLOR]
[COLOR=seagreen] 'to exit the async macro.[/COLOR]
Case WM_NCDESTROY, WM_COMMAND
UnSubClassMsgBx hwnd
lRet = vbOK
End Select
[COLOR=seagreen]' Pass Intercepted Messages To The Original WinProc[/COLOR]
NewMsgBxWindowProc = _
CallWindowProc(lOldMsgBxWindowProc, hwnd, MSG, WParam, lparam)
End Function
[COLOR=seagreen]'here is the modeless msgbox function signature[/COLOR]
[COLOR=seagreen]'which is basically a wrapper for a standard msgbox.[/COLOR]
Public Function ModelessMsgBox _
(Prompt As String, _
AsyncProcName As String, _
Optional Flags As VbMsgBoxStyle, _
Optional Title As String, _
Optional HelpFile As String, _
Optional Context As Long) As VbMsgBoxResult
Call CreateHook(AsyncProcName)
ModelessMsgBox = MsgBox(Prompt, Flags, Title, HelpFile, Context)
End Function