Access ListView model within the Repeater component in the delegate - qt

I am using a ListView with a model and a delegate.
The model is a simple ListModel with three items. Each item has a value with the key myFirstRole.
The delegate contains a Repeater component to create an arbitrary number of Labels. The Labels have to use data from the model.
The model of the repeater can not be set to the Listview's model as I have the Repeater using other data.
Here is a minimal example of what I am trying to achieve:
//MyDelegate.qml
Component {
Item {
id: root
width: childrenRect.width
height childrenRect.height
Repeater {
model: 5 //It's not an option to set the repeaters model to the ListViews model. This example just illustrates my problem.
Label {
text: root.ListView.view.model.myFirstRole //This is the line where I want to be able to access the ListView's model, but I can't figure out how to properly reefer to it.
}
}
}
}
//MyListView.qml
ListView {
id: root
delegate: MyDelegate {}
model: ListModel {
ListElement {
myFirstRole: "one"
}
ListElement {
myFirstRole: "two"
}
ListElement {
myFirstRole: "three"
}
}
}
Using Qt 5.7.0 with MSVC2015 32bit

I think that you can't access the roles via the special model property mentioned here (which is what I'm assuming you were trying to do) from the scope of the Repeater. Instead, you can declare a property at the root level of the component that can then be used in nested scopes:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
ListView {
anchors.fill: parent
model: ListModel {
ListElement { myFirstRole: "Dog" }
ListElement { myFirstRole: "Cat" }
}
delegate: Item {
id: root
width: childrenRect.width
height: childrenRect.height
property string myFirstRoleData: myFirstRole
Repeater {
model: 5
Text {
text: myFirstRoleData
}
}
}
}
}
This might get a bit tedious if you have a lot of properties though. From some quick playing around, it looks like it's also possible to store the entire model object in a property:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
ListView {
anchors.fill: parent
model: ListModel {
ListElement { myFirstRole: "Dog" }
ListElement { myFirstRole: "Cat" }
}
delegate: Item {
id: root
width: childrenRect.width
height: childrenRect.height
property var modelData: model
Repeater {
model: 5
Text {
text: root.modelData.myFirstRole
}
}
}
}
}
modelData is probably not the best name to use though, seeing as Qt uses that name for models with only one role, but... if you're going with this approach, you're gonna have more than one role anyway. :)
It looks like Qt Quick Controls' (1) Tumbler does this too.

//MyDelegate.qml
Item {
id: root
property var listViewModel // pass the model data to here
width: 100
height: 50
Column {
Repeater {
model: 5 // Use a different model here
Text {
width: 50
height: 10
text: listViewModel.myFirstRole //This is the line where I want to be able to access the ListView's model, but I can't figure out how to properly reefer to it.
}
}
}
}
//MyListView.qml
ListView {
id: root
width: 100
height: 500
delegate: MyDelegate {
listViewModel: model // set the model data here
}
model: ListModel {
ListElement {
myFirstRole: "one"
}
ListElement {
myFirstRole: "two"
}
ListElement {
myFirstRole: "three"
}
}
}
See the comments in the code. It is not trivial to guess what you want to achive, but I hope I guessed right.

Related

qml delegate cannot create binding with ListModel when ListModel isEmpty and append an empty jsobject at first time

I have a repeater like this:
Repeater {
id: idRepeater
model: fruitModel
delegate: Rectangle {
id:idRect
width:20
height:20
color:sColor
}
}
My model has nothing, like this:
ListModel {
id: fruitModel
}
All I want is that I can add element by ListModel's append(jsobject dict) API.
I write code below:
fruitModel.append({});
fruitModel.append({"sColor":"yellow"});
But the second rectangle is not yellow. why?
You should refer to model data like:
ListModel {
id: fruitModel
dynamicRoles: true
}
Repeater {
id: idRepeater
model: fruitModel
delegate: Rectangle {
id:idRect
width:20
height:20
color: model.sColor
}
}
You should also set dynamicRoles to true for your list model when members of each object are different.

Create QML Items out of DelegateModel

