Jump to content

Synchronize Scrolling Multiple ListViews?


Recommended Posts

Hi all,

I've been searching throughout the forums and I can't really find a solution to my problem. I have a child window that has two listviews and I need their scrolling to be synchronized. Using a reference to a post by Mat, I slapped together a quick replicator that will show the issue. If the scrollbars are dragged too quickly, they become out of sync.

Is there a way I can properly synchronize these scrollbars no matter the speed of the scrolling?

#include <GUIConstantsEx.au3>
#include <SendMessage.au3>
#include <WindowsConstants.au3>
#include <StructureConstants.au3>
#include <ListViewConstants.au3>

Global $hGUI, $hList1, $hList2

$hGUI = GUICreate("Demo", 400, 400)
$Button = GUICtrlCreateButton("Click Me", 150, 188, 100, 24)

GUISetState(@SW_SHOW, $hGUI)
GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $Button
            _GUI_ListViews()
    EndSwitch
WEnd

Func _GUI_ListViews()
    $hGUILV = GUICreate("Testing scroll", 500, 600, -1, -1, -1, -1, $hGUI)
    $hList1 = GUICtrlCreateListView("List view 1", 2, 2, 248, 596)
    $hList2 = GUICtrlCreateListView("List view 2", 253, 2, 249, 596)

    GUISetState(@SW_SHOW, $hGUILV)

    For $i = 1 To 500
        GUICtrlCreateListViewItem($i, $hList1)
        GUICtrlCreateListViewItem($i, $hList2)
    Next

    While 1
        Sleep(10)
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
    WEnd
    
    GUIDelete($hGUILV)
EndFunc

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
    If DllStructGetData($tNMHDR, "code") <> $LVN_ENDSCROLL Then Return
    Local $hListFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    Local $tNMLVSCROLL = DllStructCreate($tagNMLVSCROLL, $lParam)

    If $hListFrom = GUICtrlGetHandle($hList1) Then
        GUICtrlSendMsg($hList2, $LVM_SCROLL, DllStructGetData($tNMLVSCROLL, "dx"), DllStructGetData($tNMLVSCROLL, "dy") * 16)
    ElseIf $hListFrom = GUICtrlGetHandle($hList2) Then
        GUICtrlSendMsg($hList1, $LVM_SCROLL, DllStructGetData($tNMLVSCROLL, "dx"), DllStructGetData($tNMLVSCROLL, "dy") * 16)
    EndIf
EndFunc   ;==>WM_NOTIFY

 

Edited by buymeapc
Link to comment
Share on other sites

Something like this here?

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiTreeView.au3>
#include <GuiListView.au3>
#include <Misc.au3>
#include <GuiScrollBars.au3>

Global Const $iMax = 40

$hGUI = GUICreate("Synchro Pair", 195, 240, -1, -1)
$cLV_2 = GUICtrlCreateListView("LV_2", 60, 5, 85, 220)
$hLV_2 = GUICtrlGetHandle($cLV_2)
GUISetState(@SW_SHOW)

$hGUI_Child = GUICreate("", 50, 220, 10, 5, $WS_POPUP, $WS_EX_MDICHILD, $hGUI)
$cLV_1 = GUICtrlCreateListView("LV_1", 0, 0, 85, 220)
$hLV_1 = GUICtrlGetHandle($cLV_1)
GUISetState()

For $i = 1 To $iMax
    GUICtrlCreateListViewItem("Item " & $i, $cLV_1)
    GUICtrlCreateListViewItem("Item " & $i, $cLV_2)
Next

Global $aRect = _GUICtrlListView_GetItemRect($hLV_2, 0)
Global Const $iDeltaY = $aRect[3] - $aRect[1]

GUIRegisterMsg($WM_NOTIFY, "_WM_Notify")

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            Exit
    EndSwitch
WEnd

Func _WM_Notify($hWnd, $Msg, $wParam, $lParam)
    Local Const $iLines = _SendMessage($hLV_2, $LVM_GETTOPINDEX) - _SendMessage($hLV_1, $LVM_GETTOPINDEX)
    _SendMessage($hLV_1, $LVM_SCROLL, 0, $iLines * $iDeltaY)
    Return "GUI_RUNDEFMSG"
EndFunc   ;==>_WM_Notify

 

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

UEZ,
Your code works nice on 2 listviews in the main gui , but I can't get it to work if the listviews are in a dedicated child gui (like the one in the 1st post) 

