MouseWheelScrolling ListBoxes & ComboBoxes (In worksheets and UserForms)

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,779
Office Version
  1. 2016
Platform
  1. Windows
Hi all,

This is a continuation of this MrExcel thread which grew to the point where it became confusing and difficult to follow with so many small updates and improvements.

So I am starting here a fresh clean thread with (hopefully) a final update which has been revised and tested on 32bit & 64bit platforms by myself and by a couple of fellow members .

I will be linking this thread in the original one as well.

Workbook demo.







1- API code in a Standard Module:
VBA Code:
Option Explicit

' Jaafar Tribak @ MrExcel.com on 22/04/20 (last update on 05/10/2020)
' Generic code that enables mousewheel scrolling in vba ListBoxes & ComboBoxes
' in worksheests and Userforms .

'Property.
    'EnableMouseWheelScroll (Write Boolean )
        'Args :1- ListOrComboControl As Object
        '          2- Optional WheelScrollLines  As Long =1
        '          3- Optional ChangeControlValueAsYouScroll As Boolean = False


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 MSG
    #If Win64 Then
        hwnd As LongLong
        message As Long
        wParam As LongLong
        lParam As LongLong
    #Else
        hwnd As Long
        message As Long
        wParam As Long
        lParam As Long
    #End If
    time As Long
    pt As POINTAPI
End Type

#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal Point As LongPtr) As LongPtr
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongPtr, ppacc As Any, pvarChild As Variant) As Long
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As LongPtr
    #End If

    Private Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
    Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long
    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 GetClientRect Lib "user32" (ByVal hwnd As LongPtr, lpRect As RECT) As Long
    Private Declare PtrSafe Function GetCursorPos Lib "user32.dll" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDc As LongPtr, ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDc As LongPtr) As Long
    Private Declare PtrSafe Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function GetQueueStatus Lib "user32" (ByVal fuFlags As Long) As Long
    Private Declare PtrSafe Function GetParent Lib "user32" (ByVal hwnd As LongPtr) 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 WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
    Private Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As Long) As Long
    Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal Wparam As Long, ByVal lParam As Long) 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 GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
    Private Declare Function GetCursorPos Lib "user32.dll" (lpPoint As POINTAPI) 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 SetFocus Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function GetQueueStatus Lib "user32" (ByVal fuFlags As Long) As Long
    Private Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long

#End If

Public sUserFeedBack As String
Private bMonitoringMouseWheel As Boolean
Private bSomeKeyIsBeingPressed As Boolean



Public Property Let EnableMouseWheelScroll _
( _
    ByVal ListOrComboControl As Object, _
    Optional ByVal WheelScrollLines As Long = 1, _
    Optional ByVal ChangeControlValueAsYouScroll As Boolean, _
    ByVal Enable As Boolean _
    )
   
    Const WM_MOUSEWHEEL = &H20A
    Const WHEEL_DELTA = 120
    Const WM_LBUTTONDOWN = &H201
    Const WM_LBUTTONUP = &H202
    Const MK_LBUTTON = &H1
    Const WM_KEYDOWN = &H100
    Const WM_KEYUP = &H101
    Const VK_ESCAPE = &H1B
    Const PM_NOREMOVE = &H0
    Const PM_NOYIELD = &H2
    Const QS_KEY = &H1
    Const SM_CXVSCROLL = 2

    #If VBA7 Then
        Static hActualList As LongPtr
        Dim hwnd As LongPtr
    #Else
        Static hActualList As Long
        Dim hwnd As Long
    #End If
   
    Dim tRect As RECT, tMsg As MSG, tCurPos As POINTAPI
    Dim Low As Integer, High As Integer, i As Integer
    Dim vChild As Variant, oIA As IAccessible
   
   
    Call WindowFromAccessibleObject(ListOrComboControl, hwnd)

    If Not bMonitoringMouseWheel Then
        bMonitoringMouseWheel = True
       
        If Enable Then
       
            Call UserFeedBack("Start Monitoring MouseWheel Messages for : (" & ListOrComboControl.Name & ")")
           
            On Error Resume Next
                Application.EnableCancelKey = xlDisabled
            On Error GoTo 0

            Do While IsWindow(hwnd)

                Call GetCursorPos(tCurPos)
                If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tCurPos) = False Then
                    Exit Do
                End If
                   
                #If Win64 Then
                    Dim lPt As LongLong
                    Call CopyMemory(lPt, tCurPos, LenB(lPt))
                    Call AccessibleObjectFromPoint(lPt, oIA, vChild)
                #Else
                    Call AccessibleObjectFromPoint(tCurPos.x, tCurPos.y, oIA, vChild)
                #End If
       
                If oIA.accRole(0&) = 46 Then
                    tCurPos.y = tCurPos.y + PTtoPX(ListOrComboControl.Height, False)
                End If
       
                #If Win64 Then
                    Dim lPt2 As LongLong
                    Call CopyMemory(lPt2, tCurPos, LenB(lPt2))
                    hActualList = WindowFromPoint(lPt2)
                #Else
                    hActualList = WindowFromPoint(tCurPos.x, tCurPos.y)
                #End If
               
                Call WaitMessage
                If PeekMessage(tMsg, 0, 0, 0, PM_NOREMOVE + PM_NOYIELD) Then
               
                    If GetQueueStatus(QS_KEY) Then
                        bSomeKeyIsBeingPressed = True
                    Else
                        bSomeKeyIsBeingPressed = False
                    End If

                    If tMsg.message = WM_MOUSEWHEEL Then
                 
                        Call GetClientRect(hActualList, tRect)
                   
                        #If Win64 Then
                            Dim lParm As LongLong
                            If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tMsg.pt) = True Then
                                If (HighWord64(tMsg.wParam) / WHEEL_DELTA) > 0 Then
                        #Else
                            Dim lParm As Long
                            If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tMsg.pt) = True Then
                                If (HighWord32(tMsg.wParam) / WHEEL_DELTA) > 0 Then
                        #End If
                                    Call UserFeedBack("MouseWheel Scrolling (Up)")
                                    Low = tRect.Right - (GetSystemMetrics(SM_CXVSCROLL) / 2)
                                    High = tRect.Top + ((GetSystemMetrics(SM_CXVSCROLL) / 2) + 1)
                                Else
                                    Call UserFeedBack("MouseWheel Scrolling (Down)")
                                    Low = tRect.Right - (GetSystemMetrics(SM_CXVSCROLL) / 2)
                                    High = tRect.Bottom - ((GetSystemMetrics(SM_CXVSCROLL) / 2) + 1)
                                End If  'End If HighWord
                               
                                lParm = MakeDWord(Low, High)
                                For i = 1 To WheelScrollLines
                                    Call PostMessage(hActualList, WM_LBUTTONDOWN, MK_LBUTTON, lParm)
                                    Call PostMessage(hActualList, WM_LBUTTONUP, MK_LBUTTON, lParm)
                                Next i
                               
                                If TypeName(ListOrComboControl.Parent) = "Worksheet" Then SetFocus hActualList
                               
                            End If  'End If IsMouseOverControl
                    End If  ' End If WM_MOUSEWHEEL
                End If  ' End If PeekMessage
               
                DoEvents
           
            Loop
           
            If TypeName(ListOrComboControl) = "ListBox" Then
                Call PostMessage(GetParent(hActualList), WM_KEYDOWN, VK_ESCAPE, Build_lParam_WM_KEYDOWN(1, &H0, False, False, False))
                Call PostMessage(GetParent(hActualList), WM_KEYUP, VK_ESCAPE, Build_lParam_WM_KEYUP(1, &H0, False, False))
            End If

            On Error Resume Next
                Application.EnableCancelKey = xlInterrupt
            On Error GoTo 0
            bMonitoringMouseWheel = False
           
            Call UserFeedBack("Stopped Monitoring MouseWheel Messages for : (" & ListOrComboControl.Name & ")")
           
        End If  'End If Enable
       
    End If 'End If bMonitoringMouseWheel

