How can I get property in gridview which is Repeater's child - qt

how can i get gridView.itemAtIndex(index).color?
what is i have tried:
contentrepeater.itemAt(5).gridView.model.color;
contentrepeater.itemAt(5).gridView.itemAtIndex(5).color;
But it doesn't work
Rectangle {
anchors.top: bar.bottom
Layout.fillHeight: true
Layout.fillWidth: true
Repeater {
id: contentrepeater
model: 11
Rectangle {
anchors.fill: parent
color: 'red'
visible: false
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 150
cellHeight: 170
clip: true
model: 11
delegate: Rectangle{
height: 160
width: 140
color: '#333333'
}
}
}
}
}

Ultimately you probably don't want to do it that way. It will be hackish and error-prone. For example GridView only provides item access based on position coordinates, not indexes. So you'd need to dive into its children which are going to be created dynamically... it's possible but very messy and not really supported API.
You are better off defining your item models first, then using the GridView (or whatever) to display them. That way you can manipulate objects in the models and changes will be reflected in the view (instead of the other way around like you're trying now).
This example (based on your posted code) creates 4 layouts with 11 squares each and animates the color in each square using a timed script. Note that we need separate instances of the models for each of the GridViews within contentrepeater (otherwise it is only shown in the last view). So the example is a bit more convoluted since the item models are being created dynamically.
I should add that in a "real" application I'd use a different method of tracking the created item models instead of looking them up in the display hierarchy like I have it here. The main point this is trying to demonstrate is to manipulate the displayed items (delegates) via changes to the model data.
import QtQuick 2.9
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQml.Models 2.3
Pane {
id: root
width: 400
height: 650
padding: 9
// rectangle items to create per model
property int itemsPerModel: 11
// prototype object model
property Component itemModel: ObjectModel {}
// prototype model item
property Component delegate: Rectangle {
height: 30
width: 30
color: '#333333'
}
// Creates a new ObjectModel with some Rectangle items as children
function newItemModel() {
var model = itemModel.createObject(root);
for (var i=0; i < itemsPerModel; ++i)
model.append(delegate.createObject(root));
return model;
}
SequentialAnimation {
id: animate
running: true
loops: Animation.Infinite
ScriptAction {
property string nextColor: "blue"
property int nextSet: 0
property int nextItem: 0
script: {
contentrepeater.itemAt(nextSet) // the Rectangle within the GridLayout
.children[0] // the gridView within the Rectangle
.model.get(nextItem) // the model's delegate item (a Rectangle)
.color = nextColor; // set the new color on it.
// advance to next item or set of items.
nextItem = (nextItem+1) % root.itemsPerModel;
if (!nextItem)
nextSet = (nextSet+1) % contentrepeater.count;
nextColor = (nextColor === "blue" ? "orange" : nextColor === "orange" ? "white" : "blue");
}
}
PauseAnimation { duration: 100 }
}
GridLayout {
columns: 2
anchors.fill: parent
Repeater {
id: contentrepeater
model: 4
Rectangle {
color: 'red'
width: 150
height: 170
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 40
cellHeight: 40
clip: true
// here we need a unique instance of the ObjectModel
model: root.newItemModel()
}
}
}
}
}

Related

QML Repeater is not drawn correctly after model is changed