Edit
Sorry, my bad, it was a stupid saturday typo :>

#include <GUIConstantsEx.au3>
#include <SendMessage.au3>
#include <GuiListView.au3>
#include <WindowsConstants.au3>
#include <StructureConstants.au3>

Global $hGUI, $hList1, $hList2, $hLV_1, $hLV_2

$hGUI = GUICreate("Demo", 400, 400)
$Button = GUICtrlCreateButton("Click Me", 150, 188, 100, 24)
GUISetState(@SW_SHOW, $hGUI)

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $Button
            _GUI_ListViews()
    EndSwitch
WEnd

Func _GUI_ListViews()
    $hGUILV = GUICreate("Testing scroll", 500, 600, -1, -1)
    $cLV_2 = GUICtrlCreateListView("List view 2", 253, 2, 248, 596)
    $hLV_2 = GUICtrlGetHandle($cLV_2)
    GUISetState(@SW_SHOW)
    
    $hGUI_Child = GUICreate("", 249, 600, 0, 0, $WS_CHILD, -1, $hGUILV)
    $cLV_1 = GUICtrlCreateListView("List view 1", 2, 2, 246, 596)
    $hLV_1 = GUICtrlGetHandle($cLV_1)
    GUISetState()
    
    For $i = 1 To 500
        GUICtrlCreateListViewItem("Item " & $i, $cLV_1)
        GUICtrlCreateListViewItem("Item " & $i, $cLV_2)
    Next
    
    Global $aRect = _GUICtrlListView_GetItemRect($hLV_2, 0)
    Global Const $iDeltaY = $aRect[3] - $aRect[1]
    GUIRegisterMsg($WM_NOTIFY, "_WM_Notify")
    
    While 1
        Sleep(10)
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
    WEnd
    
    GUIDelete($hGUILV)
EndFunc


Func _WM_Notify($hWnd, $Msg, $wParam, $lParam)
    Local Const $iLines = _SendMessage($hLV_2, $LVM_GETTOPINDEX) - _SendMessage($hLV_1, $LVM_GETTOPINDEX)
    _SendMessage($hLV_1, $LVM_SCROLL, 0, $iLines * $iDeltaY)
    Return "GUI_RUNDEFMSG"
EndFunc   ;==>_WM_Notify

 

Edited by mikell
Link to comment
Share on other sites

In UEZ's Script the childGUI is created in the mainGUI. It also works in separetet GUI's:

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiTreeView.au3>
#include <GuiListView.au3>
#include <Misc.au3>
#include <GuiScrollBars.au3>

Global Const $iMax = 40

$hGUI = GUICreate("Synchro Pair", 195, 240, -1, -1)
$cLV_2 = GUICtrlCreateListView("LV_2", 60, 5, 85, 220)
$hLV_2 = GUICtrlGetHandle($cLV_2)
GUISetState(@SW_SHOW)

$hGUI_Child = GUICreate("Child", 50, 220, 10, 5)
$cLV_1 = GUICtrlCreateListView("LV_1", 0, 0, 85, 220)
$hLV_1 = GUICtrlGetHandle($cLV_1)
GUISetState()

For $i = 1 To $iMax
    GUICtrlCreateListViewItem("Item " & $i, $cLV_1)
    GUICtrlCreateListViewItem("Item " & $i, $cLV_2)
Next

Global $aRect = _GUICtrlListView_GetItemRect($hLV_2, 0)
Global Const $iDeltaY = $aRect[3] - $aRect[1]

GUIRegisterMsg($WM_NOTIFY, "_WM_Notify")

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            Exit
    EndSwitch
WEnd

Func _WM_Notify($hWnd, $Msg, $wParam, $lParam)
    Local Const $iLines = _SendMessage($hLV_2, $LVM_GETTOPINDEX) - _SendMessage($hLV_1, $LVM_GETTOPINDEX)
    _SendMessage($hLV_1, $LVM_SCROLL, 0, $iLines * $iDeltaY)
    Return "GUI_RUNDEFMSG"
EndFunc   ;==>_WM_Notify

 

Edited by AutoBert
Link to comment
Share on other sites

UEZ,
Your code works nice on 2 listviews in the main gui , but I can't get it to work if the listviews are in a dedicated child gui (like the one in the 1st post) 

Edit
Sorry, my bad, it was a stupid saturday typo :>

