QML Property behaviour



  • Hello everyone,

    I have the following behaviour in my project that I cant understand nor find exlanations for it:

    I am able to update QML properties from C++. Also I managed to send qml data to C++. Buto I cant modify a property from inside my qml component AND from C++ at the same time - at least not as I would expect.

    In my example I have a counter in C++ that is bound to a qml property "value" displayed as a text. At first the text keeps updating fine and always shows the c++ counters value.
    But my component also contains a mousearea that, when pressed, sets the same property "value" to another number. Once I click that mouse area, all the changes from the c++ counter are ignored and from now on can only be modified from inside the qml component.

    Id expect the latest change to take effekt, no matter if it comes from the mousearea or from the c++ counter. Is my expectation wrong? Any explanations are warmly welcome.



  • Show us the code of what is going on. There are lots of ways to communicate between C++ and QML. It would be best to see both the C++ side and QML side. Something that is runnable, but small is even better.



  • @Mr-Dollbohrer
    can you show us the code at which you are facing the issue ?

    Below links might help you.

    The Property System
    Exposing Attributes of C++ Types to QML

    All the best.



  • hi @Mr.-Dollbohrer

    i guess you are breaking the binding when you change the value from QML code.
    You can create a c++ method inside your counter class, and use that method to change the value from your QML code

    // counter.qml
    Window {
        visible: true
        width: 640
        height: 480
        Text {
            id:lab
            anchors.centerIn: parent
            text: counter.cpt
        }
        Button{
            text: "set value to -666"
          //  onClicked: {lab.text = -666} 
            onClicked: {counter.setQmlValue(-666)} // c++ method
        }
    }
    //  Counter.h
    #ifndef COUNTER_H
    #define COUNTER_H
    #include <QObject>
    #include <QTimer>
    class Counter : public QObject
    {
        Q_OBJECT
    
        Q_PROPERTY(int cpt READ cpt WRITE setCpt NOTIFY cptChanged)
    
    public:
        explicit Counter(QObject *parent = nullptr);
        Q_INVOKABLE void setQmlValue(const int &qmlVal){    
            setCpt(qmlVal);
        }
    
    int cpt() const
    {
        return m_cpt;
    }
    void setCpt(int cpt)
    {
        if (m_cpt == cpt)
            return;
        m_cpt = cpt;
        emit cptChanged(m_cpt);
    }
    signals:
        void cptChanged(int);
    private :
        int m_cpt=0;
        QTimer *countTimer;
        void incrementCount(){setCpt(m_cpt+1);}
    };
    #endif // COUNTER_H
    #include "counter.h"
    //Counter.cpp
    Counter::Counter(QObject *parent) : QObject(parent)
    {
        countTimer = new QTimer(this);
        QObject::connect(countTimer,&QTimer::timeout,this,Counter::incrementCount);
        countTimer->start(1000);
    }
    //main.cpp
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "counter.h"
    
    int main(int argc, char *argv[])
    
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
        QGuiApplication app(argc, argv);
        QQmlApplicationEngine engine;
    
        Counter cpt;
        engine.rootContext()->setContextProperty("counter",&cpt);
    
        engine.load(QUrl(QStringLiteral("qrc:/counter.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
        return app.exec();
    }
    
    


  • Helly everyone and thanks a lot for your answers.

    I understand that the behaviour of my code is not following a concept/design so the problem must lie in my code. Here is a simple example that follows the design of my project and illustrates the loss of binding from c++.

    If you start the project, c++ modifies the value every second. But once you click on the qml button, there will never be any update from c++ anymore.

    //object.h
    #ifndef OBJECT_H
    #define OBJECT_H
    
    #include <QObject>
    
    class object : public QObject
    {
        Q_OBJECT
    
        Q_PROPERTY(double value READ getValue WRITE setValue NOTIFY valueChanged)
    
    private:
        double value;
    
    public:
        object(double);
        double getValue();
        void setValue(double);
    
    signals:
        void valueChanged();
    
    };
    
    #endif // OBJECT_H
    
    //object.cpp
    #include "object.h"
    
    object::object(double init)
    {
        value = init;
    }
    
    double object::getValue(){
        return value;
    }
    
    void object::setValue(double newValue){
        value = newValue;
        emit valueChanged();
    }
    
    //backend.h
    #ifndef BACKEND_H
    #define BACKEND_H
    
    #include <QObject>
    #include "object.h"
    
    class backend : public QObject
    {
    
        Q_OBJECT
    
    public:
        backend();
        QList<QObject*> objectList;
    
    public slots:
        void modifyValue();
    
    private:
        double anotherValue;
    
    };
    
    #endif // BACKEND_H
    
    //backend.cpp
    #include <QTimer>
    #include "backend.h"
    
    backend::backend()
    {
        anotherValue = 1.23;
    
        objectList.append(new object(anotherValue));
        objectList.append(new object(4.56));
        objectList.append(new object(7.89));
    
        QTimer *timer = new QTimer(this);
        QObject::connect(timer, SIGNAL(timeout()), this, SLOT(modifyValue()));
        timer->start(1000);
    }
    
    void backend::modifyValue()
    {
        anotherValue += 1;
        qobject_cast<object *>(objectList.at(0))->setValue(anotherValue);
    }
    
    //main.cpp
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include <backend.h>
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
    
        backend myBackend;
    
        QQmlApplicationEngine engine;
    
        QQmlContext *ctxt = engine.rootContext();
            ctxt->setContextProperty("objectModel", QVariant::fromValue(myBackend.objectList));
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    
    //main.qml
    import QtQuick 2.12
    import QtQuick.Window 2.12
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        Component {
            id: objectDelegate
            Item {
                height: 100
                width: 300
    
                ObjectModifier {
                    id: objectModifier
                    myValue: model.modelData.value
                }
    
            }
        }
    
        ListView {
            id: objectView
            anchors.fill: parent
            model: objectModel
            delegate: objectDelegate
            clip: true
        }
    }
    
    //ObjectModifier.qml
    import QtQuick 2.0
    import QtQuick.Layouts 1.12
    
    Item {
    
        property real myValue: 0.0
    
        anchors.fill: parent
    
        ColumnLayout{
            spacing: 2
    
            Rectangle {
                height: 50
                width: 250
                border.width: 3
                radius: 5
                Text{
                    anchors.fill: parent
                    text: "Value: "+myValue
                    horizontalAlignment: "AlignHCenter"
                    verticalAlignment: "AlignVCenter"
                    color: "black"
    
                }
            }
    
            Rectangle {
                height: 50
                width: 250
                color: "gray"
                Text{
                    anchors.fill: parent
                    text: "Modify object"
                    horizontalAlignment: "AlignHCenter"
                    verticalAlignment: "AlignVCenter"
                    color: "white"
    
                }
                MouseArea {
                    id: theClickBox
                    anchors.fill: parent
                    onClicked: {
                        myValue = myValue+0.01
                    }
                }
            }
        }
    
    }
    


  • @Mr.-Dollbohrer said in QML Property behaviour:

    If you start the project, c++ modifies the value every second. But once you click on the qml button, there will never be any update from c++ anymore.

    This is because you are breaking your binding.
    Here you define your binding:

                ObjectModifier {
                    id: objectModifier
                    myValue: model.modelData.value
                }
    

    Here you break it:

                    onClicked: {
                        myValue = myValue+0.01
                    }
    

    You are binding a new value to myValue.

    Your approach is off to begin with. Use the built in roles (search role to find info about index and modelData) that are exposed to delegates of a ListView. They are index and modelData. You could use modelData in place of myValue and it would work. However your model is suspect.

    ctxt->setContextProperty("objectModel", QVariant::fromValue(myBackend.objectList));
    

    The docs say it returns a QVariant containing a copy of the data. I am not sure this is what you want. Especially if you intend to work with this data in the backend. If you want to expose a list properly then you need to use QAbstractListModel and derive a proper list class from that.

    Here is more info on doing that. The cool part of building the list model from the abstract class is you get to define the role names yourself. You can make them a lot more relevant to your task.



  • Also, here is an example using default roles:
    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include <QVector>
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
        // simple list data
        QStringList slist;
        slist << "one";
        slist << "two";
        slist << "three";
        slist << "four";
        slist << "five";
    
        QQmlContext *ctxt = engine.rootContext();
        ctxt->setContextProperty("objectModel", slist);
    
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
        return app.exec();
    }
    

    main.qml:

    import QtQuick 2.12
    import QtQuick.Window 2.12
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("QML List Roles")
    
        ListView {
            id: objectView
            anchors.fill: parent
            model: objectModel
            delegate: objectDelegate
            clip: true
        }
    
        Component {
            id: objectDelegate
            Item {
                height: 100
                width: 300
    
                ObjectModifier {
                    id: objectModifier
                }
            }
        }
    }
    

    Your ObjectModifier.qml updated with modelData usage:

    import QtQuick 2.0
    import QtQuick.Layouts 1.12
    
    Item {
    
        anchors.fill: parent
    
        ColumnLayout {
            spacing: 2
    
            Rectangle {
                height: 50
                width: 250
                border.width: 3
                radius: 5
                Text{
                    anchors.fill: parent
                    text: "Value: "+modelData
                    horizontalAlignment: "AlignHCenter"
                    verticalAlignment: "AlignVCenter"
                    color: "black"
    
                }
            }
    
            Rectangle {
                height: 50
                width: 250
                color: "gray"
                Text{
                    anchors.fill: parent
                    text: "Modify object"
                    horizontalAlignment: "AlignHCenter"
                    verticalAlignment: "AlignVCenter"
                    color: "white"
    
                }
                MouseArea {
                    id: theClickBox
                    anchors.fill: parent
                    onClicked: {
                        modelData = modelData+"x"
                    }
                }
            }
        }
    }
    


  • @fcarney

    Thanks so much for all that great help. I am basically going through the examples copying code and trying modify it with a lot of trial and error so that I sometimes dont really know why stuff work or doesnt work.

    Thanks to your explanations I now have a clue how binding works (only one binding is allowed, what makes sense now that im thinking about it) and that it is possible to directly write to the modeldata.

    Also Ill try to dive deeper into how to expose lists to qml. My "solution" was just another part I found in an example somewhere without really knowing what other options I have and what the advantages and disadvantages are.

    Again thanks for your time and all the great hints and help!!!



  • @Mr-Dollbohrer

    I am trying to see if there is a way to do a generic templated way to expose common QML QVector types to QML from C++. But for some reason my list values do not get updated properly even though the C++ values are getting changed and the signal is getting called.
    main.cpp:

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include <QVector>
    #include <QAbstractListModel>
    #include <QDebug>
    
    template<class T>
    class VExposer : public QAbstractListModel
    {
    public:
        explicit VExposer(QObject *parent = nullptr):QAbstractListModel(parent){}
    
        int rowCount(const QModelIndex &parent = QModelIndex()) const override {
            Q_UNUSED(parent);
            return m_vector.count();
        }
    
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
            int row = index.row();
    
            if(row < 0 || row >= m_vector.count())
                return QVariant();
    
            if(role == Qt::DisplayRole)
                return m_vector[row];
            else
                return QVariant();
        }
    
        bool setData(const QModelIndex &index, const QVariant &value, int role) override {
            if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
                return false;
    
            qInfo() << index.row() << value.toString();
    
            T &item = m_vector[index.row()];
            //if (role == Qt::DisplayRole){
            if (role == Qt::EditRole){
                item = value.toDouble();
                qInfo() << m_vector[index.row()];
            }
            else
                return false;
    
            emit dataChanged(index, index, { role } );
    
            return true ;
    
        }
    
        // hacky, don't do this for real app, create a bunch of functions for getting/setting values
        QVector<T>& getVector(){return m_vector;}
    
    private:
        QVector<T> m_vector;
    };
    
    void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles){
        qInfo() << "dataChanged signal gets called!" << topLeft.row() << bottomRight.row();
    }
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;    
    
        VExposer<qreal> vex(&app);
        auto& v_vex = vex.getVector();
        v_vex << 1.0;
        v_vex << 2.0;
        v_vex << 3.0;
        v_vex << 4.0;
        v_vex << 5.0;
    
        //qInfo() << vex.rowCount();
        // proves signals work and get called
        QObject::connect(&vex, &VExposer<qreal>::dataChanged, dataChanged);
    
        QQmlContext *ctxt = engine.rootContext();
    
        // make model available
        ctxt->setContextProperty("objectModel", &vex);
    
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
        return app.exec();
    }
    //#include "main.moc"
    

    main.qml:

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Layouts 1.12
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("QML List Roles")
    
        ListView {
            id: objectView
            anchors.fill: parent
            model: objectModel
            delegate: objectDelegate
            clip: true
        }
    
        Component {
            id: objectDelegate
            Item {
    
                height: 100
                width: 300
    
                Item {
    
                    anchors.fill: parent
    
                    ColumnLayout {
                        spacing: 2
    
                        Rectangle {
                            height: 50
                            width: 250
                            border.width: 3
                            radius: 5
                            Text{
                                anchors.fill: parent
                                text: "Value: %1".arg(display)
                                horizontalAlignment: "AlignHCenter"
                                verticalAlignment: "AlignVCenter"
                                color: "black"
    
                            }
                        }
    
                        Rectangle {
                            height: 50
                            width: 250
                            color: "gray"
                            Text{
                                anchors.fill: parent
                                text: "Modify object"
                                horizontalAlignment: "AlignHCenter"
                                verticalAlignment: "AlignVCenter"
                                color: "white"
    
                            }
                            MouseArea {
                                id: theClickBox
                                anchors.fill: parent
                                onClicked: {
                                    //display = display+0.01 // updates display
                                    edit = display+0.01 // changes value in variable, but does not update display
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    I know I cannot template a class that has the Q_OBJECT macro in it. But you can template a class that derives from a QObject class. So I figured QAbstractListModel would be a good candidate. Everything works except the signal does not seem to update the ListView in QML properly.

    Does anyone know if I am missing something important here? I would love for this template to work as it would save time writing boiler plate code to expose vectors as models.



  • Dang it. I just realized value.toDouble() ruins the ability to template this... Darn, it was worth a try.
    Maybe not, I can check types. It will just take more work.


  • Qt Champions 2018

    If you want a generic QAbstractItemModel from a list of objects deriving QObject, there is : QQmlObjectListModel from http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models (some slides about it here : https://docs.google.com/presentation/d/13pkRav2Fks_AKTXfKtGTyBuc_RmkZGiDRQZTr4usQFk/pub?start=false&loop=false&delayms=3000 )



  • @GrecKo said in QML Property behaviour:

    If you want a generic QAbstractItemModel from a list of objects deriving QObject

    I saw that, but I was hoping to make a generic one for just types like double, int, QString. I will create a new post as I think I have one most of the way there, but for some reason it won't update the list values even though the signal is getting called.


  • Qt Champions 2018

    Ah yes, I misread.
    Indeed, it should be doable with QVariant::value<T>() and QVariant::fromValue(const T &value)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.