Generic Class for detecting Mouse Enter & Leave Events on Userform Controls.

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,615
Office Version
  1. 2016
Platform
  1. Windows
The code doesn't rely entirely on the native MouseMove event. This so that it detects when the mouse leaves controls that overlap or those that are located at the edges of the userform... It also works on controls that are located inside Frames and Multipages... The Class as well as the pseudo-events are very easy to use by the userform.

Workbook Demo









1- Class Code (C_MouseEnterLeave)
VBA Code:
Option Explicit

Private Type POINTAPI
        x As Long
        Y As Long
End Type

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongLong, ppacc As Any, pvarChild As Variant) As Long
        Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongLong)
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
    #End If
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
#Else
    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 GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
#End If

Private WithEvents lbl As MsForms.Label
Private WithEvents txt As MsForms.TextBox
Private WithEvents cbx As MsForms.ComboBox
Private WithEvents lbx As MsForms.ListBox
Private WithEvents chx As MsForms.CheckBox
Private WithEvents opt As MsForms.OptionButton
Private WithEvents tgl As MsForms.ToggleButton
Private WithEvents frm As MsForms.Frame
Private WithEvents cbt As MsForms.CommandButton
Private WithEvents tbs As MsForms.TabStrip
Private WithEvents mlp As MsForms.MultiPage
Private WithEvents img As MsForms.Image

Private oThis  As C_MouseEnterLeave, oForm As Object




'________________________________Class Public Method__________________________________________________

Public Sub AddControl(ByVal ThisClass As C_MouseEnterLeave, ByVal Ctrl As MsForms.Control)

    Set oThis = ThisClass
    Set oForm = GetUserFormObject(Ctrl)
  
    Select Case True
        Case TypeOf Ctrl Is MsForms.Label
            Set lbl = Ctrl
        Case TypeOf Ctrl Is MsForms.TextBox
            Set txt = Ctrl
        Case TypeOf Ctrl Is MsForms.ComboBox
            Set cbx = Ctrl
        Case TypeOf Ctrl Is MsForms.ListBox
            Set lbx = Ctrl
        Case TypeOf Ctrl Is MsForms.CheckBox
            Set chx = Ctrl
        Case TypeOf Ctrl Is MsForms.OptionButton
            Set opt = Ctrl
        Case TypeOf Ctrl Is MsForms.ToggleButton
            Set tgl = Ctrl
        Case TypeOf Ctrl Is MsForms.Frame
            Set frm = Ctrl
        Case TypeOf Ctrl Is MsForms.CommandButton
            Set cbt = Ctrl
        Case TypeOf Ctrl Is MsForms.TabStrip
            Set tbs = Ctrl
        Case TypeOf Ctrl Is MsForms.MultiPage
            Set mlp = Ctrl
        Case TypeOf Ctrl Is MsForms.Image
            Set img = Ctrl
    End Select

End Sub



'________________________________Class Private Routines__________________________________________________

Private Sub GenericMouseMoveEvent(ByVal Ctrl As MsForms.Control, Optional ByVal Index As Long)

    Const CHILDID_SELF = &H0&
    Const ROLE_SYSTEM_PANE = &H10
  
    Static bDoLooping As Boolean
  
    Dim tCurPos As POINTAPI, oCurAcc As IAccessible, oPrevAcc As IAccessible
    Dim px1 As Long, py1 As Long, pw1 As Long, ph1 As Long
    Dim px2 As Long, py2 As Long, pw2 As Long, ph2 As Long
    Dim sCurAccLocation As String, sPrevAccLocation As String


    If bDoLooping Or oForm.Tag = "TaggedUserForm" Then Exit Sub
    oForm.Tag = "TaggedUserForm"
  
    Do
  
        bDoLooping = True
        Call GetCursorPos(tCurPos)
      
        #If Win64 Then
            Dim ptr As LongLong
            Call CopyMemory(ptr, tCurPos, LenB(tCurPos))
            Call AccessibleObjectFromPoint(ptr, oCurAcc, 0&)
        #Else
            Call AccessibleObjectFromPoint(tCurPos.x, tCurPos.Y, oCurAcc, 0&)
        #End If
      
        Call oCurAcc.accLocation(px1, py1, pw1, ph1, CHILDID_SELF)
        If Not oPrevAcc Is Nothing Then
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
        sCurAccLocation = CStr(px1) & CStr(py1) & CStr(pw1) & CStr(ph1)
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
      
        If oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            Call oForm.UserForm_OnControlMouseEnter(Ctrl)
        End If
        Set oPrevAcc = Ctrl
        Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
      
        If TypeOf Ctrl Is MsForms.MultiPage And oCurAcc.accRole(0&) = ROLE_SYSTEM_PANE Then
            Set oPrevAcc = Ctrl.Pages(Ctrl.Value)
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
      
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
      
        DoEvents
      
    Loop Until sCurAccLocation <> sPrevAccLocation
  
    bDoLooping = False
    Call oForm.UserForm_OnControlMouseLeave(Ctrl)
    oForm.Tag = ""

End Sub

Private Function GetUserFormObject(ByVal Ctrl As MsForms.Control) As Object

    Dim oTemp As Object
  
    Set oTemp = Ctrl.Parent
    Do While TypeOf oTemp Is MsForms.Control
        Set oTemp = oTemp.Parent
        DoEvents
    Loop
    Set GetUserFormObject = oTemp
  
End Function

