1
votes

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();
            }
        }
    }
}
3
Hi, welcome to StackOverflow. You're much more likely to get help if you can include the code that is causing the problem you're describing.arco444
If you include your code like @arco444 mentions, we may also be able to suggest alternative approaches that may eliminate the need to depend on the order of completion (which is undefined, like ddriver has already said).Mitch
I'll try to reduce my code to a small enough exampleoutmind
The code is still pretty huge.outmind
Is there any specific reason that prevents using parent/child relation to solve the placement problem (I mean the rectangle you're trying to place inside a cel being actually its child)? Anyhow, see my answer for a solution.W.B.

3 Answers

1
votes
    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?

0
votes

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();
            }
        }
    }
}
0
votes

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.