Jump to content

Synchronizing Vertical Scrolling Between Two ListBoxs?


Go to solution Solved by pixelsearch,

Recommended Posts

Hi Autoit Forum :)

I'm working on a project for managing designs where I have two listboxes, and I need them to always scroll together. Basically, when one listbox is scrolled vertically, the other should match it and scroll by the same amount. I've been digging around but can't seem to figure out how to sync the vertical scrolling between the two.. Has anyone done this before or have any tips on how I can achieve this? Any help or examples would be greatly appreciated

Link to comment
Share on other sites

Never tried it, but I suppose it could be possible thru WM_VSCROLL. When you receive this message, you could send the same message to the other listbox.

If you need further help, provide a runable script with those 2 listboxes so we do not have to create them on our own.

Link to comment
Share on other sites

I succeeded, but in 2 separate scripts.
1) One script is simple, testing...

_GUICtrlListBox_GetTopIndex()

...from the 1st listbox in main loop, then setting the same index in the 2nd list box
This first script is 100% accurate... only when using the mouse wheel or keyboard (up, down, pgup, pgdn, home, end)

2) 2nd script is a bit more complicated and gives 100% accuracy when using the scrollbar, its thumbtrack etc...
It requires to subclass the listbox (see what @LarsJ wrote here and let's apply it to LB instead of LV), then inside the subclassing proc we can intercept $WM_VSCROLL, but I also needed to test things like this :

Local $iScrollRequest = BitAND($wParam, 0xFFFF) ; LoWord
Local $iScrollPos = BitShift($wParam, 16) ; HiWord
If $iScrollRequest = $SB_THUMBTRACK Or $iScrollRequest = $SB_THUMBPOSITION Then
    ....

That's strange, though I can intercept $WM_MOUSEWHEEL too inside the subclassing function, results are not as good as the 1st script. Anyway, it's nearly finished. I'll try to combine both scripts, though I'm pretty sure someone else will bring a working solution before I'm done :D

Edit: combine done a few hours ago, final script is functional.

Waiting for OP's answer to @Nine before posting.

Edited by pixelsearch
typo
Link to comment
Share on other sites

  • 2 weeks later...
  • Solution

Ok, so here is my solution to scroll both listbox in parallel, when they got the same number of items :

#include <GUIConstantsEx.au3>
#include <GuiListBox.au3>
#include <ScrollBarConstants.au3>
#include <WinAPIShellEx.au3>
#include <WindowsConstants.au3>

OnAutoItExitRegister('OnAutoItExit')

Opt("MustDeclareVars", 1) ;0=no, 1=require pre-declaration

Global $g_hListBox[2], $g_hDll[2], $g_pDll[2], $g_idSubClass[2] = [9999, 9998], $g_iTopIndexOld = -1

Example()

Func Example()
    Local $iLeft[2] = [50, 300], $sFormat[2] = ["%02d", "A - %02d"], $sFunction[2] = ['_SubclassProc_0', '_SubclassProc_1']

    GUICreate("Sync scrolling V2 (mouse wheel / scrollbar / keyboard)", 550, 400)

    For $i = 0 To 1
        $g_hListBox[$i] = GUICtrlGetHandle(GUICtrlCreateList("", $iLeft[$i], 50, 200, 300))

        _GUICtrlListBox_BeginUpdate($g_hListBox[$i])
        For $iRow = 0 To 99
            _GUICtrlListBox_AddString($g_hListBox[$i], StringFormat($sFormat[$i], $iRow))
        Next
        _GUICtrlListBox_EndUpdate($g_hListBox[$i])

        ; Create a listbox callback function (to catch scrollbar messages, as listbox scrollbars are child windows of listbox controls)
        $g_hDll[$i] = DllCallbackRegister($sFunction[$i], 'lresult', 'hwnd;uint;wparam;lparam;uint_ptr;dword_ptr')
        $g_pDll[$i] = DllCallbackGetPtr($g_hDll[$i])

        ; Install the listbox subclass callback (to handle messages related to listbox scrollbars)
        _WinAPI_SetWindowSubclass($g_hListBox[$i], $g_pDll[$i], $g_idSubClass[$i], 0)
    Next

    GUISetState()

    ; main loop takes care of mouse wheel & keyboard (up, down, pgup, pgdn, home, end)
    Local $iTopIndex = 0
    Do
        For $i = 0 To 1
            $iTopIndex = _GUICtrlListBox_GetTopIndex($g_hListBox[$i])
            _CompareIndex($iTopIndex, $i)
        Next
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

EndFunc   ;==>Example

;==============================================
Func _CompareIndex($iTopIndex, $iListBox)

    If $iTopIndex <> $g_iTopIndexOld Then
        _GUICtrlListBox_SetTopIndex($g_hListBox[Not $iListBox], $iTopIndex) ; [Not 0] => [1] , [Not 1] => [0]
        $g_iTopIndexOld = $iTopIndex
    EndIf

EndFunc   ;==>_CompareIndex

;==============================================
Func _SubclassProc_0($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    #forceref $iID, $pData

    Switch $iMsg
        Case $WM_VSCROLL
            Local $iScrollRequest = BitAND($wParam, 0xFFFF) ; LoWord
            Local $iScrollPos = BitShift($wParam, 16) ; HiWord
            Local $iTopIndex = ($iScrollRequest = $SB_THUMBTRACK Or $iScrollRequest = $SB_THUMBPOSITION) _
                ? $iScrollPos _
                : _GUICtrlListBox_GetTopIndex($g_hListBox[0])
            _CompareIndex($iTopIndex, 0)
    EndSwitch

    ; Call next function in subclass chain
    Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)

EndFunc   ;==>_SubclassProc_0

;==============================================
Func _SubclassProc_1($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    #forceref $iID, $pData

    Switch $iMsg
        Case $WM_VSCROLL
            Local $iScrollRequest = BitAND($wParam, 0xFFFF) ; LoWord
            Local $iScrollPos = BitShift($wParam, 16) ; HiWord
            Local $iTopIndex = ($iScrollRequest = $SB_THUMBTRACK Or $iScrollRequest = $SB_THUMBPOSITION) _
                ? $iScrollPos _
                : _GUICtrlListBox_GetTopIndex($g_hListBox[1])
            _CompareIndex($iTopIndex, 1)
    EndSwitch

    ; Call next function in subclass chain
    Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)

EndFunc   ;==>_SubclassProc_1

;==============================================
Func OnAutoItExit()

    For $i = 1 To 0 Step - 1
        ; Remove the listbox subclass callback
        _WinAPI_RemoveWindowSubclass($g_hListBox[$i], $g_pDll[$i], $g_idSubClass[$i])

        ; Frees a previously created handle created with DllCallbackRegister
        DllCallbackFree($g_hDll[$i])
    Next

EndFunc   ;==>OnAutoItExit

In case someone is interested, I scripted 2 others :
1) Synchronize selections  too (when both listbox got the same number of items)
2) Take care of listboxes that don't have the same number of items.

