-1

All is in the title... In VBA, is it possible to get the UserForm Object from its Handle retrieved with the API function GetActiveWindow in order to create a MsgBox-like function that works well in a Modeless UserForm ? Thanks in advance for any proposal

Jean-Paul
  • 1
  • 3
  • How do you know that the handle retrieved from the API function GetActiveWindow belongs to a VBA UserForm? – GSerg May 13 '23 at 14:39
  • Because the function I'm trying to write strictly applies to a Control within a UserForm. I could pass the UserForm (Me) as a parameter but I would like to avoid this if ever possible. – Jean-Paul May 13 '23 at 14:54
  • Then what role does `GetActiveWindow` play, and why cannot you pass the control in question rather than the form? – GSerg May 13 '23 at 15:00
  • In fact I'm looking for the ActiveControl of a UserForm that I just know is the active window in order to imitate a MsgBox with a "MsgBoxInControl" (same parameters as MsgBox, so no Me) that would reset the Focus (and SelStart if a TextBox) after display, what the regualr MsgBox does not do. – Jean-Paul May 13 '23 at 15:04
  • It's a styling exercice, as indeed, I could pass the Control or the UserForm on top of regular MsgBox parameters. Just an attempt ! – Jean-Paul May 13 '23 at 15:07
  • I mean, what the regular MsgBox does not do when the UserForm is shown vbModeless ! – Jean-Paul May 13 '23 at 15:15
  • 2 points, as I'm not familiar with this Web site... 1 - Am not notified by email of any update / comment done here (have to stay on the page and refresh) 2 - I don't know how to post a file to illustrate the point – Jean-Paul May 13 '23 at 15:29

2 Answers2

0

And eventually the global code for a VBA Module of a fully efficient MsgBox in a Modeless UserForm:

Option Explicit

Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long

'---------------------------------------------
'MsgBox in a Modeless UserForm
'Same parameters as a regular MsgBox
'Return: Same return value as a regular MsgBox
'---------------------------------------------
Function MsgBoxInModelessUserForm(Prompt As String, _
                                  Optional Buttons As VbMsgBoxStyle = vbOKOnly, _
                                  Optional Title As String = "Microsoft Excel", _
                                  Optional HelpFile As String = "", _
                                  Optional Context As Integer = 0) As VbMsgBoxResult
                          
    Dim UserForm As Object
    Dim Control As Control
    Dim ReturnValue As VbMsgBoxResult
    
    ReturnValue = MsgBox(Prompt, Buttons, Title, HelpFile, Context)
    
    'Get active UserForm
    Set UserForm = GetActiveUserForm
    
    If Not UserForm Is Nothing Then
        Call ForceSetFocusInReactivatedModelessUserForm(UserForm)
    End If
    
    'Return value
    MsgBoxInModelessUserForm = ReturnValue
End Function

'---------------------------------------------------------------
'Force the Focus after the re-activation of a Modeless UserForm
'Can be used when returning from MsgBox or from another UserForm
'---------------------------------------------------------------
Sub ForceSetFocusInReactivatedModelessUserForm(UserForm_Or_Control As Object)
    Dim Control As MSForms.Control
    
    'Get the Control
    If TypeOf UserForm_Or_Control Is UserForm Then
        Set Control = UserForm_Or_Control.ActiveControl
    Else
        Set Control = UserForm_Or_Control
    End If

    With Control
        'Force Control activation
        'Warning !  This will trigger a Control_Exit() + Control_Enter() on the Active Control of the UserForm !
        '           So if coded, use a Public UserForm Flag to ignore these events in the case of Reactivation
        'UserForm.PublicReactivationFlag = True
        .Visible = False    'Triggers a Control_Exit()
        'UserForm.PublicReactivationFlag = True
        .Visible = True     'Triggers a Control_Enter()
        .SetFocus
    End With
End Sub

'------------------------------------------------------------
'Returns the UserForm Object of the currently active UserForm
'------------------------------------------------------------
Function GetActiveUserForm() As Object
    Dim UserForm As Object
    Dim WindowText As String
    
    WindowText = String(GetWindowTextLength(GetActiveWindow) + 1, Chr$(0))
    Call GetWindowText(GetActiveWindow, WindowText, Len(WindowText))
    WindowText = Left(WindowText, Len(WindowText) - 1)
    'MsgBox "<" & WindowText & ">"
    
    'Run through the visible UserForms of the Projet
    For Each UserForm In VBA.UserForms
        If UserForm.Visible Then
            If UserForm.Caption = WindowText Then Exit For
        End If
    Next UserForm
    
    If Not UserForm Is Nothing Then
        'Return value
        Set GetActiveUserForm = UserForm
    End If
End Function
Jean-Paul
  • 1
  • 3
  • I've made another version that uses a Mouse Click to reactivate the Control instead of using the Control.Visible property and its drawback of triggereing Control_Exit() and Control_Enter() events. It's stored here -> https://excel-downloads.com/resources/vba-userform-vbmodeless-msgbox-dans-un-userform-affiche-en-vbmodeless.1431/ – Jean-Paul May 20 '23 at 08:59
-1

OK I think I found a way to find the active UserForm...

Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long

'------------------------------------------------------------
'Returns the UserForm Object of the currently active UserForm
'------------------------------------------------------------
Function GetActiveUserForm() As Object
    Dim UserForm As Object
    Dim WindowText As String
    
    WindowText = String(GetWindowTextLength(GetActiveWindow) + 1, Chr$(0))
    Call GetWindowText(GetActiveWindow, WindowText, Len(WindowText))
    WindowText = Left(WindowText, Len(WindowText) - 1)
    'MsgBox "<" & WindowText & ">"
    
    'Run through the visible UserForms of the Projet
    For Each UserForm In VBA.UserForms
        If UserForm.Visible Then
            If UserForm.Caption = WindowText Then Exit For
        End If
    Next UserForm
    
    If Not UserForm Is Nothing Then
        'Return value
        Set GetActiveUserForm = UserForm
    End If
End Function
Jean-Paul
  • 1
  • 3
  • Sorry, I just can't place the code in the correct tags, I don't understand how this works here. Using the {} then "Place the code here" just returns a messy display. – Jean-Paul May 13 '23 at 16:18
  • Ok got it for the code sample ! Not very intuitive. – Jean-Paul May 13 '23 at 16:29