Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. QML Property behaviour
Forum Updated to NodeBB v4.3 + New Features

QML Property behaviour

Scheduled Pinned Locked Moved Solved QML and Qt Quick
13 Posts 5 Posters 1.8k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    Mr. Dollbohrer
    wrote on last edited by Mr. Dollbohrer
    #1

    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.

    1 Reply Last reply
    0
    • fcarneyF Offline
      fcarneyF Offline
      fcarney
      wrote on last edited by
      #2

      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.

      C++ is a perfectly valid school of magic.

      1 Reply Last reply
      0
      • Pradeep P NP Offline
        Pradeep P NP Offline
        Pradeep P N
        wrote on last edited by Pradeep P N
        #3

        @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.

        Pradeep Nimbalkar.
        Upvote the answer(s) that helped you to solve the issue...
        Keep code clean.

        1 Reply Last reply
        1
        • ODБOïO Offline
          ODБOïO Offline
          ODБOï
          wrote on last edited by
          #4

          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();
          }
          
          
          1 Reply Last reply
          6
          • M Offline
            M Offline
            Mr. Dollbohrer
            wrote on last edited by
            #5

            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
                            }
                        }
                    }
                }
            
            }
            
            1 Reply Last reply
            0
            • fcarneyF Offline
              fcarneyF Offline
              fcarney
              wrote on last edited by
              #6

              @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.

              C++ is a perfectly valid school of magic.

              1 Reply Last reply
              1
              • fcarneyF Offline
                fcarneyF Offline
                fcarney
                wrote on last edited by
                #7

                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"
                                }
                            }
                        }
                    }
                }
                

                C++ is a perfectly valid school of magic.

                M 1 Reply Last reply
                1
                • fcarneyF fcarney

                  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"
                                  }
                              }
                          }
                      }
                  }
                  
                  M Offline
                  M Offline
                  Mr. Dollbohrer
                  wrote on last edited by Mr. Dollbohrer
                  #8

                  @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!!!

                  1 Reply Last reply
                  0
                  • fcarneyF Offline
                    fcarneyF Offline
                    fcarney
                    wrote on last edited by
                    #9

                    @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.

                    C++ is a perfectly valid school of magic.

                    1 Reply Last reply
                    0
                    • fcarneyF Offline
                      fcarneyF Offline
                      fcarney
                      wrote on last edited by fcarney
                      #10

                      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.

                      C++ is a perfectly valid school of magic.

                      1 Reply Last reply
                      0
                      • GrecKoG Offline
                        GrecKoG Offline
                        GrecKo
                        Qt Champions 2018
                        wrote on last edited by
                        #11

                        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 )

                        1 Reply Last reply
                        0
                        • fcarneyF Offline
                          fcarneyF Offline
                          fcarney
                          wrote on last edited by
                          #12

                          @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.

                          C++ is a perfectly valid school of magic.

                          1 Reply Last reply
                          0
                          • GrecKoG Offline
                            GrecKoG Offline
                            GrecKo
                            Qt Champions 2018
                            wrote on last edited by
                            #13

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

                            1 Reply Last reply
                            0

                            • Login

                            • Login or register to search.
                            • First post
                              Last post
                            0
                            • Categories
                            • Recent
                            • Tags
                            • Popular
                            • Users
                            • Groups
                            • Search
                            • Get Qt Extensions
                            • Unsolved