Private Sub lbl_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(lbl)
End Sub
Private Sub txt_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(txt)
End Sub
Private Sub cbx_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(cbx)
End Sub
Private Sub lbx_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(lbx)
End Sub
Private Sub chx_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(chx)
End Sub
Private Sub opt_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(opt)
End Sub
Private Sub tgl_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(tgl)
End Sub
Private Sub frm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(frm)
End Sub
Private Sub cbt_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(cbt)
End Sub
Private Sub tbs_MouseMove(ByVal Index As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(tbs)
End Sub
Private Sub mlp_MouseMove(ByVal Index As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(mlp, Index + 1)
End Sub
Private Sub img_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(img)
End Sub




2- Code Usage (UserForm)
VBA Code:
Option Explicit

'Jaafar Tribak @MrExcel.com on 07/09/2020
'Class Name : C_MouseEnterLeave:
'Class Only Method : AddControl(ByVal ThisClass As C_MouseEnterLeave, ByVal Ctrl As MsForms.Control)


Private Sub UserForm_Initialize()

    Dim oClass As C_MouseEnterLeave
    Dim oCtrl As MsForms.Control
  
    For Each oCtrl In Me.Controls
        Set oClass = New C_MouseEnterLeave
        Call oClass.AddControl(ThisClass:=oClass, Ctrl:=oCtrl)
    Next

End Sub


'Pseudo-Events Handlers Must Be Declared As PUBLIC !!
'=============================================

Public Sub UserForm_OnControlMouseEnter(ByVal Ctrl As MsForms.Control)
  
    If TypeOf Ctrl Is MultiPage Then
        Debug.Print "Mouse Entered ...  (" & Ctrl.Name & ")" & Ctrl.Pages(Ctrl.Value).Name
    Else
        Debug.Print "Mouse Entered ...  (" & Ctrl.Name & ")"
    End If
  
End Sub


Public Sub UserForm_OnControlMouseLeave(ByVal Ctrl As MsForms.Control)

    If TypeOf Ctrl Is MultiPage Then
        Debug.Print "Mouse Left ...  (" & Ctrl.Name & ")" & Ctrl.Pages(Ctrl.Value).Name
    Else
        Debug.Print "Mouse Left ...  (" & Ctrl.Name & ")"
    End If

End Sub
 
I tried to add UserForm as Control such as
End Sub
Rich (BB code):
Private WithEvents ufm As MSForms.UserForm

Case TypeOf Ctrl Is MSForms.UserForm
            Set ufm = Ctrl

Private Sub ufm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(ufm)
However it cannot detect mouse enter or leave unlike other object. Can this be done with this Class module? I can see that the UserForm also have MouseMove event.
 
Upvote 0

Excel Facts

Did you know Excel offers Filter by Selection?
Add the AutoFilter icon to the Quick Access Toolbar. Select a cell containing Apple, click AutoFilter, and you will get all rows with Apple
The code doesn't rely entirely on the native MouseMove event. This so that it detects when the mouse leaves controls that overlap or those that are located at the edges of the userform... It also works on controls that are located inside Frames and Multipages... The Class as well as the pseudo-events are very easy to use by the userform.

Workbook Demo









1- Class Code (C_MouseEnterLeave)
VBA Code:
Option Explicit

Private Type POINTAPI
        x As Long
        Y As Long
End Type

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongLong, ppacc As Any, pvarChild As Variant) As Long
        Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongLong)
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
    #End If
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
#Else
    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 GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
#End If

Private WithEvents lbl As MsForms.Label
Private WithEvents txt As MsForms.TextBox
Private WithEvents cbx As MsForms.ComboBox
Private WithEvents lbx As MsForms.ListBox
Private WithEvents chx As MsForms.CheckBox
Private WithEvents opt As MsForms.OptionButton
Private WithEvents tgl As MsForms.ToggleButton
Private WithEvents frm As MsForms.Frame
Private WithEvents cbt As MsForms.CommandButton
Private WithEvents tbs As MsForms.TabStrip
Private WithEvents mlp As MsForms.MultiPage
Private WithEvents img As MsForms.Image

Private oThis  As C_MouseEnterLeave, oForm As Object




'________________________________Class Public Method__________________________________________________

Public Sub AddControl(ByVal ThisClass As C_MouseEnterLeave, ByVal Ctrl As MsForms.Control)

    Set oThis = ThisClass
    Set oForm = GetUserFormObject(Ctrl)
 
    Select Case True
        Case TypeOf Ctrl Is MsForms.Label
            Set lbl = Ctrl
        Case TypeOf Ctrl Is MsForms.TextBox
            Set txt = Ctrl
        Case TypeOf Ctrl Is MsForms.ComboBox
            Set cbx = Ctrl
        Case TypeOf Ctrl Is MsForms.ListBox
            Set lbx = Ctrl
        Case TypeOf Ctrl Is MsForms.CheckBox
            Set chx = Ctrl
        Case TypeOf Ctrl Is MsForms.OptionButton
            Set opt = Ctrl
        Case TypeOf Ctrl Is MsForms.ToggleButton
            Set tgl = Ctrl
        Case TypeOf Ctrl Is MsForms.Frame
            Set frm = Ctrl
        Case TypeOf Ctrl Is MsForms.CommandButton
            Set cbt = Ctrl
        Case TypeOf Ctrl Is MsForms.TabStrip
            Set tbs = Ctrl
        Case TypeOf Ctrl Is MsForms.MultiPage
            Set mlp = Ctrl
        Case TypeOf Ctrl Is MsForms.Image
            Set img = Ctrl
    End Select

End Sub



'________________________________Class Private Routines__________________________________________________

