'*****************************************************************************************************
' ___ _ _______ __ _ ____ _ _ _______ ___ _ _ _ ___ _ _.
' // \\ /\\ // // \\ // // // // // // \\ // // // // \\ //| //
' //___// //__\ // //__// // // //__// // // // // // // // // // | //
' // // \\ // // \\ // // // \\ // // // // // // // // // | //
'// // // // // // // //___ // \\ // \\__// //__// //___ \\__// // |//
'****************************************************************************************************
' MODULE DE SCROLLING SUR CONTROLS ACTIVX DANS FEUILLE ET USERFORM SANS HOOKING!!!!!!
'Auteurs:
'@jurassic pork
'@patricktoulon
'version:1.6
'date version:13/03/2025
' Dans cet exercice on utilisera pas le hooking mouse
'on utilse le peekmessage avec condition sur l'object Iaccessible dans un do/loop avec doevents
'dans ce do/loop on surveille la sortie de l'Iaccesible et on sort si le curseur n'est plus sur le control
'dans cette version on utilise le m^$eme code pour les controls dans feuille et userform
'****************************************************************************************************
Option Explicit
Public bLooping As Boolean
Private Type POINTAPI: X As Long: Y 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 Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As Msg, ByVal hWnd As LongPtr, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Private Declare PtrSafe Function WaitMessage Lib "user32" () As Long
Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Type Msg: hWnd As LongPtr: message As Long: wParam As LongPtr: lParam As LongPtr: time As Long: pt As POINTAPI: End Type
#If Win64 Then
Private Const NULL_PTR = 0
Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "Oleacc" (ByVal arg1 As LongPtr, ppacc As IAccessible, pvarChild As Variant) As Long
#Else
Private Const NULL_PTR = 0&
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 Function HiWord(Param As LongPtr) As Integer
Call CopyMemory(HiWord, ByVal VarPtr(Param) + 2&, 2&)
End Function
#Else
Private Const NULL_PTR = 0&
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As Msg, ByVal hWnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Private Declare Function WaitMessage Lib "user32" () As Long
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Type Msg: hWnd As Long: message As Long: wParam As Long: lParam As Long: time As Long: pt As POINTAPI: End Type
Private Declare Function GetDpiForWindow Lib "user32" (ByVal hWnd As Long) As Long
Private Function HiWord(Param As Long) As Integer
Call CopyMemory(HiWord, ByVal VarPtr(Param) + 2&, 2&)
End Function
#End If
Function IsScrollable(control) As Boolean
Dim PosControl As IAccessible, pos As POINTAPI, ok As Boolean, role
Select Case True
Case TypeName(control) = "ListBox": role = 33
Case TypeOf control Is ComboBox: role = 33
Case TypeOf control Is Frame: role = 20
Case TypeOf control Is Image: role = 40
Case TypeOf control Is UserForm: role = 16
Case TypeOf control Is ScrollBar: role = 3
End Select
GetCursorPos pos
#If Win64 Then
Dim lngPtr As LongPtr
CopyMemory lngPtr, pos, LenB(pos)
AccessibleObjectFromPoint lngPtr, PosControl, 0
#Else
AccessibleObjectFromPoint pos.X, pos.Y, PosControl, 0
#End If
On Error Resume Next
'permet de continuer à scroller quand on est dans le périmètre de la combobox originale
If PosControl.accRole = 46 And bLooping Then IsScrollable = True: Exit Function 'ne fonctionne que dans le userform
[c1] = PosControl.accRole
ok = PosControl.accRole = role
IsScrollable = ok
End Function
'ici on capte le message de la souris et selon le messgae on appelle la sub scroller qui gère toute les sorte de control
Sub MouseWheelOut(control As Object, Optional pilote As Object = Nothing)
Const WHEEL_DELTA = 120&, WM_MOUSEWHEEL = &H20A, PM_NOREMOVE = &H0&
Dim tMsg As Msg, lDelta As Integer, criter As Boolean, A
Dim PosControl As IAccessible
criter = IsScrollable(control) 'on controle si on est bon pour scroller (permet de pouvoir atteindre le dropbutton de la combo)
If Not criter Or bLooping Then Exit Sub
If Not TypeOf control Is UserForm Then If TypeName(control.Parent) = "Worksheet" Then ActiveSheet.ScrollArea = control.TopLeftCell.Offset(1).Address 'bloque le scrollarea de la feuille a la cellule la plus proche du control( EVITE LES PETITS SURSAUTS)
Do
criter = IsScrollable(control)
If Not criter Then
bLooping = False
If Not TypeOf control Is UserForm Then If TypeName(control.Parent) = "Worksheet" Then ActiveSheet.ScrollArea = "" 'debloque le scrollarea de la feuille
If Not TypeOf control Is UserForm Then If TypeName(control.Parent) = "Worksheet" Then control.TopLeftCell.Offset(1).Select 'la selection provoque le replis de la combo
Exit Sub
End If
bLooping = True
Call WaitMessage
If PeekMessage(tMsg, NULL_PTR, WM_MOUSEWHEEL, WM_MOUSEWHEEL, PM_NOREMOVE) Then
lDelta = HiWord(tMsg.wParam)
scroller lDelta, control, pilote
End If 'End of PeekMessage
DoEvents
Loop While criter
End Sub
Sub scroller(lDelta, control, pilote)
If pilote Is Nothing Then
If TypeOf control Is UserForm Then
If lDelta > 0& Then
control.ScrollTop = Application.Max(control.ScrollTop - 2, 0)
Else
control.ScrollTop = Application.Min(control.ScrollTop + 2, control.ScrollHeight)
End If
End If
Select Case TypeName(control)
Case "Frame"
If lDelta > 0& Then
control.ScrollTop = Application.Max(control.ScrollTop - 8, 0)
Else
control.ScrollTop = Application.Min(control.ScrollTop + 8, control.ScrollHeight)
End If
Case "ListBox", "ComboBox"
If lDelta > 0& Then
On Error Resume Next
control.TopIndex = Application.Max(control.TopIndex - 1, 0)
On Error GoTo 0
Else
On Error Resume Next
control.TopIndex = Application.Min(control.TopIndex + 1, control.ListCount - 1)
On Error GoTo 0
End If
End Select
End If
If Not pilote Is Nothing Then ' par control pilote
Select Case TypeName(pilote)
Case "ScrollBar"
If lDelta > 0& Then
pilote.Value = Application.Max(pilote.Value - pilote.LargeChange, pilote.Min)
Else
pilote.Value = Application.Min(pilote.Value + pilote.LargeChange, pilote.Max)
End If
'On peut ici ajouter des control piloté par un autre comme pour le scrollbar
End Select
End If
End Sub