#include <GUIConstantsEx.au3>
#include <SendMessage.au3>
#include <GuiListView.au3>
#include <WindowsConstants.au3>
#include <StructureConstants.au3>

Global $hGUI, $hList1, $hList2, $hLV_1, $hLV_2

$hGUI = GUICreate("Demo", 400, 400)
$Button = GUICtrlCreateButton("Click Me", 150, 188, 100, 24)
GUISetState(@SW_SHOW, $hGUI)

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $Button
            _GUI_ListViews()
    EndSwitch
WEnd

Func _GUI_ListViews()
    $hGUILV = GUICreate("Testing scroll", 500, 600, -1, -1)
    $cLV_2 = GUICtrlCreateListView("List view 2", 253, 2, 248, 596)
    $hLV_2 = GUICtrlGetHandle($cLV_2)
    GUISetState(@SW_SHOW)
    
    $hGUI_Child = GUICreate("", 249, 600, 0, 0, $WS_CHILD, -1, $hGUILV)
    $cLV_1 = GUICtrlCreateListView("List view 1", 2, 2, 246, 596)
    $hLV_1 = GUICtrlGetHandle($cLV_1)
    GUISetState()
    
    For $i = 1 To 500
        GUICtrlCreateListViewItem("Item " & $i, $cLV_1)
        GUICtrlCreateListViewItem("Item " & $i, $cLV_2)
    Next
    
    Global $aRect = _GUICtrlListView_GetItemRect($hLV_2, 0)
    Global Const $iDeltaY = $aRect[3] - $aRect[1]
    GUIRegisterMsg($WM_NOTIFY, "_WM_Notify")
    
    While 1
        Sleep(10)
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
    WEnd
    
    GUIDelete($hGUILV)
EndFunc


Func _WM_Notify($hWnd, $Msg, $wParam, $lParam)
    Local Const $iLines = _SendMessage($hLV_2, $LVM_GETTOPINDEX) - _SendMessage($hLV_1, $LVM_GETTOPINDEX)
    _SendMessage($hLV_1, $LVM_SCROLL, 0, $iLines * $iDeltaY)
    Return "GUI_RUNDEFMSG"
EndFunc   ;==>_WM_Notify

 

Thank you all for the examples. Mikell's was the closest to what I'm looking for, but unfortunately, it can't be opened more than once without crashing due to the constant being re-declared. Is there a way around this? Otherwise, it's perfect, I think.

Ok, I just made the constant a global variable and it's all good. Thank you all for the help!!

Edited by buymeapc
Link to comment
Share on other sites

  • 1 year later...
On 6/19/2015 at 5:31 PM, UEZ said:

Something like this here?

ok, now I, need the bottom scrollbar to sync up.
I've modified your example to exemplify my view

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiTreeView.au3>
#include <GuiListView.au3>
#include <Misc.au3>
#include <GuiScrollBars.au3>
#include <WinAPI.au3>

Global Const $iMax = 40
Local $s, $n

$hGUI = GUICreate("Synchro Pair - 2.0 ???", 455, 240, -1, -1, BitOR($GUI_SS_DEFAULT_GUI, $WS_MAXIMIZEBOX, $WS_SIZEBOX, $WS_THICKFRAME, $WS_TABSTOP), BitOR($WS_EX_ACCEPTFILES, $WS_EX_WINDOWEDGE))
$cLV_2 = GUICtrlCreateListView("LV_2 - A|B|C|D|E|F|G|H|I|J|K|L", 160, 25, 285, 160)
$hLV_2 = GUICtrlGetHandle($cLV_2)
GUISetState(@SW_SHOW)

;~ $hGUI_Child = GUICreate("", 150, 220, 10, 5, $WS_POPUP, $WS_EX_MDICHILD, $hGUI)
$cLV_1 = GUICtrlCreateListView("LV_1 - A|B|C|D|E|F|G|H|I|J|K|L", 5, 5, 150, 220)
$hLV_1 = GUICtrlGetHandle($cLV_1)
GUISetState()

;ConsoleWrite('--- $cLV_1 = ' & $cLV_1 & ' / $hLV_1 = ' & $hLV_1 & @CRLF)
;ConsoleWrite('--- $cLV_2 = ' & $cLV_2 & ' / $hLV_2 = ' & $hLV_2 & @CRLF)