Private Sub GenericMouseMoveEvent(ByVal Ctrl As MsForms.Control, Optional ByVal Index As Long)

    Const CHILDID_SELF = &H0&
    Const ROLE_SYSTEM_PANE = &H10
 
    Static bDoLooping As Boolean
 
    Dim tCurPos As POINTAPI, oCurAcc As IAccessible, oPrevAcc As IAccessible
    Dim px1 As Long, py1 As Long, pw1 As Long, ph1 As Long
    Dim px2 As Long, py2 As Long, pw2 As Long, ph2 As Long
    Dim sCurAccLocation As String, sPrevAccLocation As String


    If bDoLooping Or oForm.Tag = "TaggedUserForm" Then Exit Sub
    oForm.Tag = "TaggedUserForm"
 
    Do
 
        bDoLooping = True
        Call GetCursorPos(tCurPos)
     
        #If Win64 Then
            Dim ptr As LongLong
            Call CopyMemory(ptr, tCurPos, LenB(tCurPos))
            Call AccessibleObjectFromPoint(ptr, oCurAcc, 0&)
        #Else
            Call AccessibleObjectFromPoint(tCurPos.x, tCurPos.Y, oCurAcc, 0&)
        #End If
     
        Call oCurAcc.accLocation(px1, py1, pw1, ph1, CHILDID_SELF)
        If Not oPrevAcc Is Nothing Then
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
        sCurAccLocation = CStr(px1) & CStr(py1) & CStr(pw1) & CStr(ph1)
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
     
        If oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            Call oForm.UserForm_OnControlMouseEnter(Ctrl)
        End If
        Set oPrevAcc = Ctrl
        Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
     
        If TypeOf Ctrl Is MsForms.MultiPage And oCurAcc.accRole(0&) = ROLE_SYSTEM_PANE Then
            Set oPrevAcc = Ctrl.Pages(Ctrl.Value)
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
     
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
     
        DoEvents
     
    Loop Until sCurAccLocation <> sPrevAccLocation
 
    bDoLooping = False
    Call oForm.UserForm_OnControlMouseLeave(Ctrl)
    oForm.Tag = ""

End Sub

Private Function GetUserFormObject(ByVal Ctrl As MsForms.Control) As Object

    Dim oTemp As Object
 
    Set oTemp = Ctrl.Parent
    Do While TypeOf oTemp Is MsForms.Control
        Set oTemp = oTemp.Parent
        DoEvents
    Loop
    Set GetUserFormObject = oTemp
 
End Function