End Property



'_____________________________________Helper Private Routines_____________________________________________________

Private Function IsMouseOverControl( _
    ByVal ListOrComboControl As Object, _
    ByVal ChangeControlValueAsYouScroll As Boolean, _
    ByRef CusPos As POINTAPI) As Boolean

    Dim vChild As Variant, oIA As IAccessible
   
    #If Win64 Then
         Dim lPt As LongLong
         CopyMemory lPt, CusPos, LenB(lPt)
         Call AccessibleObjectFromPoint(lPt, oIA, vChild)
     #Else
           Call AccessibleObjectFromPoint(CusPos.x, CusPos.y, oIA, vChild)
     #End If
   
     On Error Resume Next
     
    If Not ListOrComboControl Is Nothing Then
        If oIA.accRole(0&) <> 46 Then
            If bSomeKeyIsBeingPressed = False Then
                If ChangeControlValueAsYouScroll Then
                    ListOrComboControl.value = ListOrComboControl.List(vChild - 1)
                End If
            End If
        End If
    End If
   
    IsMouseOverControl = oIA.accRole(0&) = 33 Or oIA.accRole(0&) = 46
   
End Function

Private Sub UserFeedBack(ByVal Feedback As String)
    Debug.Print Feedback
    sUserFeedBack = Feedback
End Sub

Private Function MakeDWord(ByVal loword As Integer, ByVal hiword As Integer) As Long
    MakeDWord = (hiword * &H10000) Or (loword And &HFFFF&)
End Function

Private Function HighWord32(ByVal wParam As Long) As Integer
    CopyMemory HighWord32, ByVal VarPtr(wParam) + 2, 2
End Function

#If Win64 Then
    Private Function HighWord64(ByVal wParam As LongLong) As Long
        CopyMemory HighWord64, ByVal VarPtr(wParam) + 2, 4
    End Function
#End If

Private Function ScreenDPI(ByVal bVert As Boolean) As Long

    Const LOGPIXELSX As Long = 88
    Const LOGPIXELSY As Long = 90
    Static lDPI(1), hDc

    If lDPI(0) = 0 Then
        hDc = GetDC(0)
        lDPI(0) = GetDeviceCaps(hDc, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(hDc, LOGPIXELSY)
        hDc = ReleaseDC(0, hDc)
    End If
    ScreenDPI = lDPI(Abs(bVert))
End Function

Private Function PTtoPX(ByVal Points As Single, ByVal bVert As Boolean) As Long
    Const POINTSPERINCH As Long = 72
    PTtoPX = Points * ScreenDPI(bVert) / POINTSPERINCH
End Function

Private Function Build_lParam( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ContextCode As Boolean, _
    ByVal ExtendedKey As Boolean, _
    ByVal PreviousKeyState As Boolean, _
    ByVal TransitionState As Boolean) As Long
   
    Dim lParamBits As Long
   
    lParamBits = RepeatCount Or (ScanCode) Or 2 ^ 16
    If ExtendedKey Then lParamBits = lParamBits Or 2 ^ 24
    If ContextCode Then lParamBits = lParamBits Or 2 ^ 29
    If PreviousKeyState Then lParamBits = lParamBits Or 2 ^ 30
    If TransitionState Then lParamBits = lParamBits Or -2 ^ 31
   
    Build_lParam = lParamBits

End Function
 
Private Function Build_lParam_WM_KEYDOWN( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ExtendedKey As Boolean, _
    ByVal ContextCode As Boolean, _
    ByVal PreviousKeyState As Boolean) _
    As Long

    Build_lParam_WM_KEYDOWN = Build_lParam(RepeatCount, ScanCode, ExtendedKey, ContextCode, PreviousKeyState, False)

End Function 
 
Private Function Build_lParam_WM_KEYUP( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ExtendedKey As Boolean, _
    ByVal ContextCode As Boolean) _
    As Long

      Build_lParam_WM_KEYUP = Build_lParam(RepeatCount, ScanCode, ExtendedKey, ContextCode, True, True)

End Function




2- Usage example for worksheet embeeded ListBoxes&ComboBoxes : (goes in the worksheet module)
VBA Code:
Option Explicit

Private Sub ComboBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)

    EnableMouseWheelScroll(ListOrComboControl:=ComboBox1, WheelScrollLines:=1, ChangeControlValueAsYouScroll:=True) = True

    On Error Resume Next
        [F1] = sUserFeedBack '<== Optional
    On Error GoTo 0