Is it possible to create QML Items out of a DelegateModel?
Here is a example DelegateModel:
DelegateModel
{
id: delegateModel
model: ListModel
{
ListElement { name: "#FAFAFA"; test: "object1" }
ListElement { name: "#000000"; test: "object2" }
}
delegate: Rectangle
{
objectName: test
width: 50
height: 50
color: name
}
Component.onCompleted:
{
Utils.var_dump(items,3)
items.create(0)
Utils.var_dump(items.get(0),3)
}
}
The Result should look like this:
Rectangle
{
objectName: "object1"
width: 50
height: 50
color: "#FAFAFA"
}
Rectangle
{
objectName: "object2"
width: 50
height: 50
color: "#000000"
}
For every ListElement there is a created delegate with the inserted ListElement data.
You can do that with anything that is usable to instantiate a Model (a View)
For example you could use it as a model for a ListView, a GridView or a Repeater. As the model provides the delegate on its own, you do not need to specify any delegate in the View, that instantiates it.
Column {
Repeater {
model: delegateModel
// delegate: ... <--- Nothing here! Uses the delegate from the Model.
}
}
If you use the create(index)-Method, the delegate will be created, but has no parent, so it is not displayed. So you need to set the parent, to have it shown:
Button {
onClicked: {
for (var a = 0; a < dm.items.count; a++) {
var o = dm.items.create(a)
o.parent = r
}
}
}
You need to be aware, that the DelegateModel (without Package and Parts) can't be used in multiple views, as each entry/delegate can be instantiated only once at the same time. If you want to have that,
consider using a QSortFilterProxyModel to filter the stuff, and use as much Views that provide their own delegates, as you want.

How to apply Qt Graphical Effect on Delegate of Repeater in QML

