Custom view with model and delegate both accessible from both QML files

502 views Asked by At

Although I already figured it out, it took quite a while. If you know a less convoluted solution - please share.

I'd like to provide a custom View that works with any type of model (with arbitrarily named fields):

  • model: 3
  • model: [ "red", "yellow", "green" ]
  • model: [ {color: "red"}, {color: "yellow"}, {color: "green"} ]
  • model: ListModel{ ListElement{color: "red"}; ListElement{color: "green"} }
  • subclassed QAbstractItemModel and such...

To do that I decided to provide a property Component delegate that user would populate with a visual Item that would be inserted deep into my View.

However, the View also needed to access some information from that same delegate because otherwise it would either promote unneeded code duplication, or unneeded complexity of proxy model.

MyView {
     mymodel: ["red", "blue"]
     mydelegate: Text {
         text: "color=" + modelData.name
         must_also_somehow_set_color_of_MyView_element_to: modelData.color
     }
}

...where MyView should be something like:

Column {
    Repeater {
        model: mymodel
        delegate: Rectangle {
            color: somehow_get_color_from_mydelegate
            Text {} // <--- instantiate mydelegate here with data from mymodel
        }
    }
}

Doing so seems easy but a naive attempts didn't work. The solution posted as an answer did for me.

2

There are 2 answers

2
Jack White On

The trick is that model which is visible to Repeater or Instantiator's delegate contains everything that is visible to said delegate including index and modelData.

Said model can be used as a parameter to ListModel.append() to create a ListModel with a single element.

It can then be assigned as a model to a Repeater which will load the delegate with a copy of all context properties that QML normally derives from the element of View's model including index, modelData and model itself.

Upon instantiation, Repeater/Instantiator fires onItemAdded/onObjectAdded signal, which can be used as a cue to obtain a reference to the instance of View's delegate

User of the View can be asked to provide a root property in the delegate that would store obtained/calculated value which can be used by a View

Alternatively, the View can contain another Repeater or Instantiator and request another delegate to obtain values it itself needs. Or a container type can encapsulate both View's data and visual Item.

Example:

MyView.qml

import QtQuick 2.0
import QtQml.Models 2.1
Column {
    property var mymodel
    property Component mydelegate

    Repeater {
        model: mymodel
        delegate: Rectangle {
            width: childrenRect.width; height: childrenRect.height

            //color: model.color //works if "mymodel" has role "color"
            property var myitem: null //this will hold instance of mydelegate
            color: ( null !== myitem
                     && undefined !== myitem.bgcolor ) 
                     ? myitem.bgcolor : "transparent" //reads property of mydelegate

            // <-- mydelegate will be injected here
            Repeater {
                delegate: mydelegate
                model: ListModel{
                    Component.onCompleted: append(model)
                }
                onItemAdded: myitem = item
                onItemRemoved: myitem = null
            }
        }
    }
}

UsageExamples.qml

import QtQuick 2.0
import QtQuick.Window 2.0
Window{
    visible: true
    Row {
        spacing: 10

        MyView {
            mymodel: ListModel {
                ListElement{ name: "roses"; color: "red";   }
                ListElement{ name: "violets"; color: "blue";   }
            }
             mydelegate: Text {
                 text: name
                 property color bgcolor: model.color
             }
        }

        MyView {
             mymodel: [{name: "roses", color: "red"}, {name: "violets", color: "blue"}]
             mydelegate: Text {
                 text: modelData.name
                 property color bgcolor: modelData.color
             }
        }

        MyView {
             mymodel: ["red", "blue"]
             mydelegate: Text {
                 text: "color="+modelData
                 property color bgcolor: modelData
             }
        }

    }
}
5
JarMan On

I think you're really looking for a Loader to instantiate the delegate, rather than a second Repeater. You can also simplify a bit by using aliases. Try something like this:

Column {
    property alias mymodel: repeater.model
    property Component mydelegate

    Repeater {
        id: repeater
        delegate: Rectangle {
            width: childrenRect.width; height: childrenRect.height

            color: ( null !== loader.item
                     && undefined !== loader.item.bgcolor ) 
                     ? loader.item.bgcolor : "transparent"

            Loader {
                id: loader
                sourceComponent: myDelegate
            }
        }
    }
}