End Sub

Private Sub ListBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)

    EnableMouseWheelScroll(ListOrComboControl:=ListBox1, WheelScrollLines:=10, ChangeControlValueAsYouScroll:=False) = True
   
    On Error Resume Next
        [F1] = sUserFeedBack '<== Optional
    On Error GoTo 0

End Sub




3- Usage example for ListBoxes&ComboBoxes on UserForms : (goes in the UserForm module)
VBA Code:
Option Explicit

Private Sub UserForm_Initialize()

    Me.ComboBox1.ListIndex = 10
    Me.ComboBox2.ListIndex = 2

End Sub

Private Sub ComboBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
    'Change the combobox value when scrolling by setting the second Optional arg to TRUE.
    EnableMouseWheelScroll(ListOrComboControl:=ComboBox1, WheelScrollLines:=1, ChangeControlValueAsYouScroll:=True) = True
    lblFeedBack.Caption = sUserFeedBack  '<= Optional
End Sub

Private Sub ComboBox2_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
    EnableMouseWheelScroll(ListOrComboControl:=ComboBox2, WheelScrollLines:=1, ChangeControlValueAsYouScroll:=False) = True
    lblFeedBack.Caption = sUserFeedBack '<= Optional
End Sub

Private Sub ListBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
    EnableMouseWheelScroll(ListOrComboControl:=ListBox1, WheelScrollLines:=1, ChangeControlValueAsYouScroll:=False) = True
    lblFeedBack.Caption = sUserFeedBack '<= Optional
End Sub
 
i was using Autocad to make some tools .and copy the code to my program.

WindowFromAccessibleObject seems something wrong with it.
anyone can help
 
Upvote 0

Excel Facts

What did Pito Salas invent?
Pito Salas, working for Lotus, popularized what would become to be pivot tables. It was released as Lotus Improv in 1989.
1- API code in a Standard Module:
VBA Code:
Option Explicit

' Jaafar Tribak @ MrExcel.com on 22/04/20 (last update on 05/10/2020)
' Generic code that enables mousewheel scrolling in vba ListBoxes & ComboBoxes
' in worksheests and Userforms .

'Property.
    'EnableMouseWheelScroll (Write Boolean )
        'Args :1- ListOrComboControl As Object
        '          2- Optional WheelScrollLines  As Long =1
        '          3- Optional ChangeControlValueAsYouScroll As Boolean = False




'_____________________________________Helper Private Routines_____________________________________________________

Private Function IsMouseOverControl( _
    ByVal ListOrComboControl As Object, _
    ByVal ChangeControlValueAsYouScroll As Boolean, _
    ByRef CusPos As POINTAPI) As Boolean

    Dim vChild As Variant, oIA As IAccessible
  
    #If Win64 Then
         Dim lPt As LongLong
         CopyMemory lPt, CusPos, LenB(lPt)
         Call AccessibleObjectFromPoint(lPt, oIA, vChild)
     #Else
           Call AccessibleObjectFromPoint(CusPos.x, CusPos.y, oIA, vChild)
     #End If
  
     On Error Resume Next
    
    If Not ListOrComboControl Is Nothing Then
        If oIA.accRole(0&) <> 46 Then
            If bSomeKeyIsBeingPressed = False Then
                If ChangeControlValueAsYouScroll Then
                    ListOrComboControl.value = ListOrComboControl.List(vChild - 1)  <<<---this line here
                End If
            End If
        End If
    End If
  
    IsMouseOverControl = oIA.accRole(0&) = 33 Or oIA.accRole(0&) = 46
  
End Function

@Jaafar Tribak

I've been happily using your magical code in Excel 2016 64bit projects and it's never hiccuped once - one of the best pieces of code I have been able to use over and over again on different projects. Well done that man!

I've just 'upgraded' to Excel 2019 64bit and the scrolling is now throwing an error with the combo boxes - the line 'ListOrComboControl.value = ListOrComboControl.List(vChild - 1)' seems to be the cause.
Just to make sure it wasn't something in my projects causing it, I downloaded your GenericListBoxComboBoxMouseWheel_V2 spreadsheet and the same thing occurred. Same line with a 'Run time error 381: Could not get the List property. Invalid property array index.'

cheers
Mike
 
Upvote 0
might help if I passed on that it happens on both worksheet and userform list and combo boxes, especially when they are no longer the focus.
 
