0

I need to scroll two or more list view at once using a single scrollBar. Initially, i used Column inside a Flickable but scroll was not happening as expected. Later, I used ListView and even that was not scrolling correctly.

So how to scroll a listview/layout content item with a scroll bar? Should I use ScrollView or Flickable or something else?

Example of My UI

pra7
  • 834
  • 2
  • 21
  • 50
  • 1
    How was `Column` + `Flickable` not scrolling as expected ? – GrecKo Aug 29 '17 at 14:16
  • Consider there are three columns where col1 has 10 items, col2 has 15 items and col3 has 20 items. Now I should find a column which has the most number of items and should set `Flickable contentHeight` to the same then it works !!! .In my case, I should do that every time as all the items is inserted dynamically into the columns. – pra7 Aug 29 '17 at 14:53

2 Answers2

3

The stock scrollbar will only hook to a single scrollable item. However, it is trivial to make a custom scroller and hook multiple views to it:

  Row {
    Flickable {
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      contentY: (contentHeight - height) * scroller.position
      Column {
        spacing: 5
        Repeater {
          model: 20
          delegate: Rectangle {
            width: 50
            height: 50
            color: "red"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    Flickable {
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      contentY: (contentHeight - height) * scroller.position
      Column {
        spacing: 5
        Repeater {
          model: 30
          delegate: Rectangle {
            width: 50
            height: 50
            color: "cyan"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    Rectangle {
      id: scroller
      width: 50
      height: 50
      color: "grey"
      property real position: y / (main.height - 50)
      MouseArea {
        anchors.fill: parent
        drag.target: parent
        drag.minimumY: 0
        drag.maximumY: main.height - 50
        drag.axis: Drag.YAxis
      }
    }
  }

Note that it will work adequately even if the the views are of different content height, scrolling each view relative to the scroller position:

enter image description here

Realizing the question was not put that well, just in case someone wants to actually scroll multiple views at the same time comes around, I will nonetheless share another interesting approach similar to a jog wheel, something that can go indefinitely in every direction rather than having a limited range like a scrollbar. This solution will scroll the two views in sync until they hit the extent of their ranges. Unlike GrecKo's answer, this never leaves you with an "empty view" when the view size is different:

enter image description here

  Row {
    Flickable {
      id: f1
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      Connections {
        target: jogger
        onScroll: f1.contentY = Math.max(0, Math.min(f1.contentHeight - f1.height, f1.contentY + p))
      }
      Column {
        spacing: 5
        Repeater {
          model: 20
          delegate: Rectangle {
            width: 50
            height: 50
            color: "red"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    Flickable {
      id: f2
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      Connections {
        target: jogger
        onScroll: f2.contentY = Math.max(0, Math.min(f2.contentHeight - f2.height, f2.contentY + p))
      }
      Column {
        spacing: 5
        Repeater {
          model: 30
          delegate: Rectangle {
            width: 50
            height: 50
            color: "cyan"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    MouseArea {
      id: jogger
      width: 50
      height: main.height
      drag.target: knob
      drag.minimumY: 0
      drag.maximumY: main.height - 50
      drag.axis: Drag.YAxis
      signal scroll(real p)
      property real dy: 0
      onPressed: dy = mouseY
      onPositionChanged: {
        scroll(dy - mouseY)
        dy = mouseY
      }
      onScroll: console.log(p)
      Rectangle {
        anchors.fill: parent
        color: "lightgrey"
      }
      Rectangle {
        id: knob
        visible: parent.pressed
        width: 50
        height: 50
        color: "grey"
        y: Math.max(0, Math.min(parent.mouseY - 25, parent.height - height))
      }
    }
  }

Another advantage the "jog" approach has it is it not relative but absolute. That means if your view is huge, if you use a scroller even a single pixel may result in a big shift in content, whereas the jog, working in absolute mode, will always scroll the same amount of pixels regardless the content size, which is handy where precision is required.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • That's disturbing to watch :) – GrecKo Aug 29 '17 at 18:17
  • @GrecKo - because they offset? That's kind of inevitable if the content height is different and yet you want uniform scrolling thorough the entire content of multiple views. – dtech Aug 29 '17 at 18:43
  • Indeed that's inevitable in this situation, but I'm not sure OP wanted uniform scrolling. Anyway I've made another alternative answer with no uniform scrolling. – GrecKo Aug 29 '17 at 18:48
  • Well, that's what makes sense, since a scroll bar is a relative scrolling element. Now if it was something more like a jog wheel, pulling up or down, then views would only go as far as they can, without leaving empty gaps like in your solution. – dtech Aug 29 '17 at 18:52
  • Kinda offtopic, but if you want "absolute" scrolling there's the scrollwheel on a mouse and flicking/dragging on a touchscreen ;) I don't feel that leaving a blank is a problem but it may be in some cases. – GrecKo Aug 29 '17 at 21:00
  • "there's the scrollwheel on a mouse and flicking/dragging on a touchscreen" sure, but that won't control multiple views ;) Sure, you can bind one to the other, but that will either cause bindings to break when you touch the other, or leave it immobilized, which a single controller put on top of that will solve. There can be numerous design requirements aside from the typical scroller. My main project for example has floating toolbars and such on the sides, so it is not even possible to have a scrollbar in the typical location, it is instead drawn upon scrolling on the back of the editor scene. – dtech Aug 29 '17 at 21:07
  • Additionally, you can easily modify the sensitivity this way, and have it settable via another scroller or so. Whereas with flicking you are stuck to 1:1, and with the wheel the behavior is entirely platform specific, and in particular on one of my laptops it is complete garbage under linux, which is why I end up always disabling interactivity and implementing my own custom scheme that provides consistent user experience. – dtech Aug 29 '17 at 21:27
2

You could just use a Flickable with your Columns. I don't know how your Columns are laid out horizontally but if they are inside a Row it's pretty straightforward:

import QtQuick 2.7
import QtQuick.Controls 2.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Multi Column")

    Flickable {
        anchors.fill: parent
        contentWidth: row.implicitWidth
        contentHeight: row.implicitHeight
        Row {
            id: row
            Column {
                spacing: 5
                Repeater {
                    model: 20
                    delegate: Rectangle {
                        width: 50
                        height: 50
                        color: "red"
                        Text {
                            anchors.centerIn: parent
                            text: index
                        }
                    }
                }
            }
            Column {
                spacing: 5
                Repeater {
                    model: 30
                    delegate: Rectangle {
                        width: 50
                        height: 50
                        color: "cyan"
                        Text {
                            anchors.centerIn: parent
                            text: index
                        }
                    }
                }
            }
        }
        ScrollBar.vertical: ScrollBar { }
    }
}

Even if they are not in a Row you could do :
contentHeight: Math.max(column1.height, column2.height, ...)

Demonstration :
Multi columns scroll

GrecKo
  • 6,615
  • 19
  • 23
  • Not sure how this classifies are scrolling *two or more views*. But maybe the OP wasn't properly worded to illustrate the desired result. Maybe all the OP needed was a way to properly size the content height to dynamically changing columns. – dtech Aug 29 '17 at 18:46
  • Given OP's answer to my comment on the question, I'd say that's all OP needs. – GrecKo Aug 29 '17 at 18:51
  • Thanks for the solution .. that's all I need . Don't mind what does "OP" mean ?? – pra7 Aug 29 '17 at 19:03
  • 1
    @pra7 - it means Original Post, or Original Poster, i.e. it refers to either the question or the person who started it, which is implied by the context. – dtech Aug 29 '17 at 19:09