Local $s, $n
For $i = 1 To $iMax
    $s = ""
    For $n = 0 To 10
        $s &= "Item " & $i & " " & Chr(Asc("A") + $n) & "|"
    Next
    $s &= "Item " & $i & " " & Chr(Asc("A") + $n)
;~  ConsoleWrite($s&@CRLF)
    GUICtrlCreateListViewItem($s, $cLV_1)
    GUICtrlCreateListViewItem($s, $cLV_2)
Next

Global $aRect = _GUICtrlListView_GetItemRect($hLV_2, 0)
Global Const $iDeltaY = $aRect[3] - $aRect[1]

GUIRegisterMsg($WM_NOTIFY, "_WM_Notify")

#Region SIZEBOX
Global Const $SBS_SIZEBOX = 0x08
Global $hSizebox = _WinAPI_CreateWindowEx(0, "Scrollbar", "", $WS_CHILD + $WS_VISIBLE + $SBS_SIZEBOX, 300 - 20, 200 - 20, 20, 20, $hGUI)
GUIRegisterMsg($WM_SIZE, "WM_SIZE")
_Sizebox__Resize($hSizebox)
_WinAPI_RedrawWindow($hGUI) ; to show the $SBS_SIZEBOX
Func _Sizebox__Resize($hCtrl)
    Local $aSize = WinGetClientSize($hGUI)
    If UBound($aSize) = 2 Then WinMove($hCtrl, "", $aSize[0] - 20, $aSize[1] - 20)