Upvote 0
I tried a handful of solution for scrolling userforms so far, they all come with some inconvenients.
This one works very nice in most situations for me :) Thanks so much Jaafar.

Just 1 question:

Is there an easy way to explicitly unhook the mouse?
If a second form opens while "the mouse is listening" (e.g. click on listbox item to open next form) it will not start listening to controls on the second form.
I tried
VBA Code:
EnableMouseWheelScroll (ctrl,1, False) = False
and blocking it from re-hooking by setting a flag, but couldn't get it working.

This demonstrates what I mean easier than words...
 
Upvote 0
@pflosch

Here is an update : GenericListBoxComboBoxMouseWheel_UPDATE.xlsm

Is there an easy way to explicitly unhook the mouse?
Now, you can just set the EnableMouseWheelScroll Property to False as follows:
EnableMouseWheelScroll = False

Notice that I have made all the Property arguments Optional so that when unhooking the mouse, you don't have to worry about passing any args to the Property.

If a second form opens while "the mouse is listening" (e.g. click on listbox item to open next form) it will not start listening to controls on the second form.
I have set up the above workbook example so that when double-clicking an item in ListBox1 (in UsrForm1) a second userform opens... UserForm1 Deactivate event takes care of unhooking the mouse by setting the EnableMouseWheelScroll Property to False thus releasing the mouse hook when UserForm2 opens.
Private Sub UserForm_Deactivate() EnableMouseWheelScroll = False lblFeedBack.Caption = "" End Sub


Here is the updated main code:

In a Standard Module:
VBA Code:
Option Explicit

' Jaafar Tribak @ MrExcel.com on 22/04/20 (last update on 05/10/2020)
' Generic code that enables mousewheel scrolling in vba ListBoxes & ComboBoxes
' in worksheests and Userforms .

'Property.
    'EnableMouseWheelScroll (Write Boolean )
        'Args :    1- ListOrComboControl As Object
        '          2- Optional WheelScrollLines  As Long =1
        '          3- Optional ChangeControlValueAsYouScroll As Boolean = False

'UPDATE: 02/NOV/2022
'======

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 MSG
    #If Win64 Then
        hwnd As LongLong
        message As Long
        wParam As LongLong
        lParam As LongLong
    #Else
        hwnd As Long
        message As Long
        wParam As Long
        lParam As Long
    #End If
    time As Long
    pt As POINTAPI
End Type


#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal Point As LongPtr) As LongPtr
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongPtr, ppacc As Any, pvarChild As Variant) As Long
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As LongPtr
    #End If

    Private Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
    Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long
    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 GetClientRect Lib "user32" (ByVal hwnd As LongPtr, lpRect As RECT) As Long
    Private Declare PtrSafe Function GetCursorPos Lib "user32.dll" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDc As LongPtr, ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDc As LongPtr) As Long
    Private Declare PtrSafe Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function GetQueueStatus Lib "user32" (ByVal fuFlags As Long) As Long
    Private Declare PtrSafe Function GetParent Lib "user32" (ByVal hwnd As LongPtr) 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 WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
    Private Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As Long) As Long
    Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal Wparam As Long, ByVal lParam As Long) 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 GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
    Private Declare Function GetCursorPos Lib "user32.dll" (lpPoint As POINTAPI) 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 SetFocus Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function GetQueueStatus Lib "user32" (ByVal fuFlags As Long) As Long
    Private Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long

#End If

Public sUserFeedBack As String
Private bMonitoringMouseWheel As Boolean
Private bSomeKeyIsBeingPressed As Boolean