I'm writing a QML application to draw brown blocks inside a blue Rectangle. The application performs this task using a ColumnLayout and a Repeater to draw an arbitrary number of blocks (4 by default):
I'm attempting to change the model of the Repeater dynamically when the user clicks on the screen to force the UI to draw a different amount of blocks. Whenever the desired amount of blocks is changed via blockCount, it triggers the recalculation of blockHeight, the height of each block, so that a smaller amount of blocks can occupy more space on the screen. At least that is the theory!
For debugging purposes, clicking on the screen sets blockCount to 2.
Here's a sample image with the expected result on the Left and the current result on the Right:
As you can see on the image above, when the click happens and rectId.blockCount = 2 is executed, it appears to trigger a sequence of calls that:
Ends up changing the model of the Repeater before blockHeight is recalculated;
Or the anchors for the ColumnLayout are reset, for some bizarre reason;
Or something else is going on;
I'm trying to understand what's causing this behavior and also looking for an approach that allows the application to change the number of blocks dynamically while being able to draw them correctly!
What am I missing?
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.15
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
if (numBlocks === undefined)
numBlocks = rectId.blockCount;
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (numBlocks-1))) / numBlocks;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.bottom: parent.bottom
anchors.bottomMargin: rectId.borderWidth + rectId.blocksSpace
anchors.left: rectId.left
anchors.leftMargin: rectId.borderWidth*2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
width: rectId.blockWidth
height: rectId.blockHeight
// Debug:
Component.onCompleted: {
print("Inner Rectangle")
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// since repId uses blockCount as the model, any change to it should automatically recreate the elements of the Repeater
// here we force blockHeight to be recalculated before the model is changed
rectId.blockHeight = rectId.updateBlockHeight(2)
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Why are you calculating the blockHeight and blockWidth while you can leverage the power of ColumnLayout?
Use the Layout.fillWidth and Layout.fillHeight properties to signal the ColumnLayout that the blocks should fill the entire width & height, evenly distributed. And then set the correct size to the ColumnLayout and it will do the calculations you try to program itself.
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
Layout.fillWidth: true
Layout.fillHeight: true
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
EDIT for keeping blockHeight calculation
If you insist on keeping the calculation since it is more difficult in the real world (fair enough), I would suggest to use implicitHeight and implicitWidth. This works because the Layout engine does not trigger on changes on width/height since it is supposed to set these himself, it does however monitor the implicit sizes:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (rectId.blockCount-1))) / rectId.blockCount;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
implicitWidth: rectId.blockWidth
implicitHeight: rectId.blockHeight
onXChanged: print("x[",index,"]=", x)
onYChanged: print("y[",index,"]=", y)
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Also, I refactored the updateBlockHeight function, it's not needed to explicitly set it, the QML engine is so smart it will even reevaluate the binding when one of the parameters in the function changes!
(From my comments)
With Qt 5.15.1 on Linux get the expected output. With Qt 5.15 get the output you indicate for what is probably a bug that has been fixed in Qt 5.15.1.

Qml: How to set a property to all GridView items

I am trying to make a change to all items of a GridView.
I have tried to iterate through either the model or the grid, I looked at similar examples on the web, but everything I try ends with Cannot read property 'buttonText' of undefined.
It seems to me that the problem is that the interpreter can't figure out that the item from the grid or model is a Button. But I don't know how to cast it.
If I change the log to only display the item, not any property, (see code snippet), it seems that it knows it is an item... see my experiments below.
The only thing I can make work is set a property (or call a signal, or a function) from the delegate. But that only affects one grid item, not all.
How can I set a property on every item of the grid ? Alternatively, how can I send a signal, or call a function, on every item?
My experiments are in function changeEverythingFunction()
file: Button.qml
Item
{
id: itemButton
signal changeEverything
property int buttonIndex
property string buttonText
...
}
file: Model.qml
Item
{
id: modelItem
ListModel
{
id: listModel
}
property int buttonCount: listModel.count
function changeEverythingFunction()
{
// for (var i = 0; i < buttonCount; i++)
// listModel.setProperty(i, buttonText, "abc")
for(var childIndex in gridItems.contentItem.children)
{
console.log(listModel.get(childIndex).buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex].buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex]["buttonText"]) // undefined (I saw this in a SO example)
var item = gridItems.contentItem.children[childIndex]
console.log(item) // qml: QQuickItem(0xe496370)
}
}
MouseArea
{
....
Rectangle
{
...
GridView
{
id: gridItems
anchors.fill: parent
clip: true
model: listModel
delegate: Item
{
id: buttonDelegate
Button
{
buttonIndex: gridId
buttonText: itemText
onChangeEverything:
{
changeEverythingFunction();
}
}
}
}
}
}
}
Your approach is in the opposite direction: Your approach is to obtain the item of the view and modify it, but the approach that Qt points out is that the view reflects the information of the model and modifies it when necessary.
The following is a simple example where every time you press on the button with "change me" text increasing the number it shows, but if you press the button with "change all" text it will change all the numbers. As it is observed everything is done through the model, not through the view that are only used to display information or receive user interaction.
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
Window {
visible: true
width: 640
height: 480
ListModel{
id: listmodel
}
function changeAll(){
for(var i=0; i< listmodel.count; ++i){
listmodel.setProperty(i, "number", listmodel.get(i).number + 1)
}
}
GridView{
id: grid
anchors.fill: parent
clip: true
model: listmodel
cellHeight: 120
cellWidth: 120
delegate: Item {
width: grid.cellWidth; height: grid.cellHeight
Column {
anchors.fill: parent
Text { text: model.number; anchors.horizontalCenter: parent.horizontalCenter }
Button{text: "change me"; onClicked: model.number +=1}
Button{text: "change all"; onClicked: changeAll()}
}
}
}
Component.onCompleted: {
for(var i=0; i < 10; ++i){
listmodel.append({"number": 0});
}
}
}

