How to preserve bindings in a re-usable QML component? - qt

I'm trying to preserve the double binding logic from within my slider component such that it behaves the same as the builtin Slider:
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Window {
ColumnLayout {
SimpleSlider { id: sslider0; value: .3 }
SimpleSlider { id: sslider1; value: 1 - sslider0.value }
Slider { id: slider0; value: .3 }
Slider { id: slider1; value: 1 - slider0.value }
}
}
Unfortunately, it doesn't:
Here is my SimpleSlider.qml:
import QtQuick 2.4
Item {
id: simple_slider
property real value: .5
property real min_value: 0
property real max_value: 1
width: childrenRect.width
height: childrenRect.height
function linear(a, b, x) { return (x - a) / (b - a) }
function mix(a, b, x) { return a * (1 - x) + b * x }
FontMetrics { id: fontMetrics }
Rectangle {
id: main_box
width: Math.max(txt_label.width + 10, 50)
height: fontMetrics.height + 10
radius: 2
color: "darkgrey"
}
Rectangle {
id: filled_box
width: linear(min_value, max_value, value) * main_box.width
height: main_box.height
radius: main_box.radius
color: "orange"
}
Text {
id: txt_label
text: value.toFixed(3)
anchors.centerIn: main_box
}
MouseArea {
property real xval: simple_slider.value
id: drag_slider
anchors.fill: parent
onPositionChanged: xval = get_value_from_pos(mouse.x)
function get_value_from_pos(x) {
var v = Math.min(Math.max(linear(0, main_box.width, x), 0), 1)
return mix(min_value, max_value, v)
}
}
Binding {
target: simple_slider
property: "value"
value: drag_slider.xval
}
}
The reason is obvious: I'm overwriting the binding here: onPositionChanged: xval = get_value_from_pos(mouse.x) (and confirmed by QT_LOGGING_RULES="qt.qml.binding.removal.info=true")
But while I identified the core of the issue, I'm not sure how to deal with it.
There might be a twisted design where I would turn this into a declarative based form using the position change state, but then I'll have a similar issue when my slider will also accept the value from a TextInput (which will need to update the value in a similar fashion in its onAccepted callback).

JarMan's hint is a good one. I tried to solve the bidirectional binding problem in your example, but had no luck. So I present to you the styled standard slider:
This is my SimpleSlider.qml:
import QtQuick 2.15
import QtQuick.Templates 2.15 as T
T.Slider {
id: slider
width: 200
height: 40
background: Rectangle {
color: "darkgrey"
Rectangle {
width: slider.visualPosition * parent.width
height: parent.height
color: "orange"
}
}
Text {
text: slider.value.toFixed(3)
anchors.centerIn: slider
}
}

Edited SimpleSlider.qml:
import QtQuick 2.15
import QtQml 2.15
Item {
id: simple_slider
property real value: .5
property real tempValue: value
property real min_value: 0
property real max_value: 1
width: childrenRect.width
height: childrenRect.height
function linear(a, b, x) { return (x - a) / (b - a) }
function mix(a, b, x) { return a * (1 - x) + b * x }
FontMetrics { id: fontMetrics }
Rectangle {
id: main_box
width: Math.max(txt_label.width + 10, 50)
height: fontMetrics.height + 10
radius: 2
color: "darkgrey"
}
Rectangle {
id: filled_box
width: linear(min_value, max_value, tempValue) * main_box.width
height: main_box.height
radius: main_box.radius
color: "orange"
}
Text {
id: txt_label
text: tempValue.toFixed(3)
anchors.centerIn: main_box
}
MouseArea {
///property real xval: simple_slider.value
id: drag_slider
anchors.fill: parent
onPositionChanged: tempValue = get_value_from_pos(mouse.x)
onReleased: tempValue = Qt.binding(function() { return value })
function get_value_from_pos(x) {
var v = Math.min(Math.max(linear(0, main_box.width, x), 0), 1)
return mix(min_value, max_value, v)
}
}
Binding on value {
when: drag_slider.pressed
value: tempValue
restoreMode: Binding.RestoreBinding
}
}