Public Property Let EnableMouseWheelScroll _
( _
    Optional ByVal ListOrComboControl As Object, _
    Optional ByVal WheelScrollLines As Long = 1, _
    Optional ByVal ChangeControlValueAsYouScroll As Boolean, _
    ByVal Enable As Boolean _
    )
    

    Const WM_MOUSEWHEEL = &H20A
    Const WHEEL_DELTA = 120
    Const WM_LBUTTONDOWN = &H201
    Const WM_LBUTTONUP = &H202
    Const MK_LBUTTON = &H1
    Const WM_KEYDOWN = &H100
    Const WM_KEYUP = &H101
    Const VK_ESCAPE = &H1B
    Const PM_NOREMOVE = &H0
    Const PM_NOYIELD = &H2
    Const QS_KEY = &H1
    Const SM_CXVSCROLL = 2

    #If VBA7 Then
        Static hActualList As LongPtr
        Dim hwnd As LongPtr
    #Else
        Static hActualList As Long
        Dim hwnd As Long
    #End If
    
    Dim tRect As RECT, tMsg As MSG, tCurPos As POINTAPI
    Dim Low As Integer, High As Integer, i As Integer
    Dim vChild As Variant, oIA As IAccessible
    
    
    If Enable = False Then GoTo Xit
    
    Call WindowFromAccessibleObject(ListOrComboControl, hwnd)

    If Not bMonitoringMouseWheel Then
        bMonitoringMouseWheel = True
        
        If Enable Then
        
            Call UserFeedBack("Start Monitoring MouseWheel Messages for : (" & ListOrComboControl.Name & ")")
            
            On Error Resume Next
                Application.EnableCancelKey = xlDisabled
            On Error GoTo 0

            Do While IsWindow(hwnd)

                Call GetCursorPos(tCurPos)
                If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tCurPos) = False Then
                    Exit Do
                End If
                    
                #If Win64 Then
                    Dim lPt As LongLong
                    Call CopyMemory(lPt, tCurPos, LenB(lPt))
                    Call AccessibleObjectFromPoint(lPt, oIA, vChild)
                #Else
                    Call AccessibleObjectFromPoint(tCurPos.X, tCurPos.Y, oIA, vChild)
                #End If
        
                If oIA.accRole(0&) = 46 Then
                    tCurPos.Y = tCurPos.Y + PTtoPX(ListOrComboControl.Height, False)
                End If
        
                #If Win64 Then
                    Dim lPt2 As LongLong
                    Call CopyMemory(lPt2, tCurPos, LenB(lPt2))
                    hActualList = WindowFromPoint(lPt2)
                #Else
                    hActualList = WindowFromPoint(tCurPos.X, tCurPos.Y)
                #End If
                
                Call WaitMessage
                If PeekMessage(tMsg, 0, 0, 0, PM_NOREMOVE + PM_NOYIELD) Then
                
                    If GetQueueStatus(QS_KEY) Then
                        bSomeKeyIsBeingPressed = True
                    Else
                        bSomeKeyIsBeingPressed = False
                    End If

                    If tMsg.message = WM_MOUSEWHEEL Then
                  
                        Call GetClientRect(hActualList, tRect)
                    
                        #If Win64 Then
                            Dim lParm As LongLong
                            If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tMsg.pt) = True Then
                                If (HighWord64(tMsg.wParam) / WHEEL_DELTA) > 0 Or (HighWord64(tMsg.wParam) = WHEEL_DELTA) Then
                        #Else
                            Dim lParm As Long
                            If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tMsg.pt) = True Then
                                  If (HighWord32(tMsg.wParam) / WHEEL_DELTA) > 0 Or (HighWord32(tMsg.wParam) = WHEEL_DELTA) Then
                        #End If
                                    Call UserFeedBack("MouseWheel Scrolling (Up)")
                                    Low = tRect.Right - (GetSystemMetrics(SM_CXVSCROLL) / 2)
                                    High = tRect.Top + ((GetSystemMetrics(SM_CXVSCROLL) / 2) + 1)
                                Else
                                    Call UserFeedBack("MouseWheel Scrolling (Down)")
                                    Low = tRect.Right - (GetSystemMetrics(SM_CXVSCROLL) / 2)
                                    High = tRect.Bottom - ((GetSystemMetrics(SM_CXVSCROLL) / 2) + 1)
                                End If  'End If HighWord
                                
                                lParm = MakeDWord(Low, High)
                                
                                For i = 1 To WheelScrollLines
                                    Call PostMessage(hActualList, WM_LBUTTONDOWN, MK_LBUTTON, lParm)
                                    Call PostMessage(hActualList, WM_LBUTTONUP, MK_LBUTTON, lParm)
                                Next i
                                
                                If TypeName(ListOrComboControl.Parent) = "Worksheet" Then SetFocus hActualList
                                
                            End If  'End If IsMouseOverControl
                    End If  ' End If WM_MOUSEWHEEL
                End If  ' End If PeekMessage
                
                DoEvents
            Loop
            
            If TypeName(ListOrComboControl) = "ListBox" Then
                Call PostMessage(GetParent(hActualList), WM_KEYDOWN, VK_ESCAPE, Build_lParam_WM_KEYDOWN(1, &H0, False, False, False))
                Call PostMessage(GetParent(hActualList), WM_KEYUP, VK_ESCAPE, Build_lParam_WM_KEYUP(1, &H0, False, False))
            End If

            Call Cleanup(ListOrComboControl)
            
        End If  'End If Enable
        
    End If 'End If bMonitoringMouseWheel
  
  
    Exit Property
Xit:

 Call Cleanup

End Property



'_____________________________________Helper Private Routines_____________________________________________________

Private Sub Cleanup(Optional ByVal Ctrl As Object)

    On Error Resume Next
        Application.EnableCancelKey = xlInterrupt
    On Error GoTo 0
    
    If Ctrl Is Nothing Then
        Call UserFeedBack("Stopped Monitoring MouseWheel Messages for : (")
    Else
        Call UserFeedBack("Stopped Monitoring MouseWheel Messages for : (" & Ctrl.Name & ")")
    End If
    
    bMonitoringMouseWheel = False
    
End Sub

Private Function IsMouseOverControl( _
    ByVal ListOrComboControl As Object, _
    ByVal ChangeControlValueAsYouScroll As Boolean, _
    ByRef CusPos As POINTAPI) As Boolean

    Dim vChild As Variant, oIA As IAccessible
    
    #If Win64 Then
         Dim lPt As LongLong
         CopyMemory lPt, CusPos, LenB(lPt)
         Call AccessibleObjectFromPoint(lPt, oIA, vChild)
     #Else
           Call AccessibleObjectFromPoint(CusPos.X, CusPos.Y, oIA, vChild)
     #End If
    
     On Error Resume Next
      
    If Not ListOrComboControl Is Nothing Then
        If oIA.accRole(0&) <> 46 Then
            If bSomeKeyIsBeingPressed = False Then
                If ChangeControlValueAsYouScroll Then
                    ListOrComboControl.value = ListOrComboControl.List(vChild - 1)
                End If
            End If
        End If
    End If
    
    IsMouseOverControl = oIA.accRole(0&) = 33 Or oIA.accRole(0&) = 46
    
End Function

Private Sub UserFeedBack(ByVal Feedback As String)
    Debug.Print Feedback
    sUserFeedBack = Feedback
End Sub

Private Function MakeDWord(ByVal loword As Integer, ByVal hiword As Integer) As Long
    MakeDWord = (hiword * &H10000) Or (loword And &HFFFF&)
End Function