How to add children inside gridlayout dynamically in Qt quick QML code

I want to add controls at runtime, e.g. a certain number of TextField items added to a GridLayout.
I've tried to use Repeater like in the code below (some code skipped for brevity).
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
width: 400
height: 400
GridLayout {
id : gridmain
Repeater {
id:gridgenerate
TextField{
id:leditfill
font.pointSize: 10
placeholderText: index +1
focus: true;
}
}
}
}
The Repeater is populated via a JS function:
function gameview() {
console.log("grid")
gridmain.rows = 10
gridmain.columns = 10
gridgenerate.model = gridmain.rows * gridmain.columns
gridgenerate.forceActiveFocus()
}
Is this the correct approach? How do I get focus on first TextField of the Gridlayout?
Almost done. Remove gridgenerate.forceActiveFocus() and set TextField focus property to true if it's the first item: index == 0.
GridLayout {
id : gridmain
Repeater {
id:gridgenerate
TextField{
id:leditfill
font.pointSize: 10
placeholderText: index +1
focus: index == 0 // only first item get focus
}
}
its' a good aproach.
you can do it dinamicaly (like a gridView)
function gameview() {
console.log("grid")
gridgenerate.model = 0 //destroy all items created by repeater
gridmain.rows = 10
gridmain.columns = 10
gridgenerate.model = buttonModel.count //add new items, using ListModel
}
ListModel {
id:buttonModel
ListElement {
color: "red"
cols:2
rows:2
name: "B1"
}
}
Repeater {
id:gridgenerate
property var listobjects:[]
Rectangle{
id:button
color :buttonModel.get(index).color
}
}

ListView in subwindow triggers immediate close, or whilst scrolling

I have rather strange scenario whereby if I launch a subwindow that contains a ListView with a moderately complex delegate and enough items to comfortably exceed the visible area, the entire subwindow will immediately close on launch.
Reducing the complexity of the delegate will allow the window to open, but then rapidly scrolling the ListView will forcibly close it.
This SSCCE triggers the effect on my laptop, but on a more powerful machine it may only do it whilst scrolling (or perhaps the delegate may need to be more complex):
import QtQuick 2.3
import QtQuick.Window 2.0
Window {
width: 300
height: 200
Component.onCompleted: {
win.createObject( null );
}
Component {
id: win
Window {
width: 600
height: 400
visible: true
ListView {
id: view
anchors.fill: parent
model: 100
boundsBehavior: Flickable.StopAtBounds
clip: true
delegate: Rectangle {
width: view.width
height: 24
property int debugLevel: index % 3
property int timestamp: index * 1000
property int message: index
color: "darkgray"
Row {
anchors.fill: parent
Repeater {
id: delegateRepeater
property list< QtObject > roleModel: [
QtObject {
property string label: timestamp
property int itemWidth: 100
},
QtObject {
property string label: debugLevel
property int itemWidth: 100
},
QtObject {
property string label: message
property int itemWidth: view.width - 100 - 100
}
]
model: roleModel
Item {
width: itemWidth
anchors {
top: parent.top
bottom: parent.bottom
}
Text {
anchors {
fill: parent
leftMargin: 4
}
verticalAlignment: Text.AlignVCenter
text: label
elide: Text.ElideRight
}
Rectangle {
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
}
width: 1
visible: index != ( delegateRepeater.count - 1 )
color: "white";
}
}
}
}
}
}
}
}
}
There doesn't seem to be any particular part of the code that is causing the problem, removing any of the objects in the delegate reduces the probability of the subwindow closing.
I've added the debugging tag because my main problem is that this effect produces no debug output. If I add a breakpoint into the subwindow's destruction handler (Component.onDestruction) then there is a single stack entry pointing at the model: roleModel statement - but removing the entire Repeater and replacing with a copy-and-pasted equivalent yields the same results minus the stack entry.
So I would be grateful is anyone knows of a way of getting more information from this pure QML example.
As noted by #BaCaRoZzo the changing of behaviour by modifying the delegate code seems to be an unrelated side-issue.
The real cause is because it turns out you cannot create new root contexts (i.e. top-level windows) from QML. This was hinted at being resolved when Qt Quick Components were released, but the blog post boasting of Window doesn't explicitly state this. Creating a new Window and passing null for the parent technically works but the result seems to be very unstable.
Thankfully in my circumstance I'm creating a QML/C++ application so I've solved the issue by creating new root contexts from Q_INVOKABLE methods on the C++ side. But if you're developing a pure QML application, it seems that you are out of luck.

