Option Explicit
Private Type POINTAPI
X As Long
Y As Long
End Type
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type MOUSEHOOKSTRUCT
pt As POINTAPI
hwnd As Long
wHitTestCode As Long
dwExtraInfo As Long
End Type
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 GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function PtInRect Lib "user32" (lpRect As RECT, ByVal X As Long, ByVal Y As Long) As Long
Private Declare Function ClientToScreen Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long
Private Declare Function GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function GetActiveWindow Lib "user32" () As Long
Private Declare Function SetRect Lib "user32" (lpRect As RECT, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Private Const WH_MOUSE_LL As Long = 14
Private Const WM_MOUSEWHEEL As Long = &H20A
Private Const HC_ACTION As Long = 0
Private Const POINTSPERINCH As Long = 72
Private Const LOGPIXELSX As Long = 88
Private Const LOGPIXELSY As Long = 90
Private Const SCROLL_CHANGE As Long = 5
Private lMouseHook As Long
Private lFormHwnd As Long
Private bHookIsSet As Boolean
Private oScrollableObject As Object
Public Sub SetScrollHook(ByVal ScrollableObject As Object)
If Not (IsObjectUserForm(ScrollableObject) Or TypeName(ScrollableObject) = "Frame") Then Exit Sub
Set oScrollableObject = ScrollableObject
lFormHwnd = GetActiveWindow
With ScrollableObject
.ScrollBars = fmScrollBarsBoth
.KeepScrollBarsVisible = fmScrollBarsBoth
.PictureAlignment = fmPictureAlignmentTopLeft
' Adjust the values of the scroll width and height properties as required
.ScrollWidth = ScrollableObject.InsideWidth * 3
.ScrollHeight = ScrollableObject.InsideHeight * 2
End With
If Not bHookIsSet Then
lMouseHook = SetWindowsHookEx( _
WH_MOUSE_LL, AddressOf MouseProc, Application.Hinstance, 0)
bHookIsSet = lMouseHook <> 0
End If
End Sub
Public Sub RemoveScrollHook(Optional ByVal Dummy As Boolean)
If bHookIsSet Then
UnhookWindowsHookEx lMouseHook
lMouseHook = 0
bHookIsSet = False
End If
End Sub
Private Function MouseProc(ByVal nCode As Long, ByVal wParam As Long, ByRef lParam As MOUSEHOOKSTRUCT) As Long
Dim tTopLeft As POINTAPI
Dim tBottomRight As POINTAPI
Dim tRect As RECT
GetClientRect lFormHwnd, tRect
With oScrollableObject
If IsObjectUserForm(oScrollableObject) Then
tTopLeft.X = tRect.Left
tTopLeft.Y = tRect.Top
tBottomRight.X = tRect.Right
tBottomRight.Y = tRect.Bottom
Else
tTopLeft.X = PTtoPX(.Left, False) + tRect.Left
tTopLeft.Y = PTtoPX(.Top, True) + tRect.Top
tBottomRight.X = PTtoPX(.Left + .Width, False) + tRect.Left
tBottomRight.Y = PTtoPX(.Top + .Height, True) + tRect.Top
End If
End With
ClientToScreen lFormHwnd, tTopLeft
ClientToScreen lFormHwnd, tBottomRight
SetRect tRect, tTopLeft.X, tTopLeft.Y, tBottomRight.X, tBottomRight.Y
On Error GoTo errH
If (nCode = HC_ACTION) And CBool(PtInRect(tRect, lParam.pt.X, lParam.pt.Y)) Then
If wParam = WM_MOUSEWHEEL Then
With oScrollableObject
Select Case GetAsyncKeyState(VBA.vbKeyControl)
Case Is = 0 'vertical scroll
If lParam.hwnd > 0 Then
.ScrollTop = Application.Max(0, .ScrollTop - SCROLL_CHANGE)
Else
.ScrollTop = Application.Min(.ScrollHeight - .InsideHeight, .ScrollTop + SCROLL_CHANGE)
End If
Case Else ' horiz scroll when the Ctl key down
If lParam.hwnd > 0 Then
.ScrollLeft = Application.Max(0, .ScrollLeft - SCROLL_CHANGE)
Else
.ScrollLeft = Application.Min(.ScrollWidth - .InsideWidth, .ScrollLeft + SCROLL_CHANGE)
End If
End Select
End With
End If
End If
MouseProc = CallNextHookEx( _
lMouseHook, nCode, wParam, ByVal lParam)
Exit Function
errH:
RemoveScrollHook
End Function
Private Function ScreenDPI(bVert As Boolean) As Long
Static lDPI(1), lDC
If lDPI(0) = 0 Then
lDC = GetDC(0)
lDPI(0) = GetDeviceCaps(lDC, LOGPIXELSX)
lDPI(1) = GetDeviceCaps(lDC, LOGPIXELSY)
lDC = ReleaseDC(0, lDC)
End If
ScreenDPI = lDPI(Abs(bVert))
End Function
Private Function PTtoPX _
(Points As Single, bVert As Boolean) As Long
PTtoPX = Points * ScreenDPI(bVert) / POINTSPERINCH
End Function
Private Function IsObjectUserForm(ByVal obj As Object) As Boolean
Dim oTemp As Object
On Error Resume Next
Set oTemp = obj.Parent
Set oTemp = Nothing
IsObjectUserForm = Err.Number = 438
On Error GoTo 0
End Function