Private Sub lbl_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(lbl)
End Sub
Private Sub txt_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(txt)
End Sub
Private Sub cbx_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(cbx)
End Sub
Private Sub lbx_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(lbx)
End Sub
Private Sub chx_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(chx)
End Sub
Private Sub opt_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(opt)
End Sub
Private Sub tgl_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(tgl)
End Sub
Private Sub frm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(frm)
End Sub
Private Sub cbt_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(cbt)
End Sub
Private Sub tbs_MouseMove(ByVal Index As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(tbs)
End Sub
Private Sub mlp_MouseMove(ByVal Index As Long, ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(mlp, Index + 1)
End Sub
Private Sub img_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
    Call GenericMouseMoveEvent(img)
End Sub




2- Code Usage (UserForm)
VBA Code:
Option Explicit

'Jaafar Tribak @MrExcel.com on 07/09/2020
'Class Name : C_MouseEnterLeave:
'Class Only Method : AddControl(ByVal ThisClass As C_MouseEnterLeave, ByVal Ctrl As MsForms.Control)


Private Sub UserForm_Initialize()

    Dim oClass As C_MouseEnterLeave
    Dim oCtrl As MsForms.Control
 
    For Each oCtrl In Me.Controls
        Set oClass = New C_MouseEnterLeave
        Call oClass.AddControl(ThisClass:=oClass, Ctrl:=oCtrl)
    Next

End Sub


'Pseudo-Events Handlers Must Be Declared As PUBLIC !!
'=============================================

Public Sub UserForm_OnControlMouseEnter(ByVal Ctrl As MsForms.Control)
 
    If TypeOf Ctrl Is MultiPage Then
        Debug.Print "Mouse Entered ...  (" & Ctrl.Name & ")" & Ctrl.Pages(Ctrl.Value).Name
    Else
        Debug.Print "Mouse Entered ...  (" & Ctrl.Name & ")"
    End If
 
End Sub


Public Sub UserForm_OnControlMouseLeave(ByVal Ctrl As MsForms.Control)

    If TypeOf Ctrl Is MultiPage Then
        Debug.Print "Mouse Left ...  (" & Ctrl.Name & ")" & Ctrl.Pages(Ctrl.Value).Name
    Else
        Debug.Print "Mouse Left ...  (" & Ctrl.Name & ")"
    End If

End Sub
I have been using your code and it works beautifully. how ever i have run into a problem in Excel 2016 -32 &64 bit versions where i can't use checkboxs and i was hoping you could help with this. the
images are in order of occurrence (Last image, Blue means the ActiveX element is not visible)
Error 1.png












Error 2.png
problem3 .png
ui.jpg
 
Upvote 0
@themodmaster8

Hi and welcome to MrExcel.

I could reproduce the error... I think this is a kind of wierd bug in the MsForms controls. Unlike what the error message says, the CheckBox control Does support the mouse_move event.

After a couple of trial & error, I found out that , for some strange reason, it is only when there is an OptionButton on the UserForm that the error happens. If you remove any existing OptionButtons from the userform, the error doesn't occur... Strange isn"t it ?!

Anyways, I think, if we were to sink the control events at runtime , the error would probably not happen.

I will give this a shot and post back later.
 
Upvote 0
@themodmaster8

Hi and welcome to MrExcel.

I could reproduce the error... I think this is a kind of wierd bug in the MsForms controls. Unlike what the error message says, the CheckBox control Does support the mouse_move event.

After a couple of trial & error, I found out that , for some strange reason, it is only when there is an OptionButton on the UserForm that the error happens. If you remove any existing OptionButtons from the userform, the error doesn't occur... Strange isn"t it ?!

Anyways, I think, if we were to sink the control events at runtime , the error would probably not happen.

I will give this a shot and post back later.
I truly appreciate it. I've been scratching me head for a while now as my knowledge with regards to tapping into windows is very limited.
 
Upvote 0
Ok - Below is the new version. The code now sinks the controls mousemove event at runtime so the above-mentioned error no longer occurs.

Unlike the previous code, this new code is designed more in line with the object oriented model (Event-RaiseEvent-WithEvents-Implements), therefore it is more intuitive and easier to use from the user perspective.

MouseEnterLeave_V2.xlsm






1- C_MouseEnterLeave Class:
VBA Code:
Option Explicit

Implements I_Hidden
Event OnControlMouseEnter(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
Event OnControlMouseLeave(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
Private WithEvents oTabStrip As MSForms.TabStrip
Private WithEvents oMutliPage As MSForms.MultiPage

Private Type POINTAPI
    X As Long
    Y As Long
End Type

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

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongLong, ppacc As Any, pvarChild As Variant) As Long
        Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongLong)
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
    #End If
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As LongPtr) As Long
    Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
    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 hForm As LongPtr
#Else
    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 GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
    Private Declare Function GetActiveWindow Lib "user32" () 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 hForm As Long
#End If

Private oForm As Object, oCtrl As MSForms.Control


'___________________________________________ Class Init __________________________________________________

Private Sub Class_Initialize()
    hForm = GetActiveWindow
End Sub


'__________________________________________ Class Public Method __________________________________________________


Public Sub HookControls(ByVal Frm As MSForms.UserForm, ByVal bHook As Boolean)
    Dim oClass As I_Hidden
    Dim oCtrl As MSForms.Control
    If bHook Then
        Call SetProp(hForm, "HOOKED", -1)
        For Each oCtrl In Frm.Controls
            Set oClass = New C_MouseEnterLeave
            Call oClass.SetControlEvent(oCtrl)
        Next
    Else
        Call SetProp(hForm, "HOOKED", 0)
    End If
End Sub


'__________________________________________ Interface Implementation __________________________________________________

Private Sub I_Hidden_SetControlEvent(ByVal Ctrl As MSForms.Control)
    Set oCtrl = Ctrl
    Set oForm = GetUserFormObject(Ctrl)
    SetControlEvents(Me, Ctrl) = True
    Select Case True
        Case TypeOf Ctrl Is MSForms.TabStrip
            Set oTabStrip = Ctrl
        Case TypeOf Ctrl Is MSForms.MultiPage
            Set oMutliPage = Ctrl
    End Select
End Sub


'__________________________________________ Class Private Routines__________________________________________________

Private Sub GenericMouseMoveEvent(ByVal Ctrl As MSForms.Control, Optional ByVal Index As Long)

    Const CHILDID_SELF = &H0&
    Const ROLE_SYSTEM_PANE = &H10
    
    Static bDoLooping As Boolean
    
    Dim tCurPos As POINTAPI, oCurAcc As IAccessible, oPrevAcc As IAccessible
    Dim px1 As Long, py1 As Long, pw1 As Long, ph1 As Long
    Dim px2 As Long, py2 As Long, pw2 As Long, ph2 As Long
    Dim sCurAccLocation As String, sPrevAccLocation As String
    Dim oPage As MSForms.Page
    
    If bDoLooping Or oForm.Tag = "TaggedUserForm" Then Exit Sub
    oForm.Tag = "TaggedUserForm"

    Do
        bDoLooping = True
        Set oForm.Form = Me
        Call GetCursorPos(tCurPos)
        
        #If Win64 Then
            Dim ptr As LongLong
            Call CopyMemory(ptr, tCurPos, LenB(tCurPos))
            Call AccessibleObjectFromPoint(ptr, oCurAcc, CHILDID_SELF)
        #Else
            Call AccessibleObjectFromPoint(tCurPos.X, tCurPos.Y, oCurAcc, CHILDID_SELF)
        #End If
        
        Call oCurAcc.accLocation(px1, py1, pw1, ph1, CHILDID_SELF)
        If Not oPrevAcc Is Nothing Then
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
        sCurAccLocation = CStr(px1) & CStr(py1) & CStr(pw1) & CStr(ph1)
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
        
        If TypeOf Ctrl Is MSForms.MultiPage Then
            Set oPage = Ctrl.Pages(Ctrl.Value)
        Else
            Set oPage = Nothing
        End If

        If Not oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            RaiseEvent OnControlMouseLeave(Ctrl, oPage)
        End If
        
        If oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            RaiseEvent OnControlMouseEnter(Ctrl, oPage)
        End If

        Set oPrevAcc = Ctrl
        Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        
        If TypeOf Ctrl Is MSForms.MultiPage And oCurAcc.accRole(0&) = ROLE_SYSTEM_PANE Then
            Set oPrevAcc = Ctrl.Pages(Ctrl.Value)
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
        
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
        
        DoEvents
        
    Loop Until sCurAccLocation <> sPrevAccLocation

    bDoLooping = False
    oForm.Tag = ""

End Sub

Private Function GetUserFormObject(ByVal Ctrl As MSForms.Control) As Object
    Dim oTemp As Object
    Set oTemp = Ctrl.Parent
    Do While TypeOf oTemp Is MSForms.Control
        Set oTemp = oTemp.Parent
        DoEvents
    Loop
    Set GetUserFormObject = oTemp
End Function

Private Property Let SetControlEvents(oSinkClass As Object, ByVal Ctrl As Object, ByVal SetEvents As Boolean)
    Const S_OK = &H0
    Const IDISPATCH = "{00020400-0000-0000-C000-000000000046}"
    Static lCookie As Long
    Dim tIID As GUID
    
    If IIDFromString(StrPtr(IDISPATCH), tIID) = S_OK Then
        Call ConnectToConnectionPoint(oSinkClass, tIID, SetEvents, Ctrl, lCookie)
        If lCookie Then
            'Debug.Print "Connection set for: " & Ctrl.Name
        Else
            'Debug.Print "Connection failed for: " & Ctrl.Name
        End If
    End If
End Property


'______________________________________________ EVENTS ________________________________________________________

Private Sub oTabStrip_MouseMove( _
    ByVal Index As Long, _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)
    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oTabStrip, oTabStrip.Value)
    End If