I want to apply the QtGraphicalEffect ColorOverlay to an Image in a Repeater delegate. The problem is that I have to set the id of the Image as the source of the ColorOverlay, but I don't know the id, because it is dynamically created by the Repeater.
import QtQuick 2.4
import QtGraphicalEffects 1.0
Item {
id:mainItem
width: 800
height: 400
property string vorneColor: "red"
ListModel {
id: safeRailModel
ListElement {name: "vorne"; imageSource:"images/saferail/ring_vorne.png";}
ListElement {name: "vorneLinks"; imageSource:"images/saferail/ring_vorne_links.png"; }
}
Component {
id: imageDelegate
Image {
source: imageSource
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
fillMode: Image.PreserveAspectFit
opacity: 1
visible: true
}
}
Repeater {
id: safeRailRepeater
model: safeRailModel
delegate: imageDelegate
}
Component {
id: effectsDelegate
Item{
id:effectsItem
ColorOverlay {
anchors.fill: safeRailRepeater.itemAt(index)// <-- This doesn't work
source: safeRailRepeater.itemAt(index)// <-- This doesn't work
color: vorneColor
}
}
}
Repeater {
id: safeRailEffectsRepeater
model: safeRailModel
delegate: effectsDelegate
}
}
How can I set source and anchors.fill properties?
I searched everywhere, but I've only found something along the lines of safeRailRepeater.itemAt(index) or safeRailRepeater.itemAt(index).item but neither the former nor the latter works.
Side note: the ColorOverlay doesn't need to be in a seperate delegate and Repeater.
It would be great if somebody has a solution for this problem or could point me in the right direction.
Thank you very much!
The problem is that the itemAt() function call returns null because the other Repeater hasn't loaded its items yet. Also, the function call won't ever be reevaluated, because none of its arguments ever change, so you'll always get null.
The design is a bit odd though; I'd suggest moving the ColorOverlay into the same delegate, as you mentioned that it doesn't have to be in a separate Repeater:
import QtQuick 2.0
import QtQuick.Window 2.0
import QtGraphicalEffects 1.0
Window {
id: mainItem
width: 800
height: 400
visible: true
property string vorneColor: "red"
ListModel {
id: safeRailModel
ListElement { name: "vorne"; vorneColor: "salmon"; }
ListElement { name: "vorneLinks"; vorneColor: "steelblue"; }
}
Component {
id: imageDelegateComponent
Rectangle {
id: delegate
color: "grey"
opacity: 1
visible: true
width: 64
height: 64
layer.enabled: true
layer.effect: ColorOverlay {
color: vorneColor
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
Repeater {
id: safeRailRepeater
model: safeRailModel
delegate: imageDelegateComponent
}
}
}
Using the layer API of Item is a convenient way of specifying graphical effects.
I also changed the Image to a Rectangle, since we don't have access to those images, and put the Repeater within a row, so that you can see all of the delegates.

Qt/QML: How to refer to a component/object from a GridView's model's ListElement

I have a GridView with a delegate that is supposed to use a Loader to load and display components which are defined in the same QML file.
Let's say I have a GridView like this:
GridView {
delegate: Rectangle {
Loader { sourceComponent: model.pageContents }
}
model: ListModel {
ListElement { /* how do I reference any of the components defined below from here, so the Loader can actually load and display it... ? */ }
}
}
Component {
id: page_01
Rectangle {
color: "red"
// Page contents for page 1 go here.
}
}
Component {
id: page_02
Rectangle {
color: "red"
// Page contents for page 2 go here.
}
}
I know I can create QML objects and components from Strings, external files and URLs. But I'd like to ideally do something like this:
ListModel {
ListElement { pageContents: page_01 }
ListElement { pageContents: page_02 }
}
I'd prefer to keep everything in a single QML file, so I can easily transfer and store it on the device without having to worry about resolving external dependencies, etc.
How do I refer to components in the same QML file from within ListElements?
Due to ListElement values limitation you cannot just put item id here. But you easily can use some external storage, for example property to store pointers to your pages. In example below I use array with pages ids and an index to wanted page as ListElement data:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width: 600
height: 600
GridView {
id: grid
anchors.fill: parent
property var pages: [page_01, page_02]
model: ListModel {
ListElement { pageIndex: 0 }
ListElement { pageIndex: 1 }
}
delegate: Loader { sourceComponent: grid.pages[pageIndex] }
}
Component {
id: page_01
Rectangle {
color: "red"
width: 100
height: 100
Component.onCompleted: console.log("page_01 was created")
}
}
Component {
id: page_02
Rectangle {
color: "blue"
width: 100
height: 100
Component.onCompleted: console.log("page_02 was created")
}
}
}

ListElements (ListModel) defined in other qml files?

I got a fonctionnal ListModel defined like this :
ListModel {
id: leftGrid
ListElement { icon: "Images/1.png" }
ListElement { icon: "Images/2.png" }
}
The thing is that I'd like to define ListElement in separate qml files but I really don't know how to do it...
I wrote the qml like this :
//myWidget.qml
import QtQuick 1.0
ListElement {
icon: "Images/X.png"
}
But I don't know how to "invoke" or "instanciate" it in my main file...
I tried :
ListModel {
id: leftGrid
ListElement { icon: "Images/1.png" }
myWidget //qml file
}
and :
ListModel {
id: leftGrid
ListElement { icon: "Images/1.png" }
ListElement { myWidget }
}
Both doesn't work...
Any help with be welcomed, thanks in advance.
I don't think it's possible to have ListElements as separate files. This is because when you do that you are implicitly creating a Component, with your contents inside (in this case the ListElement). However the ListModel can only accept ListElements as its children, not Components with nested ListElements inside.
What you can do however to dynamically define your model items is to declare a ListModel, then add your data via a piece of javascript, for example in your Component.onCompleted handler.
If you look at the API for ListModel you will see it has an append() method, among others.
You can pass a JS dictionary to this method and it will add a new ListElement to the list and populate its properties according to the dictionary.
Example:
import QtQuick 1.0
Rectangle {
width: 360
height: 360
ListView {
id:list
anchors.fill: parent
model: ListModel {
ListElement { foo: "hello" }
}
delegate: Text {
text: foo
width: ListView.view.width
}
Component.onCompleted: {
list.model.append({ "foo": "world" })
}
}
}
Your list will appear with two items in it: "hello" and "world"

Resources