Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QML: How can I pass model properties to a delegate loaded inside a GridView (or ListView) delegate?

I'm attempting to build a reusable QML Component that internally hosts a GridView. Each element in the GridView has a set of behaviors (mostly display and mouse-based stuff) that is common throughout the application. However, what is displayed inside the GridView elements changes depending on the use-case. (That is, the same encapsulated Component for one GridView, but elsewhere in the application it might use a different Component.)

So, what I would like to do is have each invocation supply a delegate that is added to each element in the GridView, which is already a delegate. In other words, something like this:

MyGrid.qml

import QtQuick 1.1

Rectangle {
  id: myGrid
  objectName: "myGrid"

  property Component internalDelegate
  property variant internalModel

  GridView {
    anchors.fill: parent

    delegate: Row {
      Loader {
        sourceComponent: myGrid.internalDelegate
      }
    }

    model: parent.internalModel
  }
}

The idea is that the Loader inside the GridView delegate loads the user-supplied delegate, which would look something like this:

Main.qml

import QtQuick 1.1

Rectangle {
  anchors.fill: parent

  width: 300
  height: 200

  MyGrid {
    anchors.fill: parent

    internalDelegate: Text {
      text: name
    }

    internalModel: ListModel {
      ListElement {
        name: "a"
      }

      ListElement {
        name: "b"
      }
    }
  }
}

However, this doesn't work. QML reports that "name" is an unknown variable inside the Text element. If I replace the name variable with a string (i.e. "hello"), it works as expected.

My question is, how can I pass the "name" variable to internalDelegate, or better yet, make the entire Model available so internalDelegate can access all of them (since the caller is defining the model as well).

A side question is: Is there a better way to do this?

like image 631
Jim Nelson Avatar asked Dec 09 '11 19:12

Jim Nelson


People also ask

What is a QML delegate?

The delegate provides a template defining each item instantiated by a view. The index is exposed as an accessible index property. Properties of the model are also available depending upon the type of Data Model. filterOnGroup : string. This property holds name of the group that is used to filter the delegate model.

What is property alias in QML?

Unlike an ordinary property definition, which allocates a new, unique storage space for the property, a property alias connects the newly declared property (called the aliasing property) as a direct reference to an existing property (the aliased property).

What is ListView in QML?

A ListView displays data from models created from built-in QML types like ListModel. A ListView has a model, which defines the data to be displayed, and a delegate, which defines how the data should be displayed. Items in a ListView are laid out horizontally or vertically.


3 Answers

I solved it like this:

MyGrid.qml

import QtQuick 1.1

Rectangle {
    id: myGrid
    objectName: "myGrid"

    property Component internalDelegate
    property variant internalModel

    GridView {
        id: grid
        anchors.fill: parent

        delegate: Row {
            Loader {
                sourceComponent: myGrid.internalDelegate
                property variant modelData: grid.model.get(index)
            }
        }

        model: parent.internalModel
    }
}

main.qml

import QtQuick 1.1

Rectangle {
    anchors.fill: parent

    width: 300
    height: 200

    MyGrid {
        anchors.fill: parent

        internalDelegate: Text {
            text: modelData.name
        }

        internalModel: ListModel {
            ListElement {
                name: "a"
            }

            ListElement {
                name: "b"
            }
        }
    }
}

Don't know if it's "better", but it has less and simpler code. The list models are a bit awkward and not really consistent in QML. And keep in mind that model.get(index) is owned by the GridView because the whole model is owned by that. So if you want to use that object after the list was destroyed you have to reparent or copy it.

like image 183
blakharaz Avatar answered Oct 06 '22 23:10

blakharaz


Just want to point out that "grid.model.get(model.index)" here:

    delegate: Row {
        Loader {
            sourceComponent: myGrid.internalDelegate
            property QtObject modelData: grid.model.get(index) //<
        }
    }

Is equivalent to "model" under the current context:

    delegate: Row {
        Loader {
            sourceComponent: myGrid.internalDelegate
            property QtObject modelData: model //< 
        }
    }
like image 12
jichi Avatar answered Oct 06 '22 22:10

jichi


I'm answering my own question because I kept working at the problem and found an acceptable solution. I'm not marking this question as answered (yet) because I would like to know if there are easier or more efficient solutions.

Here's how I solved this problem: I used the Binding component with the Loader and created a custom MyDelegate component that has a variant property for the model's current ListElement. Here's the modified code with important changes marked by comments.

Main.qml

import QtQuick 1.1

Rectangle {
  anchors.fill: parent

  width: 300
  height: 200

  MyGrid {
    anchors.fill: parent

    // *** Use MyDelegate rather than generic Component and refer to
    // *** model's properties via MyDelegate's model property
    internalDelegate: MyDelegate {
      Text {
        text: model.index + model.name
      }
    }

    internalModel: ListModel {
      ListElement {
        name: "a"
      }

      ListElement {
        name: "b"
      }
    }
  }
}

MyGrid.qml

import QtQuick 1.1

Rectangle {
  id: myGrid
  objectName: "myGrid"

  property Component internalDelegate
  property variant internalModel

  GridView {
    anchors.fill: parent

    delegate: Row {
      Loader {
        id: loader

        sourceComponent: myGrid.internalDelegate

        // *** Bind current model element to the component
        // *** when it's loaded
        Binding {
          target: loader.item
          property: "model"
          value: model
          when: loader.status == Loader.Ready
        }
      }
    }

    model: parent.internalModel
  }
}

The crucial addition is a custom MyDelegate component, which defines the model property as a variant. This binds at interpretation-time, meaning there's no error in Main.qml when the model's name property is referenced.

MyDelegate.qml

import QtQuick 1.1

Rectangle {
  property variant model
}

Something tells me there's an even easier way to do this, but this works for now.

like image 5
Jim Nelson Avatar answered Oct 06 '22 22:10

Jim Nelson