End Sub

Private Sub oMutliPage_MouseMove( _
    ByVal Index As Long, _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)

    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oMutliPage, Index)
    End If
End Sub

Public Sub OnMouseMove_DoNotUse( _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)
    'Attribute OnMouseMove_DoNotUse.VB_UserMemId = -606
    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oCtrl)
    End If
End Sub


2- I_Hidden Interface:
VBA Code:
Option Explicit

Public Sub SetControlEvent(ByVal Ctrl As MSForms.Control)
'
End Sub


3- UserForm Usage Example:
VBA Code:
Option Explicit

Public WithEvents Form As C_MouseEnterLeave

Private Sub UserForm_Initialize()
    Set Form = New C_MouseEnterLeave
    Form.HookControls Me, True
    btnEnableEvent.Enabled = False
End Sub

Private Sub btnDisableEvent_Click()
    Form.HookControls Me, False
    btnEnableEvent.Enabled = True
    btnDisableEvent.Enabled = False
End Sub

Private Sub btnEnableEvent_Click()
    Form.HookControls Me, True
    btnEnableEvent.Enabled = False
    btnDisableEvent.Enabled = True
End Sub


'____________________________________ MOUSE ENTER\LEAVE EVENETS _________________________________________________

Private Sub Form_OnControlMouseEnter(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
    With TextBox1
        .SetFocus
        If Page Is Nothing Then
            .Text = .Text & "> " & "Mouse Entered ...   [ " & Ctrl.Name & " ]" & vbLf
        Else
            'Special case for MultiPages
            .Text = .Text & "> " & "Mouse Entered ...   [ " & Ctrl.Name & "-" & Page.Name & " ]" & vbLf
        End If
    End With
End Sub

Private Sub Form_OnControlMouseLeave(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
    With TextBox1
        .SetFocus
        If Page Is Nothing Then
            .Text = .Text & "< " & "Mouse Leave ...   [ " & Ctrl.Name & " ]" & vbLf
        Else
            'Special case for MultiPages
            .Text = .Text & "< " & "Mouse Leave ...   [ " & Ctrl.Name & "-" & Page.Name & " ]" & vbLf
        End If
    End With
End Sub


Note: The code doesn't trap the mouse enter and leave events for Spin and ScrollBar controls as they don't support a mousemove event.
 
Upvote 1
Ok - Below is the new version. The code now sinks the controls mousemove event at runtime so the above-mentioned error no longer occurs.

Unlike the previous code, this new code is designed more in line with the object oriented model (Event-RaiseEvent-WithEvents-Implements), therefore it is more intuitive and easier to use from the user perspective.

MouseEnterLeave_V2.xlsm






1- C_MouseEnterLeave Class:
VBA Code:
Option Explicit

Implements I_Hidden
Event OnControlMouseEnter(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
Event OnControlMouseLeave(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
Private WithEvents oTabStrip As MSForms.TabStrip
Private WithEvents oMutliPage As MSForms.MultiPage

Private Type POINTAPI
    X As Long
    Y As Long
End Type

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

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongLong, ppacc As Any, pvarChild As Variant) As Long
        Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongLong)
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
    #End If
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As LongPtr) As Long
    Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
    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 hForm As LongPtr
#Else
    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 GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
    Private Declare Function GetActiveWindow Lib "user32" () 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 hForm As Long
#End If

Private oForm As Object, oCtrl As MSForms.Control


'___________________________________________ Class Init __________________________________________________

Private Sub Class_Initialize()
    hForm = GetActiveWindow
End Sub


'__________________________________________ Class Public Method __________________________________________________


Public Sub HookControls(ByVal Frm As MSForms.UserForm, ByVal bHook As Boolean)
    Dim oClass As I_Hidden
    Dim oCtrl As MSForms.Control
    If bHook Then
        Call SetProp(hForm, "HOOKED", -1)
        For Each oCtrl In Frm.Controls
            Set oClass = New C_MouseEnterLeave
            Call oClass.SetControlEvent(oCtrl)
        Next
    Else
        Call SetProp(hForm, "HOOKED", 0)
    End If
End Sub


'__________________________________________ Interface Implementation __________________________________________________

Private Sub I_Hidden_SetControlEvent(ByVal Ctrl As MSForms.Control)
    Set oCtrl = Ctrl
    Set oForm = GetUserFormObject(Ctrl)
    SetControlEvents(Me, Ctrl) = True
    Select Case True
        Case TypeOf Ctrl Is MSForms.TabStrip
            Set oTabStrip = Ctrl
        Case TypeOf Ctrl Is MSForms.MultiPage
            Set oMutliPage = Ctrl
    End Select
End Sub


'__________________________________________ Class Private Routines__________________________________________________

Private Sub GenericMouseMoveEvent(ByVal Ctrl As MSForms.Control, Optional ByVal Index As Long)

    Const CHILDID_SELF = &H0&
    Const ROLE_SYSTEM_PANE = &H10
   
    Static bDoLooping As Boolean
   
    Dim tCurPos As POINTAPI, oCurAcc As IAccessible, oPrevAcc As IAccessible
    Dim px1 As Long, py1 As Long, pw1 As Long, ph1 As Long
    Dim px2 As Long, py2 As Long, pw2 As Long, ph2 As Long
    Dim sCurAccLocation As String, sPrevAccLocation As String
    Dim oPage As MSForms.Page
   
    If bDoLooping Or oForm.Tag = "TaggedUserForm" Then Exit Sub
    oForm.Tag = "TaggedUserForm"

    Do
        bDoLooping = True
        Set oForm.Form = Me
        Call GetCursorPos(tCurPos)
       
        #If Win64 Then
            Dim ptr As LongLong
            Call CopyMemory(ptr, tCurPos, LenB(tCurPos))
            Call AccessibleObjectFromPoint(ptr, oCurAcc, CHILDID_SELF)
        #Else
            Call AccessibleObjectFromPoint(tCurPos.X, tCurPos.Y, oCurAcc, CHILDID_SELF)
        #End If
       
        Call oCurAcc.accLocation(px1, py1, pw1, ph1, CHILDID_SELF)
        If Not oPrevAcc Is Nothing Then
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
        sCurAccLocation = CStr(px1) & CStr(py1) & CStr(pw1) & CStr(ph1)
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
       
        If TypeOf Ctrl Is MSForms.MultiPage Then
            Set oPage = Ctrl.Pages(Ctrl.Value)
        Else
            Set oPage = Nothing
        End If

        If Not oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            RaiseEvent OnControlMouseLeave(Ctrl, oPage)
        End If
       
        If oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            RaiseEvent OnControlMouseEnter(Ctrl, oPage)
        End If

        Set oPrevAcc = Ctrl
        Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
       
        If TypeOf Ctrl Is MSForms.MultiPage And oCurAcc.accRole(0&) = ROLE_SYSTEM_PANE Then
            Set oPrevAcc = Ctrl.Pages(Ctrl.Value)
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
       
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
       
        DoEvents
       
    Loop Until sCurAccLocation <> sPrevAccLocation

    bDoLooping = False
    oForm.Tag = ""

End Sub

Private Function GetUserFormObject(ByVal Ctrl As MSForms.Control) As Object
    Dim oTemp As Object
    Set oTemp = Ctrl.Parent
    Do While TypeOf oTemp Is MSForms.Control
        Set oTemp = oTemp.Parent
        DoEvents
    Loop
    Set GetUserFormObject = oTemp
End Function

Private Property Let SetControlEvents(oSinkClass As Object, ByVal Ctrl As Object, ByVal SetEvents As Boolean)
    Const S_OK = &H0
    Const IDISPATCH = "{00020400-0000-0000-C000-000000000046}"
    Static lCookie As Long
    Dim tIID As GUID
   
    If IIDFromString(StrPtr(IDISPATCH), tIID) = S_OK Then
        Call ConnectToConnectionPoint(oSinkClass, tIID, SetEvents, Ctrl, lCookie)
        If lCookie Then
            'Debug.Print "Connection set for: " & Ctrl.Name
        Else
            'Debug.Print "Connection failed for: " & Ctrl.Name
        End If
    End If
End Property


'______________________________________________ EVENTS ________________________________________________________

Private Sub oTabStrip_MouseMove( _
    ByVal Index As Long, _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)
    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oTabStrip, oTabStrip.Value)
    End If