Private Function HighWord32(ByVal wParam As Long) As Integer
    CopyMemory HighWord32, ByVal VarPtr(wParam) + 2, 2
End Function

#If Win64 Then
    Private Function HighWord64(ByVal wParam As LongLong) As Long
        CopyMemory HighWord64, ByVal VarPtr(wParam) + 2, 4
    End Function
#End If

Private Function ScreenDPI(ByVal bVert As Boolean) As Long

    Const LOGPIXELSX As Long = 88
    Const LOGPIXELSY As Long = 90
    Static lDPI(1), hDc

    If lDPI(0) = 0 Then
        hDc = GetDC(0)
        lDPI(0) = GetDeviceCaps(hDc, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(hDc, LOGPIXELSY)
        hDc = ReleaseDC(0, hDc)
    End If
    ScreenDPI = lDPI(Abs(bVert))
End Function

Private Function PTtoPX(ByVal Points As Single, ByVal bVert As Boolean) As Long
    Const POINTSPERINCH As Long = 72
    PTtoPX = Points * ScreenDPI(bVert) / POINTSPERINCH
End Function


Private Function Build_lParam( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ContextCode As Boolean, _
    ByVal ExtendedKey As Boolean, _
    ByVal PreviousKeyState As Boolean, _
    ByVal TransitionState As Boolean) As Long
    
    Dim lParamBits As Long
    
    lParamBits = RepeatCount Or (ScanCode) Or 2 ^ 16
    If ExtendedKey Then lParamBits = lParamBits Or 2 ^ 24
    If ContextCode Then lParamBits = lParamBits Or 2 ^ 29
    If PreviousKeyState Then lParamBits = lParamBits Or 2 ^ 30
    If TransitionState Then lParamBits = lParamBits Or -2 ^ 31
    
    Build_lParam = lParamBits

End Function

  
Private Function Build_lParam_WM_KEYDOWN( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ExtendedKey As Boolean, _
    ByVal ContextCode As Boolean, _
    ByVal PreviousKeyState As Boolean) _
    As Long

    Build_lParam_WM_KEYDOWN = Build_lParam(RepeatCount, ScanCode, ExtendedKey, ContextCode, PreviousKeyState, False)

End Function
  
  
Private Function Build_lParam_WM_KEYUP( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ExtendedKey As Boolean, _
    ByVal ContextCode As Boolean) _
    As Long

      Build_lParam_WM_KEYUP = Build_lParam(RepeatCount, ScanCode, ExtendedKey, ContextCode, True, True)

End Function
 
Upvote 0
Oh, didn't think of using deactivate event, didn't even know when it fires :rolleyes:
You're the man. Thanx soooooooooo much Jaafar.
 
Upvote 0
@pflosch

Here is an update : GenericListBoxComboBoxMouseWheel_UPDATE.xlsm


Now, you can just set the EnableMouseWheelScroll Property to False as follows:
EnableMouseWheelScroll = False

Notice that I have made all the Property arguments Optional so that when unhooking the mouse, you don't have to worry about passing any args to the Property.


I have set up the above workbook example so that when double-clicking an item in ListBox1 (in UsrForm1) a second userform opens... UserForm1 Deactivate event takes care of unhooking the mouse by setting the EnableMouseWheelScroll Property to False thus releasing the mouse hook when UserForm2 opens.
Private Sub UserForm_Deactivate() EnableMouseWheelScroll = False lblFeedBack.Caption = "" End Sub


Here is the updated main code:

In a Standard Module:
VBA Code:
Option Explicit

' Jaafar Tribak @ MrExcel.com on 22/04/20 (last update on 05/10/2020)
' Generic code that enables mousewheel scrolling in vba ListBoxes & ComboBoxes
' in worksheests and Userforms .

'Property.
    'EnableMouseWheelScroll (Write Boolean )
        'Args :    1- ListOrComboControl As Object
        '          2- Optional WheelScrollLines  As Long =1
        '          3- Optional ChangeControlValueAsYouScroll As Boolean = False

'UPDATE: 02/NOV/2022
'======

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 MSG
    #If Win64 Then
        hwnd As LongLong
        message As Long
        wParam As LongLong
        lParam As LongLong
    #Else
        hwnd As Long
        message As Long
        wParam As Long
        lParam As Long
    #End If
    time As Long
    pt As POINTAPI
End Type


#If VBA7 Then
    #If Win64 Then
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal Point As LongPtr) As LongPtr
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal arg1 As LongPtr, ppacc As Any, pvarChild As Variant) As Long
    #Else
        Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "oleacc" (ByVal lX As Long, ByVal lY As Long, ppacc As IAccessible, pvarChild As Variant) As Long
        Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As LongPtr
    #End If

    Private Declare PtrSafe Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As LongPtr) As Long
    Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hwnd As LongPtr) As Long
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long
    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 GetClientRect Lib "user32" (ByVal hwnd As LongPtr, lpRect As RECT) As Long
    Private Declare PtrSafe Function GetCursorPos Lib "user32.dll" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDc As LongPtr, ByVal nIndex As Long) As Long
    Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hDc As LongPtr) As Long
    Private Declare PtrSafe Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
    Private Declare PtrSafe Function GetQueueStatus Lib "user32" (ByVal fuFlags As Long) As Long
    Private Declare PtrSafe Function GetParent Lib "user32" (ByVal hwnd As LongPtr) 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 WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
    Private Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As IAccessible, phwnd As Long) As Long
    Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
    Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal Wparam As Long, ByVal lParam As Long) 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 GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
    Private Declare Function GetCursorPos Lib "user32.dll" (lpPoint As POINTAPI) 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 SetFocus Lib "user32" (ByVal hwnd As Long) As Long
    Private Declare Function GetQueueStatus Lib "user32" (ByVal fuFlags As Long) As Long
    Private Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long

