Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Triggering QML animation for property changes

Tags:

c++

qt

qml

I'm implementing an app with some CPU intensive code. This code must update the interface designed in QML to display its results.

The interface is made of a grid and a few blocks with some contents inside. The Grid is fully C++ implemented, and the Block is only QML.

Block.qml

import QtQuick 2.3

Item {
    id: root

    // lot of non-related stuff here.

    Behavior on x {
        NumberAnimation {
            duration: 1000
            easing.type: Easing.Linear

            onRunningChanged: {
                console.log("behavior on x");
            }
        }
    }

    Behavior on y {
        NumberAnimation {
            duration: 1000
            easing.type: Easing.Linear

            onRunningChanged: {
                console.log("behavior on y");
            }
        }
    }
}

Each of the blocks are created in C++ and positioned accordingly.

The problem is that some times I must move the blocks and I would like to trigger an animation. To accomplish this I've included the Behavior elements as you have probably noticed.

But when I change the position of the block from C++ code, the behavior is not triggered. The Block simply changes its position, without any animation.

The code I'm using to change the block position is:

void Grid::setBlockPosition(QQuickItem *block, unsigned i, unsigned j)
{
    float x, y;
    x = GRID_MARGIN_LEFT + j * this->_blockSize;
    y = GRID_MARGIN_TOP + (GRID_ROWS - i - 1) * this->_blockSize;
    block->setPosition(QPointF(x, y));
}

I've tried also changing x and then y instead of changing both at the same time, but it gave the same result.

How can I trigger the behavior on x and y properties from C++?

like image 550
Vinícius Gobbo A. de Oliveira Avatar asked Oct 25 '25 12:10

Vinícius Gobbo A. de Oliveira


1 Answers

This is a good question. I took a look into the source of QQuickItem, and setX(), setY() and setPosition() all have very similar code paths, with all of them emitting geometryChanged(). You might think that that would be enough, but it turns out that it's not:

You should always use QObject::setProperty(), QQmlProperty or QMetaProperty::write() to change a QML property value, to ensure the QML engine is made aware of the property change. For example, say you have a custom type PushButton with a buttonText property that internally reflects the value of a m_buttonText member variable. Modifying the member variable directly [...] is not a good idea [...].

Since the value is changed directly, this bypasses Qt's meta-object system and the QML engine is not made aware of the property change. This means property bindings to buttonText would not be updated, and any onButtonTextChanged handlers would not be called.

Using an example specific to your code: if you change the x property of your block in QML, for example, the meta-object system is aware of the change and the Behavior gets notified of it and sets the property directly, in increments, until it reaches its target. When you set the property directly yourself, the Behavior has no idea that anything has changed, and so it does nothing.

So, you should call setProperty("x", x) and setProperty("y", y):

#include <QApplication>
#include <QtQuick>
#include <QtQml>

class Grid : public QQuickItem
{
    Q_OBJECT
public:
    Grid(QQuickItem *parent) {
        setParentItem(parent);
    }

public slots:
    void addBlocks() {
        QQmlComponent *blockComponent = new QQmlComponent(qmlEngine(parentItem()), "Block.qml");
        for (int i = 0; i < 4; ++i) {
            QQuickItem *block = qobject_cast<QQuickItem*>(blockComponent->create());
            Q_ASSERT(!blockComponent->isError());
            block->setParentItem(this);
            block->setProperty("x", i * 100);
            block->setProperty("y", i * 100);
            mBlocks.append(block);
        }
    }

private:
    QList<QQuickItem*> mBlocks;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    QQuickItem *contentItem = engine.rootObjects().first()->property("contentItem").value<QQuickItem*>();
    Grid *grid = new Grid(contentItem);
    grid->addBlocks();

    return a.exec();
}

#include "main.moc"

I've added this information to the detailed description of QQuickItem's documentation; hopefully others see this if they run into this problem.

like image 92
Mitch Avatar answered Oct 28 '25 02:10

Mitch



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!