End Sub

Private Sub oMutliPage_MouseMove( _
    ByVal Index As Long, _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)

    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oMutliPage, Index)
    End If
End Sub

Public Sub OnMouseMove_DoNotUse( _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)
    'Attribute OnMouseMove_DoNotUse.VB_UserMemId = -606
    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oCtrl)
    End If
End Sub


2- I_Hidden Interface:
VBA Code:
Option Explicit

Public Sub SetControlEvent(ByVal Ctrl As MSForms.Control)
'
End Sub


3- UserForm Usage Example:
VBA Code:
Option Explicit

Public WithEvents Form As C_MouseEnterLeave

Private Sub UserForm_Initialize()
    Set Form = New C_MouseEnterLeave
    Form.HookControls Me, True
    btnEnableEvent.Enabled = False
End Sub

Private Sub btnDisableEvent_Click()
    Form.HookControls Me, False
    btnEnableEvent.Enabled = True
    btnDisableEvent.Enabled = False
End Sub

Private Sub btnEnableEvent_Click()
    Form.HookControls Me, True
    btnEnableEvent.Enabled = False
    btnDisableEvent.Enabled = True
End Sub


'____________________________________ MOUSE ENTER\LEAVE EVENETS _________________________________________________

Private Sub Form_OnControlMouseEnter(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
    With TextBox1
        .SetFocus
        If Page Is Nothing Then
            .Text = .Text & "> " & "Mouse Entered ...   [ " & Ctrl.Name & " ]" & vbLf
        Else
            'Special case for MultiPages
            .Text = .Text & "> " & "Mouse Entered ...   [ " & Ctrl.Name & "-" & Page.Name & " ]" & vbLf
        End If
    End With
End Sub

Private Sub Form_OnControlMouseLeave(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
    With TextBox1
        .SetFocus
        If Page Is Nothing Then
            .Text = .Text & "< " & "Mouse Leave ...   [ " & Ctrl.Name & " ]" & vbLf
        Else
            'Special case for MultiPages
            .Text = .Text & "< " & "Mouse Leave ...   [ " & Ctrl.Name & "-" & Page.Name & " ]" & vbLf
        End If
    End With
End Sub


Note: The code doesn't trap the mouse enter and leave events for Spin and ScrollBar controls as they don't support a mousemove event.
This solved my problem, Thanks for the help! do you know of a good source of learning for windows dll's (how to make things like this?)
 
Upvote 0
Ok - Below is the new version. The code now sinks the controls mousemove event at runtime so the above-mentioned error no longer occurs.

Unlike the previous code, this new code is designed more in line with the object oriented model (Event-RaiseEvent-WithEvents-Implements), therefore it is more intuitive and easier to use from the user perspective.

MouseEnterLeave_V2.xlsm






1- C_MouseEnterLeave Class:
VBA Code:
Option Explicit

Implements I_Hidden
Event OnControlMouseEnter(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
Event OnControlMouseLeave(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
Private WithEvents oTabStrip As MSForms.TabStrip
Private WithEvents oMutliPage As MSForms.MultiPage

Private Type POINTAPI
    X As Long
    Y As Long
End Type

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

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongLong, ppacc As Any, pvarChild As Variant) As Long
        Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongLong)
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
    #End If
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As LongPtr) As Long
    Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
    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 hForm As LongPtr
#Else
    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 GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
    Private Declare Function GetActiveWindow Lib "user32" () 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 hForm As Long
#End If

Private oForm As Object, oCtrl As MSForms.Control


'___________________________________________ Class Init __________________________________________________

Private Sub Class_Initialize()
    hForm = GetActiveWindow
End Sub


'__________________________________________ Class Public Method __________________________________________________


Public Sub HookControls(ByVal Frm As MSForms.UserForm, ByVal bHook As Boolean)
    Dim oClass As I_Hidden
    Dim oCtrl As MSForms.Control
    If bHook Then
        Call SetProp(hForm, "HOOKED", -1)
        For Each oCtrl In Frm.Controls
            Set oClass = New C_MouseEnterLeave
            Call oClass.SetControlEvent(oCtrl)
        Next
    Else
        Call SetProp(hForm, "HOOKED", 0)
    End If
End Sub


'__________________________________________ Interface Implementation __________________________________________________

Private Sub I_Hidden_SetControlEvent(ByVal Ctrl As MSForms.Control)
    Set oCtrl = Ctrl
    Set oForm = GetUserFormObject(Ctrl)
    SetControlEvents(Me, Ctrl) = True
    Select Case True
        Case TypeOf Ctrl Is MSForms.TabStrip
            Set oTabStrip = Ctrl
        Case TypeOf Ctrl Is MSForms.MultiPage
            Set oMutliPage = Ctrl
    End Select
End Sub


'__________________________________________ Class Private Routines__________________________________________________

Private Sub GenericMouseMoveEvent(ByVal Ctrl As MSForms.Control, Optional ByVal Index As Long)

    Const CHILDID_SELF = &H0&
    Const ROLE_SYSTEM_PANE = &H10
   
    Static bDoLooping As Boolean
   
    Dim tCurPos As POINTAPI, oCurAcc As IAccessible, oPrevAcc As IAccessible
    Dim px1 As Long, py1 As Long, pw1 As Long, ph1 As Long
    Dim px2 As Long, py2 As Long, pw2 As Long, ph2 As Long
    Dim sCurAccLocation As String, sPrevAccLocation As String
    Dim oPage As MSForms.Page
   
    If bDoLooping Or oForm.Tag = "TaggedUserForm" Then Exit Sub
    oForm.Tag = "TaggedUserForm"

    Do
        bDoLooping = True
        Set oForm.Form = Me
        Call GetCursorPos(tCurPos)
       
        #If Win64 Then
            Dim ptr As LongLong
            Call CopyMemory(ptr, tCurPos, LenB(tCurPos))
            Call AccessibleObjectFromPoint(ptr, oCurAcc, CHILDID_SELF)
        #Else
            Call AccessibleObjectFromPoint(tCurPos.X, tCurPos.Y, oCurAcc, CHILDID_SELF)
        #End If
       
        Call oCurAcc.accLocation(px1, py1, pw1, ph1, CHILDID_SELF)
        If Not oPrevAcc Is Nothing Then
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
        sCurAccLocation = CStr(px1) & CStr(py1) & CStr(pw1) & CStr(ph1)
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
       
        If TypeOf Ctrl Is MSForms.MultiPage Then
            Set oPage = Ctrl.Pages(Ctrl.Value)
        Else
            Set oPage = Nothing
        End If

        If Not oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            RaiseEvent OnControlMouseLeave(Ctrl, oPage)
        End If
       
        If oPrevAcc Is Nothing And sCurAccLocation <> sPrevAccLocation Then
            RaiseEvent OnControlMouseEnter(Ctrl, oPage)
        End If

        Set oPrevAcc = Ctrl
        Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
       
        If TypeOf Ctrl Is MSForms.MultiPage And oCurAcc.accRole(0&) = ROLE_SYSTEM_PANE Then
            Set oPrevAcc = Ctrl.Pages(Ctrl.Value)
            Call oPrevAcc.accLocation(px2, py2, pw2, ph2, CHILDID_SELF)
        End If
       
        sPrevAccLocation = CStr(px2) & CStr(py2) & CStr(pw2) & CStr(ph2)
       
        DoEvents
       
    Loop Until sCurAccLocation <> sPrevAccLocation

    bDoLooping = False
    oForm.Tag = ""

End Sub

Private Function GetUserFormObject(ByVal Ctrl As MSForms.Control) As Object
    Dim oTemp As Object
    Set oTemp = Ctrl.Parent
    Do While TypeOf oTemp Is MSForms.Control
        Set oTemp = oTemp.Parent
        DoEvents
    Loop
    Set GetUserFormObject = oTemp
End Function

Private Property Let SetControlEvents(oSinkClass As Object, ByVal Ctrl As Object, ByVal SetEvents As Boolean)
    Const S_OK = &H0
    Const IDISPATCH = "{00020400-0000-0000-C000-000000000046}"
    Static lCookie As Long
    Dim tIID As GUID
   
    If IIDFromString(StrPtr(IDISPATCH), tIID) = S_OK Then
        Call ConnectToConnectionPoint(oSinkClass, tIID, SetEvents, Ctrl, lCookie)
        If lCookie Then
            'Debug.Print "Connection set for: " & Ctrl.Name
        Else
            'Debug.Print "Connection failed for: " & Ctrl.Name
        End If
    End If
End Property


'______________________________________________ EVENTS ________________________________________________________

Private Sub oTabStrip_MouseMove( _
    ByVal Index As Long, _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)
    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oTabStrip, oTabStrip.Value)
    End If