Link to comment
Share on other sites

4 hours ago, pixelsearch said:

Ok, so here is my solution to scroll both listbox in parallel, when they got the same number of items :

#include <GUIConstantsEx.au3>
#include <GuiListBox.au3>
#include <ScrollBarConstants.au3>
#include <WinAPIShellEx.au3>
#include <WindowsConstants.au3>

OnAutoItExitRegister('OnAutoItExit')

Opt("MustDeclareVars", 1) ;0=no, 1=require pre-declaration

Global $g_hListBox[2], $g_hDll[2], $g_pDll[2], $g_idSubClass[2] = [9999, 9998], $g_iTopIndexOld = -1

Example()

Func Example()
    Local $iLeft[2] = [50, 300], $sFormat[2] = ["%02d", "A - %02d"], $sFunction[2] = ['_SubclassProc_0', '_SubclassProc_1']

    GUICreate("Sync scrolling V2 (mouse wheel / scrollbar / keyboard)", 550, 400)

    For $i = 0 To 1
        $g_hListBox[$i] = GUICtrlGetHandle(GUICtrlCreateList("", $iLeft[$i], 50, 200, 300))

        _GUICtrlListBox_BeginUpdate($g_hListBox[$i])
        For $iRow = 0 To 99
            _GUICtrlListBox_AddString($g_hListBox[$i], StringFormat($sFormat[$i], $iRow))
        Next
        _GUICtrlListBox_EndUpdate($g_hListBox[$i])

        ; Create a listbox callback function (to catch scrollbar messages, as listbox scrollbars are child windows of listbox controls)
        $g_hDll[$i] = DllCallbackRegister($sFunction[$i], 'lresult', 'hwnd;uint;wparam;lparam;uint_ptr;dword_ptr')
        $g_pDll[$i] = DllCallbackGetPtr($g_hDll[$i])

        ; Install the listbox subclass callback (to handle messages related to listbox scrollbars)
        _WinAPI_SetWindowSubclass($g_hListBox[$i], $g_pDll[$i], $g_idSubClass[$i], 0)
    Next

    GUISetState()

    ; main loop takes care of mouse wheel & keyboard (up, down, pgup, pgdn, home, end)
    Local $iTopIndex = 0
    Do
        For $i = 0 To 1
            $iTopIndex = _GUICtrlListBox_GetTopIndex($g_hListBox[$i])
            _CompareIndex($iTopIndex, $i)
        Next
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

