0

I want to create a reusable component where I could pass a model i.e.

["red", "green", "blue", "black", "orange", "pink", "gray", "navy", "magenta"]

And it would fill Grid with rectangles of model data. And if there are more than let's say 6 items in model it would then fill other "page".

That's how it should look like: example

Currently I use StackLayout have 2 Grid items and Repeater inside of them and I divided my model into 2:

model: ["red", "green", "blue", "black", "orange", "pink"]
model: ["gray", "navy", "magenta"]

To fill each "page" with rectangles.

Writing logic to dynamically separate model into separate parts for each page seems overly complicated. I have tried GridView but I couldn't find important properties like in Grid:

topPadding: 10
bottomPadding: 10
leftPadding: 20
rightPadding: 20
spacing: 10
columns: 2

Source of my example:

import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Rectangle {
        id: mainArea
        width: 400
        height: 400
        color: "beige"

        StackLayout {
            id: stackLayout
            anchors.fill: parent
            currentIndex: 0

            Grid {
                anchors.fill: parent
                topPadding: 10
                bottomPadding: 10
                leftPadding: 20
                rightPadding: 20
                spacing: 10
                columns: 2
                property int maxRows: 3

                Repeater {
                    model: ["red", "green", "blue", "black", "orange", "pink"]

                    Rectangle {
                        width: (parent.width - parent.leftPadding - parent.rightPadding - parent.spacing) / parent.columns
                        height: (parent.height - parent.topPadding - parent.bottomPadding - (parent.maxRows - 1) * parent.spacing) / parent.maxRows
                        color: modelData
                    }
                }
            }

            Grid {
                anchors.fill: parent
                topPadding: 10
                bottomPadding: 10
                leftPadding: 20
                rightPadding: 20
                spacing: 10
                columns: 2
                property int maxRows: 3

                Repeater {
                    model: ["gray", "navy", "magenta"]

                    Rectangle {
                        width: (parent.width - parent.leftPadding - parent.rightPadding - parent.spacing) / parent.columns
                        height: (parent.height - parent.topPadding - parent.bottomPadding - (parent.maxRows - 1) * parent.spacing) / parent.maxRows
                        color: modelData
                    }
                }
            }
        }
    }

    Button {
        anchors.bottom: mainArea.verticalCenter
        anchors.bottomMargin: 5
        anchors.left: mainArea.right
        text: "<"
        onClicked: stackLayout.currentIndex = 0
    }
    Button {
        anchors.top: mainArea.verticalCenter
        anchors.topMargin: 5
        anchors.left: mainArea.right
        text: ">"
        onClicked: stackLayout.currentIndex = 1
    }
}
Eligijus Pupeikis
  • 1,115
  • 8
  • 19
  • You want to have the model to be a array or a `ListModel`? – derM - not here for BOT dreams Sep 19 '17 at 11:32
  • @derM array. I guess `ListModel`would be even more complicated. – Eligijus Pupeikis Sep 19 '17 at 11:37
  • Well you could use the solution from [**here**](https://stackoverflow.com/questions/40086850/dynamically-create-page-based-on-listmodel-count/40100855#40100855) though... I wrote it shortly after starting with QML and there is much to improve. Usage of the `DelegateModel` might be faster, if you'd replace it by a C++ `...ProxyModel` like the `QSortFilterProxyModel` or a not-so-identity-proxymodel, but the latter requires a `ListModel` afaik. – derM - not here for BOT dreams Sep 19 '17 at 11:40

2 Answers2

2

You could try filtering the model to show only specific indices.

Or even simpler, you can simply set the delegate visibility depending on the index and items per page:

ApplicationWindow {
  id: main
  visible: true
  width: 640
  height: 480
  color: "darkgray"

  property int maxRows: 3
  property int page: 0
  property int iperp: 2 * maxRows

  Grid {
    anchors.fill: parent
    topPadding: 10
    bottomPadding: 50
    leftPadding: 20
    rightPadding: 20
    spacing: 10
    columns: 2

    Repeater {
      id: rep
      model: ["red", "green", "blue", "black", "orange", "pink", "gray", "navy", "magenta", "yellow", "cyan", "brown", "lightblue", "darkred"]

      Rectangle {
        width: (parent.width - parent.leftPadding - parent.rightPadding - parent.spacing) / parent.columns
        height: (parent.height - parent.topPadding - parent.bottomPadding - (maxRows - 1) * parent.spacing) / maxRows
        color: modelData
        visible: {
          var i = page * iperp
          return index >= i && index < i + iperp
        }
        Text {
          anchors.centerIn: parent
          text: index
        }
      }
    }
  }
  Row {
    anchors.horizontalCenter: main.contentItem.horizontalCenter
    anchors.bottom: main.contentItem.bottom
    Button {
      text: "<<"
      enabled: page
      onClicked: --page
    }
    Button {
      text: ">>"
      enabled: page < rep.count / iperp - 1
      onClicked: ++page
    }
  }
}

enter image description here

dtech
  • 47,916
  • 17
  • 112
  • 190
  • Love the solution. I didn't like that I used `StackLayout` so this is exactly what I was looking for. – Eligijus Pupeikis Sep 19 '17 at 12:09
  • For small models this is a simple solution. For large models you will have a massive overhead of `Rectangle`s. – derM - not here for BOT dreams Sep 19 '17 at 12:09
  • The `Repeater` always creates all instances. A `Grid` just ignores all `Item`s without dimensions or visibility for it's layout. – derM - not here for BOT dreams Sep 19 '17 at 12:13
  • [(From Documentation)](http://doc.qt.io/qt-5/qml-qtquick-grid.html) *"If an item within a Grid is not visible, or if it has a width or height of 0, the item will not be laid out and it will not be visible within the column."* You might change the `Grid` for a `GridView` and partition the `contentItem` to show always the right part. – derM - not here for BOT dreams Sep 19 '17 at 12:15
  • 1
    Yes, I did a quick check and indeed that's the case. All items are created. Still, perfectly usable for models that are not huge. In actual produciton I use my own proxy model that can be filtered and sorted via JS functors. – dtech Sep 19 '17 at 12:16
  • derM's solution is indeed the way to go because of the reasons mentioned above. – Eligijus Pupeikis Sep 19 '17 at 12:27
  • It has overheads of its own - every time the model changes, all delegate items will be recreated, using more CPU. And due to QML's lousy memory management, memory usage is likely to exceed having all objects in memory, unless you are dealing with a very high number of items, like thousands upon thousands of items. – dtech Sep 19 '17 at 12:34
2

For a simple array, you can use the method array.slice(from, to) to create models for each page.

property int page: 0

Button {
    text: "up"
    onClicked: page++
}

Grid {
    y: 100
    rows: 2
    columns: 2
    Repeater {
        model: ["red", "green", "blue", "black", "orange", "pink", "gray", "navy", "magenta", "yellow", "cyan", "brown", "lightblue", "darkred"].slice(page * 4, (page + 1) * 4)
        Rectangle {
            width: 100
            height: 100
            color: modelData
        }
    }
}

For QAbstractItemModel-descendents, you can use the method explained here if you want to have a QML-only solution.

Otherwise you might implement a faster filter model in C++ utilizing the QSortFilterProxyModel or maybe the QIdentityProxyModel

See this implementation by GrecKo for a possible way, how to get the SortFilterProxyModel working in QML.