End Sub

Private Sub oMutliPage_MouseMove( _
    ByVal Index As Long, _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)

    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oMutliPage, Index)
    End If
End Sub

Public Sub OnMouseMove_DoNotUse( _
    ByVal Button As Integer, _
    ByVal Shift As Integer, _
    ByVal X As Single, _
    ByVal Y As Single _
)
    'Attribute OnMouseMove_DoNotUse.VB_UserMemId = -606
    If GetProp(hForm, "HOOKED") Then
        Call GenericMouseMoveEvent(oCtrl)
    End If
End Sub


2- I_Hidden Interface:
VBA Code:
Option Explicit

Public Sub SetControlEvent(ByVal Ctrl As MSForms.Control)
'
End Sub


3- UserForm Usage Example:
VBA Code:
Option Explicit

Public WithEvents Form As C_MouseEnterLeave

Private Sub UserForm_Initialize()
    Set Form = New C_MouseEnterLeave
    Form.HookControls Me, True
    btnEnableEvent.Enabled = False
End Sub

Private Sub btnDisableEvent_Click()
    Form.HookControls Me, False
    btnEnableEvent.Enabled = True
    btnDisableEvent.Enabled = False
End Sub

Private Sub btnEnableEvent_Click()
    Form.HookControls Me, True
    btnEnableEvent.Enabled = False
    btnDisableEvent.Enabled = True
