1
votes

I want to create, in QML, a TV-schedule where the vertical axis is a list of Channels and the horizontal axis is time-based. For example something like

enter image description here
(source: zappware.com)

Initially, I created

  • a vertical ListView with
    • model = the list of Channels
    • delegate = a horizontal ListView
  • every horizontal ListView has
    • model = the list of Events
    • delegate = an Item where the width is proportional to the duration of the Event

So far so good. Only drawback is that the horizontal ListViews scroll one by one while they should scroll together.

So somehow, the contentX property of every horizontal ListView should be bound to the contentX property of the moving/flicking horizontal ListView. Note that this binding is dynamic: when flicking in the first row, all other rows should bind to the contentX of the first row. But this should be changed when flicking in the second row.

Any advice on how this can be done?

I tried a somewhat different approach by

  • creating a Flickable Item on top of the vertical ListView (with contentWidth the complete time-window).
  • binding every horizontal ListView to the contentX of this Flickable (this is a static binding)

This resulted in nice synchronous scrolling but I still have some issues

  • I had to do some tricks to ensure that flicking is only horizontal or vertical but not both
  • I'm not able anymore to click on individual Events; I guess events are intercepted by the Flickable
  • I'm also not sure about the memory impact of such a Flickable with a huge contentWidth?

Feedback appreciated!

1

1 Answers

3
votes

I'd say have only one vertical list view for the channels. But the channel names only, not the actual programs. Instead of a horizontal view for the programs, you can cram them all together in a single flickable, using the begin time and duration to layout the programs in the flickable by binding their x and width properties to the former.

Then you can bind the channel list view together with the vertical scrolling of the program items, so that you have the programs corresponding to their appropriate channels. This way you can scroll vertically from both, and only scroll horizontally the programs.

Here is a quick example:

ApplicationWindow {
    id: main
    width: 500
    height: 100
    visible: true
    color: "white"

    ListModel {
        id: modC
        ListElement { name: "Ch1" }
        ListElement { name: "Ch2" }
        ListElement { name: "Ch3" }
    }

    ListModel {
        id: modP1
        ListElement { name: "p1"; start: 0; duration: 6 }
        ListElement { name: "p2"; start: 6; duration: 6 }
        ListElement { name: "p3"; start: 12; duration: 6 }
        ListElement { name: "p4"; start: 18; duration: 6 }
    }
    ListModel {
        id: modP2
        ListElement { name: "p1"; start: 0; duration: 12 }
        ListElement { name: "p2"; start: 12; duration: 12 }
    }
    ListModel {
        id: modP3
        ListElement { name: "p1"; start: 0; duration: 8 }
        ListElement { name: "p2"; start: 8; duration: 8 }
        ListElement { name: "p3"; start: 16; duration: 8 }
    }

    property var subMod : [ modP1, modP2, modP3 ]

    Component {
        id: progDelegate
        Rectangle {
            property var source
            x: source.start * 50
            width: source.duration * 50
            height: 50
            color: "lightblue"
            border.color: "black"
            Text {
                text: source.name
            }
        }
    }

    Row {
        anchors.fill: parent
        ListView {
            id: list
            height: parent.height
            width: 100
            model: modC

            delegate: Item {
                width: 100
                height: 50
                Rectangle {
                    anchors.fill: parent
                    color: "red"
                    border.color: "black"
                    Text {
                        anchors.centerIn: parent
                        text: name
                    }
                }
                Component.onCompleted: {
                    var mod = subMod[index]
                    for (var i = 0; i < mod.count; ++i) progDelegate.createObject(flick.contentItem, {"source": mod.get(i), "y": index * 50})
                }
            }
        }
        Flickable {
            id: flick
            height: parent.height
            width: parent.width - list.width
            contentWidth: 1200
            contentHeight: contentItem.childrenRect.height
            clip: true
            flickableDirection: Flickable.HorizontalFlick
            contentY: list.contentY
        }
    }
}