#End If

Public sUserFeedBack As String
Private bMonitoringMouseWheel As Boolean
Private bSomeKeyIsBeingPressed As Boolean



Public Property Let EnableMouseWheelScroll _
( _
    Optional ByVal ListOrComboControl As Object, _
    Optional ByVal WheelScrollLines As Long = 1, _
    Optional ByVal ChangeControlValueAsYouScroll As Boolean, _
    ByVal Enable As Boolean _
    )
   

    Const WM_MOUSEWHEEL = &H20A
    Const WHEEL_DELTA = 120
    Const WM_LBUTTONDOWN = &H201
    Const WM_LBUTTONUP = &H202
    Const MK_LBUTTON = &H1
    Const WM_KEYDOWN = &H100
    Const WM_KEYUP = &H101
    Const VK_ESCAPE = &H1B
    Const PM_NOREMOVE = &H0
    Const PM_NOYIELD = &H2
    Const QS_KEY = &H1
    Const SM_CXVSCROLL = 2

    #If VBA7 Then
        Static hActualList As LongPtr
        Dim hwnd As LongPtr
    #Else
        Static hActualList As Long
        Dim hwnd As Long
    #End If
   
    Dim tRect As RECT, tMsg As MSG, tCurPos As POINTAPI
    Dim Low As Integer, High As Integer, i As Integer
    Dim vChild As Variant, oIA As IAccessible
   
   
    If Enable = False Then GoTo Xit
   
    Call WindowFromAccessibleObject(ListOrComboControl, hwnd)

    If Not bMonitoringMouseWheel Then
        bMonitoringMouseWheel = True
       
        If Enable Then
       
            Call UserFeedBack("Start Monitoring MouseWheel Messages for : (" & ListOrComboControl.Name & ")")
           
            On Error Resume Next
                Application.EnableCancelKey = xlDisabled
            On Error GoTo 0

            Do While IsWindow(hwnd)

                Call GetCursorPos(tCurPos)
                If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tCurPos) = False Then
                    Exit Do
                End If
                   
                #If Win64 Then
                    Dim lPt As LongLong
                    Call CopyMemory(lPt, tCurPos, LenB(lPt))
                    Call AccessibleObjectFromPoint(lPt, oIA, vChild)
                #Else
                    Call AccessibleObjectFromPoint(tCurPos.X, tCurPos.Y, oIA, vChild)
                #End If
       
                If oIA.accRole(0&) = 46 Then
                    tCurPos.Y = tCurPos.Y + PTtoPX(ListOrComboControl.Height, False)
                End If
       
                #If Win64 Then
                    Dim lPt2 As LongLong
                    Call CopyMemory(lPt2, tCurPos, LenB(lPt2))
                    hActualList = WindowFromPoint(lPt2)
                #Else
                    hActualList = WindowFromPoint(tCurPos.X, tCurPos.Y)
                #End If
               
                Call WaitMessage
                If PeekMessage(tMsg, 0, 0, 0, PM_NOREMOVE + PM_NOYIELD) Then
               
                    If GetQueueStatus(QS_KEY) Then
                        bSomeKeyIsBeingPressed = True
                    Else
                        bSomeKeyIsBeingPressed = False
                    End If

                    If tMsg.message = WM_MOUSEWHEEL Then
                 
                        Call GetClientRect(hActualList, tRect)
                   
                        #If Win64 Then
                            Dim lParm As LongLong
                            If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tMsg.pt) = True Then
                                If (HighWord64(tMsg.wParam) / WHEEL_DELTA) > 0 Or (HighWord64(tMsg.wParam) = WHEEL_DELTA) Then
                        #Else
                            Dim lParm As Long
                            If IsMouseOverControl(ListOrComboControl, ChangeControlValueAsYouScroll, tMsg.pt) = True Then
                                  If (HighWord32(tMsg.wParam) / WHEEL_DELTA) > 0 Or (HighWord32(tMsg.wParam) = WHEEL_DELTA) Then
                        #End If
                                    Call UserFeedBack("MouseWheel Scrolling (Up)")
                                    Low = tRect.Right - (GetSystemMetrics(SM_CXVSCROLL) / 2)
                                    High = tRect.Top + ((GetSystemMetrics(SM_CXVSCROLL) / 2) + 1)
                                Else
                                    Call UserFeedBack("MouseWheel Scrolling (Down)")
                                    Low = tRect.Right - (GetSystemMetrics(SM_CXVSCROLL) / 2)
                                    High = tRect.Bottom - ((GetSystemMetrics(SM_CXVSCROLL) / 2) + 1)
                                End If  'End If HighWord
                               
                                lParm = MakeDWord(Low, High)
                               
                                For i = 1 To WheelScrollLines
                                    Call PostMessage(hActualList, WM_LBUTTONDOWN, MK_LBUTTON, lParm)
                                    Call PostMessage(hActualList, WM_LBUTTONUP, MK_LBUTTON, lParm)
                                Next i
                               
                                If TypeName(ListOrComboControl.Parent) = "Worksheet" Then SetFocus hActualList
                               
                            End If  'End If IsMouseOverControl
                    End If  ' End If WM_MOUSEWHEEL
                End If  ' End If PeekMessage
               
                DoEvents
            Loop
           
            If TypeName(ListOrComboControl) = "ListBox" Then
                Call PostMessage(GetParent(hActualList), WM_KEYDOWN, VK_ESCAPE, Build_lParam_WM_KEYDOWN(1, &H0, False, False, False))
                Call PostMessage(GetParent(hActualList), WM_KEYUP, VK_ESCAPE, Build_lParam_WM_KEYUP(1, &H0, False, False))
            End If

            Call Cleanup(ListOrComboControl)
           
        End If  'End If Enable
       
    End If 'End If bMonitoringMouseWheel
 
 
    Exit Property