End Sub


'____________________________________ MOUSE ENTER\LEAVE EVENETS _________________________________________________

Private Sub Form_OnControlMouseEnter(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
    With TextBox1
        .SetFocus
        If Page Is Nothing Then
            .Text = .Text & "> " & "Mouse Entered ...   [ " & Ctrl.Name & " ]" & vbLf
        Else
            'Special case for MultiPages
            .Text = .Text & "> " & "Mouse Entered ...   [ " & Ctrl.Name & "-" & Page.Name & " ]" & vbLf
        End If
    End With
End Sub

Private Sub Form_OnControlMouseLeave(ByVal Ctrl As MSForms.Control, ByVal Page As MSForms.Page)
    With TextBox1
        .SetFocus
        If Page Is Nothing Then
            .Text = .Text & "< " & "Mouse Leave ...   [ " & Ctrl.Name & " ]" & vbLf
        Else
            'Special case for MultiPages
            .Text = .Text & "< " & "Mouse Leave ...   [ " & Ctrl.Name & "-" & Page.Name & " ]" & vbLf
        End If
    End With
End Sub


Note: The code doesn't trap the mouse enter and leave events for Spin and ScrollBar controls as they don't support a mousemove event.
Mr. Jafafar,
it is an amazing code you wrote here. i am trying to do same thing on my userform, making controls glow when hovered over by mouse. Later i will try to do it with keyboard also.
But i can't seem to implement your example on my userform. Events not firing. can you help me to find the reason why. i am using other events, can it be the reason?
i can paste the code here if it is needed.
thank you in advance and sorry for bad English.
 
Upvote 0
Mr. Jafafar,
it is an amazing code you wrote here. i am trying to do same thing on my userform, making controls glow when hovered over by mouse. Later i will try to do it with keyboard also.
But i can't seem to implement your example on my userform. Events not firing. can you help me to find the reason why. i am using other events, can it be the reason?
i can paste the code here if it is needed.
thank you in advance and sorry for bad English.
Download the workbook from the link in post#15 and then import the C_MouseEnterLeave class module into your project.
 
Upvote 0

Forum statistics

Threads
1,214,904
Messages
6,122,169
Members
449,070
Latest member
webster33

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