Related

QML need to create component with bool property all list elements were within limits

I was thinking I need a component similar to ListModel, but I need to extend it to expose a readonly bool property such as "all list elements were within minimum and maximum limit" so I can do logic outside the component the determine certain things. How should I go about doing this extending a boolean property based on model's contents?
I guess naive way is to just add the qml property and do javascript loop on QML side to check all model contents but that might not be so good performance
Have you considered DelegateModel? It allows you to create "views" on your ListModel so you can control what you want to be displayed via the filterOnGroup property.
It is rather difficult to comprehend, but, in the following example, I have a ListModel containing 5 cities. When you start changing the RangeSlider the 5 cities will be filtered based on the minimum/maximum population selected. This works by updating the boolean function filter on the DelegateModel to reflect the cities that are now visible.
property var filter: model => model.pop >= rangeSlider.first.value
&& model.pop <= rangeSlider.second.value
Here's the full code snippet:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
Page {
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
Label { text: qsTr("States") }
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: DelegateModel {
id: filterDelegateModel
property int updateIndex: 0
property var filter: model => model.pop >= rangeSlider.first.value
&& model.pop <= rangeSlider.second.value
onFilterChanged: Qt.callLater(update)
model: us_states
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
onCountChanged: {
if (filterDelegateModel.updateIndex > allItems.count) filterDelegateModel.updateIndex = allItems.count;
if (filterDelegateModel.updateIndex < allItems.count) Qt.callLater(update, filterDelegateModel.updateIndex);
}
},
DelegateModelGroup {
id: visibleItems
name: "visible"
}]
filterOnGroup: "visible"
delegate: Frame {
id: frame
width: ListView.view.width - 20
background: Rectangle {
color: (frame.DelegateModel.visibleIndex & 1) ? "#f0f0f0" : "#e0e0e0"
border.color: "#c0c0c0"
}
RowLayout {
width: parent.width
Text {
text: (frame.DelegateModel.visibleIndex + 1)
color: "#808080"
}
Text {
Layout.fillWidth: true
text: model.state
}
Text {
text: qsTr("pop: %1 M").arg((pop / 1000000).toFixed(2))
}
}
}
function update(startIndex) {
startIndex = startIndex ?? 0;
if (startIndex < 0) startIndex = 0;
if (startIndex >= allItems.count) {
updateIndex = allItems.count;
return;
}
updateIndex = startIndex;
if (updateIndex === 0) {
allItems.setGroups(0, allItems.count, ["all"]);
}
for (let ts = Date.now(); updateIndex < allItems.count && Date.now() < ts + 50; updateIndex++) {
let visible = !filter || filter(allItems.get(filterDelegateModel.updateIndex).model);
if (!visible) continue;
allItems.setGroups(updateIndex, 1, ["all", "visible"]);
}
if (updateIndex < allItems.count) Qt.callLater(update, updateIndex);
}
Component.onCompleted: Qt.callLater(update)
}
}
Label { text: "Population Range" }
RangeSlider {
id: rangeSlider
Layout.fillWidth: true
from: 0
to: 100000000
first.value: 1
first.onMoved: Qt.callLater(filterDelegateModel.update)
second.value: 100000000
second.onMoved: Qt.callLater(filterDelegateModel.update)
stepSize: 1000000
}
Label { text: qsTr("Minimum %1 M").arg((rangeSlider.first.value / 1000000).toFixed(2)) }
Label { text: qsTr("Maximum %1 M").arg((rangeSlider.second.value / 1000000).toFixed(2)) }
}
ListModel {
id: us_states
ListElement { state:"California"; pop: 39350000 }
ListElement { state:"Texas"; pop: 28640000 }
ListElement { state:"New York"; pop: 8380000 }
ListElement { state:"Nevada"; pop: 3030000 }
ListElement { state:"Las Vegas"; pop: 644000 }
}
}
You can Try it Online!
I have refactored the above into a FilterDelegateModel reusable component. Feel free to check it out:
https://github.com/stephenquan/qt5-qml-toolkit
https://github.com/stephenquan/qt5-qml-toolkit/wiki/FilterDelegateModel