Xit:

 Call Cleanup

End Property



'_____________________________________Helper Private Routines_____________________________________________________

Private Sub Cleanup(Optional ByVal Ctrl As Object)

    On Error Resume Next
        Application.EnableCancelKey = xlInterrupt
    On Error GoTo 0
   
    If Ctrl Is Nothing Then
        Call UserFeedBack("Stopped Monitoring MouseWheel Messages for : (")
    Else
        Call UserFeedBack("Stopped Monitoring MouseWheel Messages for : (" & Ctrl.Name & ")")
    End If
   
    bMonitoringMouseWheel = False
   
End Sub

Private Function IsMouseOverControl( _
    ByVal ListOrComboControl As Object, _
    ByVal ChangeControlValueAsYouScroll As Boolean, _
    ByRef CusPos As POINTAPI) As Boolean

    Dim vChild As Variant, oIA As IAccessible
   
    #If Win64 Then
         Dim lPt As LongLong
         CopyMemory lPt, CusPos, LenB(lPt)
         Call AccessibleObjectFromPoint(lPt, oIA, vChild)
     #Else
           Call AccessibleObjectFromPoint(CusPos.X, CusPos.Y, oIA, vChild)
     #End If
   
     On Error Resume Next
     
    If Not ListOrComboControl Is Nothing Then
        If oIA.accRole(0&) <> 46 Then
            If bSomeKeyIsBeingPressed = False Then
                If ChangeControlValueAsYouScroll Then
                    ListOrComboControl.value = ListOrComboControl.List(vChild - 1)
                End If
            End If
        End If
    End If
   
    IsMouseOverControl = oIA.accRole(0&) = 33 Or oIA.accRole(0&) = 46
   
End Function

Private Sub UserFeedBack(ByVal Feedback As String)
    Debug.Print Feedback
    sUserFeedBack = Feedback
End Sub

Private Function MakeDWord(ByVal loword As Integer, ByVal hiword As Integer) As Long
    MakeDWord = (hiword * &H10000) Or (loword And &HFFFF&)
End Function

Private Function HighWord32(ByVal wParam As Long) As Integer
    CopyMemory HighWord32, ByVal VarPtr(wParam) + 2, 2
End Function

#If Win64 Then
    Private Function HighWord64(ByVal wParam As LongLong) As Long
        CopyMemory HighWord64, ByVal VarPtr(wParam) + 2, 4
    End Function
#End If

Private Function ScreenDPI(ByVal bVert As Boolean) As Long

    Const LOGPIXELSX As Long = 88
    Const LOGPIXELSY As Long = 90
    Static lDPI(1), hDc

    If lDPI(0) = 0 Then
        hDc = GetDC(0)
        lDPI(0) = GetDeviceCaps(hDc, LOGPIXELSX)
        lDPI(1) = GetDeviceCaps(hDc, LOGPIXELSY)
        hDc = ReleaseDC(0, hDc)
    End If
    ScreenDPI = lDPI(Abs(bVert))
End Function

Private Function PTtoPX(ByVal Points As Single, ByVal bVert As Boolean) As Long
    Const POINTSPERINCH As Long = 72
    PTtoPX = Points * ScreenDPI(bVert) / POINTSPERINCH
End Function


Private Function Build_lParam( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ContextCode As Boolean, _
    ByVal ExtendedKey As Boolean, _
    ByVal PreviousKeyState As Boolean, _
    ByVal TransitionState As Boolean) As Long
   
    Dim lParamBits As Long
   
    lParamBits = RepeatCount Or (ScanCode) Or 2 ^ 16
    If ExtendedKey Then lParamBits = lParamBits Or 2 ^ 24
    If ContextCode Then lParamBits = lParamBits Or 2 ^ 29
    If PreviousKeyState Then lParamBits = lParamBits Or 2 ^ 30
    If TransitionState Then lParamBits = lParamBits Or -2 ^ 31
   
    Build_lParam = lParamBits

End Function

 
Private Function Build_lParam_WM_KEYDOWN( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ExtendedKey As Boolean, _
    ByVal ContextCode As Boolean, _
    ByVal PreviousKeyState As Boolean) _
    As Long

    Build_lParam_WM_KEYDOWN = Build_lParam(RepeatCount, ScanCode, ExtendedKey, ContextCode, PreviousKeyState, False)

End Function
 
 
Private Function Build_lParam_WM_KEYUP( _
    ByVal RepeatCount As Integer, _
    ByVal ScanCode As Byte, _
    ByVal ExtendedKey As Boolean, _
    ByVal ContextCode As Boolean) _
    As Long

      Build_lParam_WM_KEYUP = Build_lParam(RepeatCount, ScanCode, ExtendedKey, ContextCode, True, True)

End Function
@Jaafar Tribak

Does this updated code move the selected item (if multiselect = false) to ensure it is always visible when one scrolls with the mousewheel? (Your combobox code does this).
I've been trying to code this in the mousemove sub (with your previous code), with limited success (trying to force the selected item to always be visible in relation to TopIndex, but my Click sub interferes). Should this "ensurevisible" code be in the MouseMove sub, or would it be better in your code?
 
Upvote 0

Forum statistics

Threads
1,223,164
Messages
6,170,444
Members
452,326
Latest member
johnshaji

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