QML iterating over custom elements and calling function

I have a custom element called MenuButton:
import QtQuick 1.1
import VPlay 1.0
Image {
property alias text: buttontext.text
property alias mouseArea: area
property alias fontBold: buttontext.font.bold
property alias textSize: buttontext.font.pixelSize
id: button
source: "img/cloudButton.png"
opacity: 1
Text {
id: buttontext
color: "black"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 50
font.bold: true
}
MouseArea {
id: area
anchors.fill: parent
onPressed: button.opacity = 0.7
onReleased: button.opacity = 1.0
onCanceled: button.opacity = 1.0
}
function doStuff {
// do something here
}
width: 200
height: 60
}
Now, in my main View, I have a Column with 5 of those MenuButtons. And I want to iterate over them and call the function doStuff(). How do I do that?
I tried with column.childAt(i) and stuff like that, nothing worked.
MainView.qml
Rectangle {
width: 480; height: 320
// HERE IS MY PROBLEM, how do I iterate over all my elements in the column?
function update() {
for(var i = 0; i < 5; i++) {
column.childAt(i).doStuff(); // THIS IS WHAT I WANT TO DO
}
}
Column {
id: column
spacing: 5
anchors.centerIn: parent
Repeater {
id: repeater
model: 5
MenuButton {
id: levelbutton
text: "Level " + (modelData+1);
source: "img/cloud4.png"
}
}
}
}
The problem is in my update function in the MainView.qml
I dont know how to iterate over the elements and call the doStuff() function.
You may use Component.onCompleted attached signal, like this:
import QtQuick 1.0
Rectangle {
height: 600
width: 600
Repeater {
model: 5
Item {
Component.onCompleted: console.log('Component ' + index + ' completed!')
}
}
}
However, please consider, that this imperative operation is not good, because it will be called all time after model update. Probably you have problem X and asking how to get Y, that (you think) will solve your X?
From what I've seen in the source code in the QDeclarativePositioners class you cannot access the children elements!
But you could turn the way you call your doStuff() method: when do you want it to be called? After some time has passed (then add a Timer element to your MenuButton), or when a signal occurs? In the latter case, you could use the Connections element and listen to the signal beign emitted in your calling qml file where you use the Column and Repeater.
Cheers, Chris
You can access the children of an element via the children property that exists on all QtObjects. It contains an array of child elements and can be freely acccessed in javascript.
eg. element.children[0].doStuff()
Generally speaking, you should avoid actions that require you to manually iterate over the children. However, if you are attempting to write code that generically calls something on each of the children you do not always have a choice.

Resources