'QML List of Lists (2D data)

Summary: How can I nest one ListView inside another, feeding data from the outer model to an inner model inside the delegate?

Data and Desire

I have an array of arrays of data in JS:

[[ {t:0, label:"Big Bang"}, {t:5, label:"Earth Forms"} ],
 [ {t:0, label:"Alternate Bang"}, {t:3, label:"Cool Stuff"} ],
 [ {t:1, label:"Late Bang"}, {t:4, label:"Whee"}, {t:5, label:"More" } ]]

I want to display this as a horizontal-scrolling "timeline", where the top-level array represents rows of data and each sub-array represents items within that row, placed at various times.

Excel-based mockup

Note that the timeline is only discrete at the millisecond level, with values out to the hour range. Unlike what this Excel-based mockup shows, a 2D grid of merged cells would be infeasible.

Code and Problem

I'm passing the array to a ListView via a custom ListModel:

Item {
    property var timeline: []
    onTimelineChanged: {
      rowData.clear();
      timeline.forEach(function(evts){
        rowData.append({ events:evts });
      });
    }
    ListView {
        id: rows
        model: ListModel { id:rowData }
        delegate: Item {
            Text { text: "Idx #"+index }
            ListView {
                orientation: ListView.Horizontal
                model: ListModel { id:events }
                delegate: …
            }
        }
    }
}

This is populating the rows properly, but I cannot figure out how to pass the events data role from the outer ListView into the events model within each delegate.

Additionally, as I wrote this question up, I realized that each row may try to scroll horizontally independent of one another. I want them all to scroll as a group. This makes me wonder if nested ListView is the appropriate tool for this job, or if I should be using some other data-driven QML structure.



Solution 1:[1]

You can simply assign events to the inner ListView.model, for example:

ListView {
    anchors.fill: parent
    model: ListModel { id:rowData }
    delegate: Rectangle {
        width: parent.width; height: 50
        ListView {
            anchors.fill: parent; orientation: ListView.Horizontal
            model: events
            delegate: Item {
                width: 80; height: 20
                Text { text: label }
            }
        }
    }
}

When a JavaScript array is appended to a ListModel row, it will be automatically converted to a ListModel (however, Qt documentation says nothing about this). So when you access the role name events in the delegate in the outer ListView, what you get is something like this:

ListModel { //row 0
    id: events
    ListElement {t:0, label:"Big Bang"}
    ListElement {t:5, label:"Earth Forms"}
}

And I think it's fine to horizontally scroll all inner ListView at the same time by setting contentX or positionViewAtIndex to all inner ListView.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mcchu