EndFunc   ;==>_Sizebox__Resize
; Resize the status bar when GUI size changes
Func WM_SIZE($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam
    _Sizebox__Resize($hSizebox)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_SIZE
#EndRegion SIZEBOX


While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            GUIDelete($hGUI)
            ExitLoop
    EndSwitch
WEnd

Func _WM_Notify($hWnd, $Msg, $wParam, $lParam)
    ;ConsoleWrite('+ Func _WM_Notify("' & $hWnd & '", "' & $Msg & '", "' & $wParam & '", "' & $lParam & '")' & @CRLF)

    If $cLV_2 = $wParam Then ; to make both scrollers work
        Local $iLines = _SendMessage($hLV_2, $LVM_GETTOPINDEX) - _SendMessage($hLV_1, $LVM_GETTOPINDEX)
        _SendMessage($hLV_1, $LVM_SCROLL, 0, $iLines * $iDeltaY)
    EndIf

    If $cLV_1 = $wParam Then
        Local $iLines = _SendMessage($hLV_1, $LVM_GETTOPINDEX) - _SendMessage($hLV_2, $LVM_GETTOPINDEX)
        _SendMessage($hLV_2, $LVM_SCROLL, 0, $iLines * $iDeltaY)
    EndIf

    Return "GUI_RUNDEFMSG"
EndFunc   ;==>_WM_Notify

 can you @UEZ add the bottom too ? ( actually anyone who'd know how :) )

Edited by argumentum
prettify the example code

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Link to comment
Share on other sites

Here is my try ...

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiListView.au3>
#include <GuiScrollBars.au3>

$hGUI = GUICreate("Synchro Pair - 2.0 ???", 455, 240, -1, -1)
$cLV_1 = GUICtrlCreateListView("LV_1 A|B|C|D|E|F|G|H|I|J|K|L", 5, 5, 190, 220)
$hLV_1 = GUICtrlGetHandle($cLV_1)
$cLV_2 = GUICtrlCreateListView("LV_2 A|B|C|D|E|F|G|H|I|J|K|L", 240, 5, 190, 220)
$hLV_2 = GUICtrlGetHandle($cLV_2)
GUISetState()

For $i = 1 To 40
    $s = ""
    For $n = 0 To 10
        $s &= "Item " & $i & " " & Chr(Asc("A") + $n) & "|"
    Next
    GUICtrlCreateListViewItem($s, $cLV_1)
    GUICtrlCreateListViewItem($s, $cLV_2)
Next

Global $aRect = _GUICtrlListView_GetItemRect($hLV_2, 0)
Global Const $iDeltaY = $aRect[3] - $aRect[1]
GUIRegisterMsg($WM_NOTIFY, "_WM_Notify")


While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            GUIDelete($hGUI)
            ExitLoop
    EndSwitch
WEnd


Func _WM_Notify($hWnd, $Msg, $wParam, $lParam)

    If $cLV_2 = $wParam Then ; to make both scrollers work
        $x1 = _GUIScrollBars_GetScrollPos($hLV_1, $SB_HORZ)
        $x2 = _GUIScrollBars_GetScrollPos($hLV_2, $SB_HORZ)
        Local $iLines = _GUICtrlListView_GetTopIndex($hLV_2) - _GUICtrlListView_GetTopIndex($hLV_1)
      _GUICtrlListView_Scroll ($hLV_1, $x2-$x1, $iLines * $iDeltaY)
   EndIf

    If $cLV_1 = $wParam Then
        $x1 = _GUIScrollBars_GetScrollPos($hLV_1, $SB_HORZ)
        $x2 = _GUIScrollBars_GetScrollPos($hLV_2, $SB_HORZ)
        Local $iLines = _GUICtrlListView_GetTopIndex($hLV_1) - _GUICtrlListView_GetTopIndex($hLV_2)
        _GUICtrlListView_Scroll ($hLV_2, $x1-$x2, $iLines * $iDeltaY)
    EndIf

    Return "GUI_RUNDEFMSG"
EndFunc   ;==>_WM_Notify

 

Link to comment
Share on other sites

9 hours ago, mikell said:

Here is my try ...

Well, your try, is perfect !. Very many thanks :) 

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiListView.au3>
#include <GuiScrollBars.au3>

Global $hGUI = GUICreate("Synchro Pair - 2.0      ;)", 455, 240, -1, -1, BitOR($GUI_SS_DEFAULT_GUI, $WS_MAXIMIZEBOX, $WS_SIZEBOX, $WS_THICKFRAME, $WS_TABSTOP), BitOR($WS_EX_ACCEPTFILES, $WS_EX_WINDOWEDGE))
Global $cLV_2 = GUICtrlCreateListView("LV_2 - A|B|C|D|E|F|G|H|I|J|K|L", 160, 45, 285, 160)
Global $hLV_2 = GUICtrlGetHandle($cLV_2)
Global $cLV_1 = GUICtrlCreateListView("LV_1 - A|B|C|D|E|F|G|H|I|J|K|L", 5, 5, 150, 220)
Global $hLV_1 = GUICtrlGetHandle($cLV_1)
GUISetState(@SW_SHOW)

PopulateListViewDemo()
Func PopulateListViewDemo()
    Local $s, $n, $i
    For $i = 1 To 40
        $s = ""
        For $n = 0 To 10
            $s &= "Item " & $i & " " & Chr(Asc("A") + $n) & "|"
        Next
        $s &= "Item " & $i & " " & Chr(Asc("A") + $n)
        GUICtrlCreateListViewItem($s, $cLV_1)
        GUICtrlCreateListViewItem($s, $cLV_2)
    Next
EndFunc   ;==>fillDemo

GUIRegisterMsg($WM_NOTIFY, "_WM_Notify")

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            GUIDelete($hGUI)
            ExitLoop
    EndSwitch
WEnd

Func _WM_Notify($hWnd, $Msg, $wParam, $lParam)
    Switch $wParam
        Case $cLV_2
            _GUICtrlListView_SyncScroll($hLV_2, $hLV_1)
        Case $cLV_1
            _GUICtrlListView_SyncScroll($hLV_1, $hLV_2)
    EndSwitch
    Return "GUI_RUNDEFMSG"
EndFunc   ;==>_WM_Notify

Func _GUICtrlListView_SyncScroll($hWndControler, $hWndControled)
    ;Local Static $aRect = _GUICtrlListView_GetItemRect($hWndControler, 0) ; the result is usually 14
    Local Static $iDeltaY = 14 ; $aRect[3] - $aRect[1]                     ; so, set as 14 or remove the "Static"
    Local $x1 = _GUIScrollBars_GetScrollPos($hWndControled, $SB_HORZ)      ; otherwise the Static could set to 0 
    Local $x2 = _GUIScrollBars_GetScrollPos($hWndControler, $SB_HORZ)      ; if no entry in the listview when first called !
    Local $iLines = _GUICtrlListView_GetTopIndex($hWndControler) - _GUICtrlListView_GetTopIndex($hWndControled)
    _GUICtrlListView_Scroll($hWndControled, $x2 - $x1, $iLines * $iDeltaY)
EndFunc   ;==>_GUICtrlListView_SyncScroll

 

Edited by argumentum
show how I will use your code

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

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...