How to bind to a property of a repeater-generated item outside of the repeater?

I would like to be able to bind to a property of an item generated by Repeater to do something with it, e.g. to show its coordinates. For that purpose I am using itemAt() like this:
ListModel {
id: modelNodes
ListElement { name: "Banana"; x: 100; y: 200 }
ListElement { name: "Orange"; x: 150; y: 100 }
}
Repeater {
id: foo
model: modelNodes
Rectangle {
x: model.x; y: model.y
width: textBox.implicitWidth + 20
height: textBox.implicitHeight + 20
color: "red"
Drag.active: true
Text {
id: textBox
anchors.centerIn: parent
color: "white"
text: model.name + ": " + foo.itemAt(index).x
}
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}
Text {
id: moo
Binding {
target: moo
property: "text"
value: foo.itemAt(0).x + " -> " + foo.itemAt(1).x
}
}
Inside the delegate this works fine, but when I attempt to use it outside of the Repeater (i.e. to bind moo's text to it), I get the following error:
TypeError: Cannot read property 'x' of null
How to fix this?
The reason the Binding object doesn't work outside of the Repeater is because the Repeater has not constructed its items yet when the binding is being evaluated. To fix this, you can move the binding into the Component.onCompleted handler. Then just use the Qt.binding() function to do binding from javascript (docs).
Text {
Component.onCompleted: {
text = Qt.binding(function() { return foo.itemAt(0).x + ", " + foo.itemAt(1).x })
}
}
You don't.
(or more precisely, you shouldn't)
Delegates shouldn't store state or data, just display it or be able to interact with it.
In your case what you are after is the data stored in the model.
Your solution should be to modify your model in your delegates and get the data from your model if you want.
I've created a small example of what I mean:
import QtQuick 2.15
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 800
height: 640
ListModel {
id: modelNodes
ListElement { name: "Banana"; x: 50; y: 50 }
ListElement { name: "Orange"; x: 50; y: 100 }
}
Row {
anchors.centerIn: parent
spacing: 1
Repeater {
model: 2 // display 2 copy of the delegates for demonstration purposes
Rectangle {
color: "transparent"
width: 300
height: 300
border.width: 1
Repeater {
id: foo
model: modelNodes
Rectangle {
x: model.x; y: model.y
width: textBox.implicitWidth + 20
height: textBox.implicitHeight + 20
color: "red"
DragHandler {
dragThreshold: 0
}
onXChanged: model.x = x // modify model data when dragging
onYChanged: model.y = y
Text {
id: textBox
anchors.centerIn: parent
color: "white"
text: model.name + ": " + foo.itemAt(index).x
}
}
}
}
}
}
Instantiator {
model: modelNodes
delegate: Binding { // the hacky solution to the initial problem.
target: myText
property: model.name.toLowerCase() + "Point"
value: Qt.point(model.x, model.y)
}
}
Text {
id: myText
property point bananaPoint
property point orangePoint
anchors.right: parent.right
text: JSON.stringify(bananaPoint)
}
ListView {
anchors.fill: parent
model: modelNodes
delegate: Text {
text: `${model.name} - (${model.x} - ${model.y})`
}
}
}
I've used a hacky solution to your initial problem with an Instantiator of Bindings, I don't really understand the usecase so that might not be the ideal solution. Here it creates a binding for every element of your model but that's weird. If you only want data from your first row, you may want to do when: index === 0 in the Binding. I've created a third party library to get a cleaner code : https://github.com/okcerg/qmlmodelhelper
This will result in the following code for your outside Text (and allowing you to get rid of the weird Instantiator + Binding part):
Text {
readonly property var firstRowData: modelNodes.ModelHelper.map(0)
text: firstRowData.x + ", " + firstRowData.y
}
Note that my point about not storing data in delegates (or accessing them from outside) still stands for whatever solution you chose.

Holding common objects as a list in QML

I am having an architectural problem in the app I am developing in QML. Please consider the following figure:
Some facts about the application:
I need to store an array of elements names, here it is Orange, Apple and Banana.
The amount of elements is fixed and will not change at runtime.
Although there are only 1 array of elements, it should be possible to present the in different graphical forms at the same time. In the example, the elements are once represented as yellow squares, and other time as green triangles. They do not necessarily have to be shown in the same order. But the order also doesn't change at runtime.
I want to avoid unnecessary code copying, thus, wanted to use only 1 list with different graphical representations. I am having problems implementing this however.
I don't quite understand what OP wants to archive, but I guess that model is what you need.
This is a simple example of reusable model:
import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
visible: true
width: 600
height: 400
title: qsTr("Model example")
ListModel {
id: myModel;
ListElement { name: "Apple" }
ListElement { name: "Orange" }
ListElement { name: "Banana" }
}
Repeater {
model: myModel
delegate: type1
}
Repeater {
model: myModel
delegate: type2
}
ListView {
model: myModel
delegate: Text { text: name; height: 30; }
width: 100
height: 200
}
ComboBox {
width: 100
y: 200
model: myModel
}
Component {
id: type1
Canvas {
x: 100 + Math.round(Math.random() * 400)
y: Math.round(Math.random() * 100)
rotation: Math.round(Math.random() * 360)
antialiasing: true
width: 100
height: 100
onPaint: {
var ctx = getContext("2d");
ctx.fillStyle = "#00DD00";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(100, 100);
ctx.lineTo(0, 100);
ctx.fill();
}
Text {
anchors.centerIn: parent
text: name
}
}
}
Component {
id: type2
Rectangle {
x: 100 + Math.round(Math.random() * 400)
y: 200 + Math.round(Math.random() * 100)
rotation: Math.round(Math.random() * 360)
antialiasing: true
width: 100
height: 100
color: "orange"
Text {
anchors.centerIn: parent
text: name
}
}
}
}
Welp, can't comment until I have enough reputation, but can't you just use QAbstractListModel for this? Then you could use two path views that determine where the objects go. Here's an example, but you would have your own QAbstractListModel instead of the examples ListModel. The delegate would determine the shape of the items.
The reason to use QAbstractListModel over QML's ListModel is because ListModel is created runtime.

Multi item swipedelegate

Edited due to insufficient intial posting.
Hi,
thanks for your help!
You're right, I guess it is better to include the whole file, in spite of the size:
import QtQuick 2.5
import QtQuick.LocalStorage 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Controls 2.1
import QtQuick.Controls.Styles 1.4
import QtQuick.Window 2.0
import QtQuick.Controls.Material 2.1
import "./database.js" as Database
ApplicationWindow {
visible: true
width: 640
height: 480
id: appWindow
x: Screen.width / 2 - width / 2
y: Screen.height / 2 - height / 2
title: qsTr("Project Stats")
Material.theme: Material.Dark
ListModel {
id: projectModel
ListElement {
projectID: "123654"
manager: "Schneider"
sponsor: "3466"
}
}
Component {
id: projectDelegate
SwipeDelegate {
id: projectSwipeDelegate
width: parent.width
height: projectDelegateItem.implicitHeight
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
contentItem: Item {
id: projectDelegateItem
Text {
id: projectID_text
text: "Project ID: " + projectID
font.pointSize: 20
anchors.horizontalCenter: parent.horizontalCenter
font.weight: Font.Black
color: "white"
}
Text {
id: manager_text
text: 'Manager: ' + manager + " Sponsor: " + sponsor
anchors.top: projectID_text.bottom
anchors.horizontalCenter: parent.horizontalCenter
font.weight: Font.Thin
color: "lightgrey"
}
}
onClicked: {
console.log(index, projectModel.get(index).projectID)
if (swipe.complete)
projectModel.remove(index)
else {
//var component= Qt.createComponent("timepointsstackview.qml")
//var loadwin = component.createObject(appWindow)
//loadwin.selected_project = projectModel.get(index).projectID
// stackView.push(Qt.resolvedUrl("timepointsstackview.qml"), {properties: {selected_project: projectModel.get(index).projectID}})
stackView.push(component, {properties: {selected_project: projectModel.get(index).projectID}})
}
}
swipe.right: Label {
id: deleteLabel
text: qsTr("Delete")
color: "white"
verticalAlignment: Label.AlignVCenter
padding: 12
height: parent.height
anchors.right: parent.right
SwipeDelegate.onClicked: projectListView.model.remove(index)
background: Rectangle {
color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato"
}
}
}
}
Item {
Component.onCompleted: {
Database.getDatabase()
Database.getProjects()
}
}
StackView {
id: stackView
anchors.fill: parent
// Implements back key navigation
focus: true
Keys.onReleased: if (event.key === Qt.Key_Back && stackView.depth > 1) {
stackView.pop();
event.accepted = true;
}
initialItem: Item {
width: parent.width
height: parent.height
ListView {
id: projectListView
anchors.fill: parent
clip: true
model: projectModel
delegate: projectDelegate
}
}
}
onClosing: {
if (Qt.platform.os == "android") {
close.accepted = false;
// if (stack.depth > 1) stack.pop();
}
}
}
Meanwhile I already had removed the row/column stuff, which I put in to get it working somehow though I started without it.
I also experimented with implicitheight before intially posting, but sadly to no avail. The above is my current code, though putting in
height: projectDelegateItem.implicitHeight
in that spot (probabaly not the correct one or the wrong reference? Had to change it from your suggestion as I already took out the row) leads to rendering in one spot only.
Thanks for your time so far and also if you still have the patience to give me a clue where to turn the screws...
Ok, first of all:
Take warnings serious. If qml tells you, you should not try to use anchors within rows or columns, don't do it!
QML Row: Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside Row. Row will not function.
QML Column: Cannot specify top, bottom, verticalCenter, fill or centerIn anchors for items inside Column. Column will not function.
Also don't do it, if you can't see those warnings. It will mess up a lot.
A row automatically anchors all its children side by side to each other. A column does the same, just horizontraly. If you mess with it, everything breaks.
Frankly: I don't even understand why you use this strange Row/Column-Setup.
For your case it seems way better to just resort to anchoring. If you have reasons for that, why not take a grid?
Secondly: You need to specify a height for your delegate. Unfortunately it seems like, it does not calculate an implicit height.
The SwipeDelegate calculates its own implcitHeight based on the implicitHeight of its contentItem.
The problem is, that you don't assign the row (which has a proper implicitHeight) as the contentItem, but add it as a child instead.
Assigning it as contentItem would fix that for you.
Regarding your edit, and removal of the Row: The Item you use now does not calculate a implicitHeight based on its children. So you need to provide your calculation yourself.
This will set a proper height to your delegate, and your delegates won't overlap.
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
Window {
width: 1024
height: 800
visible: true
ListView {
width: 400
height: 800
model: ListModel {
ListElement { projectID: 0; manager: 'I'; sponsor: 'mom' }
ListElement { projectID: 1; manager: 'YOU'; sponsor: 'dad' }
ListElement { projectID: 1; manager: 'HE'; sponsor: 'auntie' }
}
delegate: SwipeDelegate {
id: projectSwipeDelegate
width: parent.width
// height: <--- provide a height, if the contentItem does not provide it.
contentItem: Row{ // <--- Add your content as contentItem.
id: rowProjectDelegate
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
Column {
id: column
// anchors.horizontalCenter: parent.horizontalCenter <--- Don't do that!
width: parent.width
Rectangle{ // If you don't want to have them in a column, they can't be siblings. If you want to, then you should.
height: 10
width: 250
color: "red"
Rectangle {
height: 10
width: 200
color: "blue"
}
}
Label {
id: projectID_text
text: "Project ID: " + projectID
font.pointSize: 20
font.weight: Font.Black
color: "white"
}
Label {
id: manager_text
text: 'Manager: ' + manager + " Sponsor: " + sponsor
// anchors.top: projectID_text.bottom <--- Don't do that!
font.weight: Font.Thin
color: "lightgrey"
}
}
}
}
}
}

How to catch the moment when QML Component is completely layouted?

I have a GridView in QML ApplicationWindow which should be filled
with some Items.
I place my items with JS function "placeItems".
But the problem is that when Component.onCreated signal of ApplicationWindow is called the GridView is not yet layouted.
For example, the GridView has x coordinate equal to -425 in Component.onCreated of ApplicationWindow.
If I call the same function a second later - everything is ok and GridView
has correct coordinates (=75).
I've check the Qt reference back and forth and haven't found other signals (something like onLayouted or onLayoutComplete) that may be helpful.
The question is when to call "placeItems" so the GridView in ApplicationWindow
already has correct coordinates?
UPDATE1:
To observe the bad behaviour just click File->Start after the application started. It will place the item in the correct place.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
function max (a,b) { return a>b ? a : b; }
function min (a,b) { return a<b ? a : b; }
property int sizeMin: width < height ? width : height
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
property var items: []
function centerCell(column,row) {
return {x: field.x + (column + 0.5) * cellWidth,
y: field.y + (row + 0.5) * cellHeight}
}
function placeItem(name, col, row) {
var c = centerCell(col,row)
items[name].centerX = c.x
items[name].centerY = c.y
}
function placeItems() {
placeItem ("a", 3, 3)
//placeItem ("b", 4, 4)
}
}
Component.onCompleted: field.placeItems()
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int centerX: 0
property int centerY: 0
property int margin: 5
property var cell: field.items["a"] = this
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
x: centerX - width/2
y: centerY - height/2
color: "red"
opacity: 0.5
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Start")
onTriggered: field.placeItems();
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
function placeItem(name, col, row) {
items[name].anchors.horizontalCenter = field.left;
items[name].anchors.verticalCenter = field.top;
items[name].anchors.horizontalCenterOffset = (col + 0.5) * cellWidth;
items[name].anchors.verticalCenterOffset = (row + 0.5) * cellHeight;
}
The key is to anchor the element in the grid view and then move it according to your calculations.
BTW, you know that QML has built in functions Math.min/Math.max?
EDIT
Or better yet, why not define the bindings in rectItemA directly?
Another, less hackish way to have the right behavior (don't play with Timer with layout, really, it's a bad idea):
You are defining your Rectangle as an item centered in a instance of a item belonging to your GridView. So, I use a little of your way (getting an item at the r row and the c column in the gridview), and then I reparent the Rectangle to this item. To make it centered, it is only needed to anchor it to the center of its newly bound parent.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
property int sizeMin: Math.min(width, height)
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
function cellAt(row, col) {
return itemAt(row * (dimCell + dimCellSpacing), col * (dimCell + dimCellSpacing));
}
}
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int margin: 5
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
anchors.centerIn: parent
color: "red"
opacity: 0.5
}
Component.onCompleted: {
rectItemA.parent = field.cellAt(3, 3);
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
Why don't you just delay the placeItems function so it runs with a tiny delay so that when it runs the "static" components are all completed.
Timer {
interval: 250 // might want to tune that
repeat: false
onTriggered: placeItems()
}
In a perfect world, Component.onCompleted would nest perfectly and the root item would be the last one to be emitted. But unfortunately Qt does not guarantee the order, and indeed as I did a quick test, the parent item emits (or at least responds to) onCompleted BEFORE the child items.
And if you don't want to pollute your QML file with the timer, you can actually instantiate a "helper object" from a JS function dynamically, set it to do its work and then delete itself when done. Similar to the "reparent helper" I outlined in this answer: Better way to reparent visual items in QML but rather delete itself on the timer timeout rather than in the JS function which would not give it the time to trigger.

Resources