EndFunc   ;==>Example

;==============================================
Func _CompareIndex($iTopIndex, $iListBox)

    If $iTopIndex <> $g_iTopIndexOld Then
        _GUICtrlListBox_SetTopIndex($g_hListBox[Not $iListBox], $iTopIndex) ; [Not 0] => [1] , [Not 1] => [0]
        $g_iTopIndexOld = $iTopIndex
    EndIf

EndFunc   ;==>_CompareIndex

;==============================================
Func _SubclassProc_0($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    #forceref $iID, $pData

    Switch $iMsg
        Case $WM_VSCROLL
            Local $iScrollRequest = BitAND($wParam, 0xFFFF) ; LoWord
            Local $iScrollPos = BitShift($wParam, 16) ; HiWord
            Local $iTopIndex = ($iScrollRequest = $SB_THUMBTRACK Or $iScrollRequest = $SB_THUMBPOSITION) _
                ? $iScrollPos _
                : _GUICtrlListBox_GetTopIndex($g_hListBox[0])
            _CompareIndex($iTopIndex, 0)
    EndSwitch

    ; Call next function in subclass chain
    Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)

EndFunc   ;==>_SubclassProc_0

;==============================================
Func _SubclassProc_1($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    #forceref $iID, $pData

    Switch $iMsg
        Case $WM_VSCROLL
            Local $iScrollRequest = BitAND($wParam, 0xFFFF) ; LoWord
            Local $iScrollPos = BitShift($wParam, 16) ; HiWord
            Local $iTopIndex = ($iScrollRequest = $SB_THUMBTRACK Or $iScrollRequest = $SB_THUMBPOSITION) _
                ? $iScrollPos _
                : _GUICtrlListBox_GetTopIndex($g_hListBox[1])
            _CompareIndex($iTopIndex, 1)
    EndSwitch

    ; Call next function in subclass chain
    Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)

EndFunc   ;==>_SubclassProc_1

;==============================================
Func OnAutoItExit()

    For $i = 1 To 0 Step - 1
        ; Remove the listbox subclass callback
        _WinAPI_RemoveWindowSubclass($g_hListBox[$i], $g_pDll[$i], $g_idSubClass[$i])

        ; Frees a previously created handle created with DllCallbackRegister
        DllCallbackFree($g_hDll[$i])
    Next

EndFunc   ;==>OnAutoItExit

In case someone is interested, I scripted 2 others :
1) Synchronize selections  too (when both listbox got the same number of items)
2) Take care of listboxes that don't have the same number of items.

Thanks a ton!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...