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. Binding C++ properties exposed to QML to dynamically created QML objects
QtWS25 Last Chance

Binding C++ properties exposed to QML to dynamically created QML objects

Scheduled Pinned Locked Moved Solved QML and Qt Quick
qmlbindingcreateobjectlistmodelproperties
27 Posts 3 Posters 14.8k Views
  • 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.
  • O Obi-Wan

    Hi!

    I have a C++ backend that receives messages over TCP. The message contains information about some number of boxes. This number is not known prior to the first message is received, but remains the same in one complete run of the application. In C++ I create a QList<QVariant> that I expose to QML through the use of Q_PROPERTY, and this list contains information about the boxes received in the message.

    In QML I use the associated onPropertyChanged slot to create objects when the C++ member changes. The first time I receive the message I loop through the array and create a custom Box component by using createComponent and createObject. These objects are stored in a ListModel.

    On subsequent invokations of onPropertyChanged I can loop through the objects in the ListModel and access the objects from there. I can set properties on these objects by doing:

    for (var i = 0; i < boxListModel.count; ++i)
    {
          var box = boxListModel.get(i).obj // Gives me access to the object
          box.pos_x = backEnd.array[i].x // Sets the pos_x property of the dynamically created Box to whatever the QList<QVariant>'s x property currently is.
    }
    

    This allows me to manually set the properties of the dynamically created objects, and essentially this is exactly the behavior I want.

    But what I really want to do is to bind the properties of the Box objects in the ListModel to the data in the QList<QVariant> in C++. I.e., I don't want to set box.pos_x = backEnd.array.x manually, but rather have it update whenever the property changes.

    How do I accomplish this?

    I tried doing:

    for (var i = 0; i < boxListModel.count; ++i) 
    {
         var box = boxListModel.get(i).obj // Gives me access to the object
         box.pos_x = Qt.binding(function() {return backend.array[i].x}) // Binds the backend x to the current box's pos_x
    }
    

    But this doesn't work. I'm assuming because the box object goes out of scope? (I'm new to js so I'm not sure how that works).

    I've also looked at the Binding type, but can't figure out how to use it. There is an example here, but I haven't been able to figure out how to use that it in my scenario.

    E Offline
    E Offline
    Eeli K
    wrote on last edited by
    #7

    @Obi-Wan said in Binding C++ properties exposed to QML to dynamically created QML objects:

    backEnd.array.x

    You didn't tell what this is, I presume the array is QList<QVariant> which you mentioned, but what is x?

    But this doesn't work. I'm assuming because the box object goes out of scope?

    I don't think so; rather it's not automatically updated if x isn't a property with the "changed" signal. And how can it be if the array is just QList<QVariant>? Then x is just a static value in the binding, the engine can't know when it's changed.

    1 Reply Last reply
    0
    • GrecKoG GrecKo

      Why not just use the QVariantList as a model in QML ?

      O Offline
      O Offline
      Obi-Wan
      wrote on last edited by
      #8

      @GrecKo I don't quite understand what this means, could you elaborate? :)

      @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

      You didn't tell what this is, I presume the array is QList<QVariant> which you mentioned, but what is x?

      I left out the details because I assumed the answer would be more of a conceptual one. The QVariant in the QList is a QVector2D in C++, which is a vector2d in QML, so the .x is just the way to access the x property of the vector2d. The array is the representation of the QList in javascript as a javascript array.

      @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

      I don't think so; rather it's not automatically updated if x isn't a property with the "changed" signal. And how can it be if the array is just QList<QVariant>? Then x is just a static value in the binding, the engine can't know when it's changed.

      The QList<QVariant> property emits a signal every time the C++ QVector2D changes, and I access this slot in QML. So the engine knowns when the array changes. It doesn't know when x changes directly, but it knows when the array changes and I then want it to use its current x value through a binding rather than calling it manually.

      Basically I have two properties. One is exposed from C++ and one belongs to a QML object that is created after the program has started. I want the property of the QML object to change whenever the C++ property changes.

      Not sure if I'm making it clearer or the opposite, but I'm thankful for any and all help! :)

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

        I don't know what you need but in my mind I would do something like that :

        Repeater {
            id: repeater
            model: vectorList
            Rectangle {
                x: modelData.x
                y: modelData.y
                width: 5
                height: 5
                color: "red"
            }
        }
        

        Don't dynamically create the components myself, let Repeater do it

        1 Reply Last reply
        1
        • O Obi-Wan

          @GrecKo I don't quite understand what this means, could you elaborate? :)

          @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

          You didn't tell what this is, I presume the array is QList<QVariant> which you mentioned, but what is x?

          I left out the details because I assumed the answer would be more of a conceptual one. The QVariant in the QList is a QVector2D in C++, which is a vector2d in QML, so the .x is just the way to access the x property of the vector2d. The array is the representation of the QList in javascript as a javascript array.

          @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

          I don't think so; rather it's not automatically updated if x isn't a property with the "changed" signal. And how can it be if the array is just QList<QVariant>? Then x is just a static value in the binding, the engine can't know when it's changed.

          The QList<QVariant> property emits a signal every time the C++ QVector2D changes, and I access this slot in QML. So the engine knowns when the array changes. It doesn't know when x changes directly, but it knows when the array changes and I then want it to use its current x value through a binding rather than calling it manually.

          Basically I have two properties. One is exposed from C++ and one belongs to a QML object that is created after the program has started. I want the property of the QML object to change whenever the C++ property changes.

          Not sure if I'm making it clearer or the opposite, but I'm thankful for any and all help! :)

          E Offline
          E Offline
          Eeli K
          wrote on last edited by
          #10

          @Obi-Wan No need to elaborate, I presumed correctly that you've got QList<QVariant> in C++ and "array" in QML is that list object. But in "backend.array.x" x would be a member of QList, which it is not. It should be something like array[index].x to work. And even then x in C++ should be a property (Q_PROPERTY) for it to work automatically in bindings so that box.pos_x would be bind to it and updated automatically when the C++ x value is changed. QVector2D doesn't have properties (and cannot have because it's not a QObject). You must write your own class if you want that.

          O 1 Reply Last reply
          0
          • E Eeli K

            @Obi-Wan No need to elaborate, I presumed correctly that you've got QList<QVariant> in C++ and "array" in QML is that list object. But in "backend.array.x" x would be a member of QList, which it is not. It should be something like array[index].x to work. And even then x in C++ should be a property (Q_PROPERTY) for it to work automatically in bindings so that box.pos_x would be bind to it and updated automatically when the C++ x value is changed. QVector2D doesn't have properties (and cannot have because it's not a QObject). You must write your own class if you want that.

            O Offline
            O Offline
            Obi-Wan
            wrote on last edited by Obi-Wan
            #11

            @Eeli-K You are absolutely right, backend.array.x should be backend.array[i].x in both cases. That what a mistake in my example code. I edited the original question.

            The second part has me confused however. I don't understand why I should have to bind the C++ QVector2D.x specifically? In my head (which again might be confused) it should be enough to create a property of the QList, and all the data in the QList should be available in QML.

            If I have a single QVector2D in C++ and use Q_PROPERTY to expose that to QML, I can indeed bind the x and y components of that vector to a property of any static QML object. I do not have to create a property of the x and y components in that case, why is that different now?

            I haven't tried, but I expect I would have the same problem if I used a single QVector2D to expose some data to QML and then dynamically created one single box. That is, I think the problems comes from the QML object being created some time after application start, and that the bindings therefore need to be handled in a special way.

            @GrecKo Thanks for the tip! How do you use the Repeater to create objects on the go?

            In the documentation it says:

            The Repeater type creates all of its delegate items when the repeater is first created. This can be inefficient if there are a large number of delegate items and not all of the items are required to be visible at the same time. If this is the case, consider using other view types like ListView (which only creates delegate items when they are scrolled into view) or use the Dynamic Object Creation methods to create items as they are required.

            The Dynamic Object Creation methods they refer to are the ones I am using :)

            E GrecKoG 3 Replies Last reply
            0
            • O Obi-Wan

              @Eeli-K You are absolutely right, backend.array.x should be backend.array[i].x in both cases. That what a mistake in my example code. I edited the original question.

              The second part has me confused however. I don't understand why I should have to bind the C++ QVector2D.x specifically? In my head (which again might be confused) it should be enough to create a property of the QList, and all the data in the QList should be available in QML.

              If I have a single QVector2D in C++ and use Q_PROPERTY to expose that to QML, I can indeed bind the x and y components of that vector to a property of any static QML object. I do not have to create a property of the x and y components in that case, why is that different now?

              I haven't tried, but I expect I would have the same problem if I used a single QVector2D to expose some data to QML and then dynamically created one single box. That is, I think the problems comes from the QML object being created some time after application start, and that the bindings therefore need to be handled in a special way.

              @GrecKo Thanks for the tip! How do you use the Repeater to create objects on the go?

              In the documentation it says:

              The Repeater type creates all of its delegate items when the repeater is first created. This can be inefficient if there are a large number of delegate items and not all of the items are required to be visible at the same time. If this is the case, consider using other view types like ListView (which only creates delegate items when they are scrolled into view) or use the Dynamic Object Creation methods to create items as they are required.

              The Dynamic Object Creation methods they refer to are the ones I am using :)

              E Offline
              E Offline
              Eeli K
              wrote on last edited by
              #12

              @Obi-Wan Maybe it was me who was confused. You wrote "In C++ I create a QList<QVariant> that I expose to QML through the use of Q_PROPERTY, and this list contains information about the boxes received in the message." I interpreted it to mean that there's one list which has one element per one QML box object. Correct me if I was wrong. But in any case, if you have a binding

              x: y
              

              or the dynamic equivalent

              x = Qt.binding(function() {return y})
              

              y must be a property if you want it to evaluated automatically every time when y is changed. I think it this way: it's not just an ordinary function but the QML engine kind of keeps the whole function and every property which is inside it so that when one property inside that function changes the function can be evaluated anew. If y is not a property in C++ but just a normal C++ object or member, then the QML engine can't know when it's changed.

              I'm not actually sure how deeply the binding can reach different things in QML, but when it comes to C++ objects it's quite clear when you think about it. In C++ you use the signal/slot for dynamic messages about changes, but you have always emit signals manually, or they have to be emitted by the library (written in the C++ code) for the connections to work. There's just no way anyone can know that something inside a QList is changed unless a signal is sent. There's no such signal in QList, and QVector2D doesn't have such signals. But when you create a Q_PROPERTY, for example:

              Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
              

              and then emit the signal when that member value is changed, all listeners who have been connected to the colorChanged signal are notified. The QML engine uses that possibility, using the meta object system, and can listen to property changes of C++ objects. When such a property is used inside a binding the whole function is evaluated when the C++ property is changed and the corresponding signal is emitted.

              You wrote:

              If i have a single QVector2D in C++ and use Q_PROPERTY to expose that to QML, I can indeed bind the x and y components of that vector to a property of any static QML object. I do not have to create a property of the x and y components in that case, why is that different now?

              But you can't do that to achieve automatic binding evaluation. You can use x and y, yes, but the binding isn't evaluated automatically when the values are changed. The function is evaluated only once, with the values they have when the binding is made. If you actually have tested it and it works as you supposed, tell me I'm wrong.

              O 1 Reply Last reply
              0
              • O Obi-Wan

                @Eeli-K You are absolutely right, backend.array.x should be backend.array[i].x in both cases. That what a mistake in my example code. I edited the original question.

                The second part has me confused however. I don't understand why I should have to bind the C++ QVector2D.x specifically? In my head (which again might be confused) it should be enough to create a property of the QList, and all the data in the QList should be available in QML.

                If I have a single QVector2D in C++ and use Q_PROPERTY to expose that to QML, I can indeed bind the x and y components of that vector to a property of any static QML object. I do not have to create a property of the x and y components in that case, why is that different now?

                I haven't tried, but I expect I would have the same problem if I used a single QVector2D to expose some data to QML and then dynamically created one single box. That is, I think the problems comes from the QML object being created some time after application start, and that the bindings therefore need to be handled in a special way.

                @GrecKo Thanks for the tip! How do you use the Repeater to create objects on the go?

                In the documentation it says:

                The Repeater type creates all of its delegate items when the repeater is first created. This can be inefficient if there are a large number of delegate items and not all of the items are required to be visible at the same time. If this is the case, consider using other view types like ListView (which only creates delegate items when they are scrolled into view) or use the Dynamic Object Creation methods to create items as they are required.

                The Dynamic Object Creation methods they refer to are the ones I am using :)

                E Offline
                E Offline
                Eeli K
                wrote on last edited by
                #13

                @Obi-Wan I just noticed that there happens an implicit data type conversion between C++ and QML: QVector2D becomes vector2D. It doesn't have properties, it has members x and y. If x or y are used in bindings they don't trigger automatic evaluation. And when I try this (in main.cpp):

                auto vec = QVector2D{3.4, 5.6};
                QQmlApplicationEngine engine;
                engine.rootContext()->setContextProperty("vector", vec);
                vec.setX(6.7);
                

                QML side gets only the first set x value 3.4, not 6.7. So it handles only a copy of the original. Your list probably becomes a javascript array which have vector2d elements and which is only a copy of the original C++ list.

                So, in short, your problem isn't in dynamic creation of objects, it's in the C++ data. You just have to use your current method which worked for you or write much more complicated C++ code.

                1 Reply Last reply
                0
                • E Eeli K

                  @Obi-Wan Maybe it was me who was confused. You wrote "In C++ I create a QList<QVariant> that I expose to QML through the use of Q_PROPERTY, and this list contains information about the boxes received in the message." I interpreted it to mean that there's one list which has one element per one QML box object. Correct me if I was wrong. But in any case, if you have a binding

                  x: y
                  

                  or the dynamic equivalent

                  x = Qt.binding(function() {return y})
                  

                  y must be a property if you want it to evaluated automatically every time when y is changed. I think it this way: it's not just an ordinary function but the QML engine kind of keeps the whole function and every property which is inside it so that when one property inside that function changes the function can be evaluated anew. If y is not a property in C++ but just a normal C++ object or member, then the QML engine can't know when it's changed.

                  I'm not actually sure how deeply the binding can reach different things in QML, but when it comes to C++ objects it's quite clear when you think about it. In C++ you use the signal/slot for dynamic messages about changes, but you have always emit signals manually, or they have to be emitted by the library (written in the C++ code) for the connections to work. There's just no way anyone can know that something inside a QList is changed unless a signal is sent. There's no such signal in QList, and QVector2D doesn't have such signals. But when you create a Q_PROPERTY, for example:

                  Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
                  

                  and then emit the signal when that member value is changed, all listeners who have been connected to the colorChanged signal are notified. The QML engine uses that possibility, using the meta object system, and can listen to property changes of C++ objects. When such a property is used inside a binding the whole function is evaluated when the C++ property is changed and the corresponding signal is emitted.

                  You wrote:

                  If i have a single QVector2D in C++ and use Q_PROPERTY to expose that to QML, I can indeed bind the x and y components of that vector to a property of any static QML object. I do not have to create a property of the x and y components in that case, why is that different now?

                  But you can't do that to achieve automatic binding evaluation. You can use x and y, yes, but the binding isn't evaluated automatically when the values are changed. The function is evaluated only once, with the values they have when the binding is made. If you actually have tested it and it works as you supposed, tell me I'm wrong.

                  O Offline
                  O Offline
                  Obi-Wan
                  wrote on last edited by
                  #14

                  @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                  Maybe it was me who was confused. You wrote "In C++ I create a QList<QVariant> that I expose to QML through the use of Q_PROPERTY, and this list contains information about the boxes received in the message." I interpreted it to mean that there's one list which has one element per one QML box object. Correct me if I was wrong.

                  No, this is correct.

                  @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                  There's just no way anyone can know that something inside a QList is changed unless a signal is sent

                  Agreed. But there is a signal being sent whenever any part of the QList changes. In C++ I manually send a signal whenever the QList is updated with new data in order make NOTIFY work properly. And this does indeed work. Every time I change the QList in C++ a slot in QML is triggered, and I can do stuff with the updated data in the array.

                  @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                  You can use x and y, yes, but the binding isn't evaluated automatically when the values are changed. The function is evaluated only once, with the values they have when the binding is made.

                  This is exactly what seems to be happening when I try to bind to dynamic objects.

                  @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                  If you actually have tested it and it works as you supposed, tell me I'm wrong.

                  But yes, with a static object (not sure if static is the right word, but one that I declare in QML with an id, and that I know will be there from the start), I can simply bind any property to the QVector2D's x and y members.

                  Say I have a C++ class called "backend". It has a single QVector2D that I expose to QML through Q_PROPERTY and that has a manually defined signal that is emitted whenever the QVector2D changes. This QVector2D has the name "box". I am able to bind the text property of a static object in QML with the x member of the QVector2D as such:

                  Text 
                  {
                        text: backend.box.x // This binding works
                  }
                  

                  I don't have the code available for the weekend, but I will have to try some more come Monday. Thanks for all input in any case! :)

                  E 1 Reply Last reply
                  0
                  • O Obi-Wan

                    @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                    Maybe it was me who was confused. You wrote "In C++ I create a QList<QVariant> that I expose to QML through the use of Q_PROPERTY, and this list contains information about the boxes received in the message." I interpreted it to mean that there's one list which has one element per one QML box object. Correct me if I was wrong.

                    No, this is correct.

                    @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                    There's just no way anyone can know that something inside a QList is changed unless a signal is sent

                    Agreed. But there is a signal being sent whenever any part of the QList changes. In C++ I manually send a signal whenever the QList is updated with new data in order make NOTIFY work properly. And this does indeed work. Every time I change the QList in C++ a slot in QML is triggered, and I can do stuff with the updated data in the array.

                    @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                    You can use x and y, yes, but the binding isn't evaluated automatically when the values are changed. The function is evaluated only once, with the values they have when the binding is made.

                    This is exactly what seems to be happening when I try to bind to dynamic objects.

                    @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                    If you actually have tested it and it works as you supposed, tell me I'm wrong.

                    But yes, with a static object (not sure if static is the right word, but one that I declare in QML with an id, and that I know will be there from the start), I can simply bind any property to the QVector2D's x and y members.

                    Say I have a C++ class called "backend". It has a single QVector2D that I expose to QML through Q_PROPERTY and that has a manually defined signal that is emitted whenever the QVector2D changes. This QVector2D has the name "box". I am able to bind the text property of a static object in QML with the x member of the QVector2D as such:

                    Text 
                    {
                          text: backend.box.x // This binding works
                    }
                    

                    I don't have the code available for the weekend, but I will have to try some more come Monday. Thanks for all input in any case! :)

                    E Offline
                    E Offline
                    Eeli K
                    wrote on last edited by
                    #15

                    @Obi-Wan

                    text: backend.box.x // This binding works

                    OK. This probably works because backend.box.x is re-evaluated every time the boxChanged (or whatever the name is) signal is sent. Then behind the scenes backend.box() (or whatever the getter name is) function is called and you receive a new copy of the 2d vector. Therefore the x is correct. Maybe I now understand better: x doesn't need to be a property, it's enough that the expression is evaluated when x is changed. I have to test what it means in practice.

                    O 1 Reply Last reply
                    0
                    • E Eeli K

                      @Obi-Wan

                      text: backend.box.x // This binding works

                      OK. This probably works because backend.box.x is re-evaluated every time the boxChanged (or whatever the name is) signal is sent. Then behind the scenes backend.box() (or whatever the getter name is) function is called and you receive a new copy of the 2d vector. Therefore the x is correct. Maybe I now understand better: x doesn't need to be a property, it's enough that the expression is evaluated when x is changed. I have to test what it means in practice.

                      O Offline
                      O Offline
                      Obi-Wan
                      wrote on last edited by
                      #16

                      @Eeli-K It very well might be that you are right about the problem being something related to C++! I created a working example where I bind properties of dynamic objects using QML only, and it works! It does however use a method explicitly warned against in the documentation, so I think I should find an alternative. The documentation says about ListModel.get that():

                      Warning: The returned object is not guaranteed to remain valid. It should not be used in property bindings.

                      which is what I'm doing now.

                      I will have to check again on Monday why this doesn't work in the original code, and if the problem lies with the C++ property!

                      main.qml

                      import QtQuick 2.6
                      import QtQuick.Window 2.2
                      import QtQuick.Controls 2.1
                      
                      Window {
                          visible: true
                          width: 640
                          height: 480
                      
                          ListModel
                          {
                              id: listModel
                          }
                      
                          Column
                          {
                              id: mainCol
                              anchors.centerIn: parent
                              spacing: 10
                      
                              Button
                              {
                                  text: "Click to create object"
                      
                                  onClicked:
                                  {
                                      var component = Qt.createComponent("Box.qml");
                                      var obj = component.createObject(mainCol);
                      
                                      listModel.append({"obj": obj})
                                  }
                              }
                      
                              TextField
                              {
                                  id: inputTxt
                              }
                      
                              Button
                              {
                                  text: "Click to bind properties"
                      
                                  onClicked:
                                  {
                                      for (var i = 0; i < listModel.count; ++i)
                                      {
                                          var dynObj = listModel.get(i).obj;
                                          dynObj.boxTxt = Qt.binding(function() {return inputTxt.text});
                                      }
                                  }
                              }
                          }
                      }
                      

                      Box.qml

                      import QtQuick 2.0
                      
                      Rectangle
                      {
                          property alias boxTxt: txt.text
                      
                          width: 100
                          height: 100
                          color: "lightblue"
                      
                          Text
                          {
                              id: txt
                              text: "Dynamic object"
                          }
                      }
                      
                      
                      E 2 Replies Last reply
                      0
                      • O Obi-Wan

                        @Eeli-K It very well might be that you are right about the problem being something related to C++! I created a working example where I bind properties of dynamic objects using QML only, and it works! It does however use a method explicitly warned against in the documentation, so I think I should find an alternative. The documentation says about ListModel.get that():

                        Warning: The returned object is not guaranteed to remain valid. It should not be used in property bindings.

                        which is what I'm doing now.

                        I will have to check again on Monday why this doesn't work in the original code, and if the problem lies with the C++ property!

                        main.qml

                        import QtQuick 2.6
                        import QtQuick.Window 2.2
                        import QtQuick.Controls 2.1
                        
                        Window {
                            visible: true
                            width: 640
                            height: 480
                        
                            ListModel
                            {
                                id: listModel
                            }
                        
                            Column
                            {
                                id: mainCol
                                anchors.centerIn: parent
                                spacing: 10
                        
                                Button
                                {
                                    text: "Click to create object"
                        
                                    onClicked:
                                    {
                                        var component = Qt.createComponent("Box.qml");
                                        var obj = component.createObject(mainCol);
                        
                                        listModel.append({"obj": obj})
                                    }
                                }
                        
                                TextField
                                {
                                    id: inputTxt
                                }
                        
                                Button
                                {
                                    text: "Click to bind properties"
                        
                                    onClicked:
                                    {
                                        for (var i = 0; i < listModel.count; ++i)
                                        {
                                            var dynObj = listModel.get(i).obj;
                                            dynObj.boxTxt = Qt.binding(function() {return inputTxt.text});
                                        }
                                    }
                                }
                            }
                        }
                        

                        Box.qml

                        import QtQuick 2.0
                        
                        Rectangle
                        {
                            property alias boxTxt: txt.text
                        
                            width: 100
                            height: 100
                            color: "lightblue"
                        
                            Text
                            {
                                id: txt
                                text: "Dynamic object"
                            }
                        }
                        
                        
                        E Offline
                        E Offline
                        Eeli K
                        wrote on last edited by Eeli K
                        #17

                        @Obi-Wan

                        It does however use a method explicitly warned against in the documentation, so I think I should find an alternative. The documentation says about ListModel.get that():
                        Warning: The returned object is not guaranteed to remain valid. It should not be used in property bindings.

                        I think it rather means using it in the right side of the binding. But I may be wrong. After all, I have demonstrably been wrong sometimes :)

                        But here is my code:
                        main.qml:

                        import QtQuick 2.7
                        import QtQuick.Controls 2.0
                        import QtQuick.Layouts 1.3
                        
                        ApplicationWindow {
                            visible: true
                            width: 640
                            height: 480
                            title: qsTr("Hello World")
                        
                            ColumnLayout {
                                id: layout
                                anchors.fill:parent
                        
                                ListModel {
                                    id: boxListModel
                                }
                        
                                Button {
                                    text: "create qml model"
                                    onClicked: {
                                        for (var i = 0; i < 1; ++i)
                                        {
                                            var component = Qt.createComponent("Box.qml");
                                            var obj = component.createObject(layout);
                                            boxListModel.append({"obj": obj})
                                            obj.boxTxt = Qt.binding(function() {return backend.proplist[i].x})
                                        }
                                    }
                                }
                        
                                Button {
                                    id: button1
                                    text: "change backend"
                                    onClicked: {
                                        backend.changeList()
                                    }
                                }
                            }
                        }
                        

                        backend.changeList() which you should be able to add to your backend class easily:

                        void MyClass::changeList() {
                            float f = m_list.at(0).value<QVector2D>().x() + 1;
                            m_list.clear();
                            m_list.append(QVariant(QVector2D{f, f}));
                            emit arrayChanged();
                        }
                        

                        (The m_list is supposed to have at least one QVector2D in it.)

                        This doesn't work, but if you change

                        obj.boxTxt = Qt.binding(function() {return backend.proplist[i].x})
                        

                        to

                        obj.boxTxt = Qt.binding(function() {return backend.proplist[0].x})
                        

                        it works!

                        1 Reply Last reply
                        1
                        • O Obi-Wan

                          @Eeli-K It very well might be that you are right about the problem being something related to C++! I created a working example where I bind properties of dynamic objects using QML only, and it works! It does however use a method explicitly warned against in the documentation, so I think I should find an alternative. The documentation says about ListModel.get that():

                          Warning: The returned object is not guaranteed to remain valid. It should not be used in property bindings.

                          which is what I'm doing now.

                          I will have to check again on Monday why this doesn't work in the original code, and if the problem lies with the C++ property!

                          main.qml

                          import QtQuick 2.6
                          import QtQuick.Window 2.2
                          import QtQuick.Controls 2.1
                          
                          Window {
                              visible: true
                              width: 640
                              height: 480
                          
                              ListModel
                              {
                                  id: listModel
                              }
                          
                              Column
                              {
                                  id: mainCol
                                  anchors.centerIn: parent
                                  spacing: 10
                          
                                  Button
                                  {
                                      text: "Click to create object"
                          
                                      onClicked:
                                      {
                                          var component = Qt.createComponent("Box.qml");
                                          var obj = component.createObject(mainCol);
                          
                                          listModel.append({"obj": obj})
                                      }
                                  }
                          
                                  TextField
                                  {
                                      id: inputTxt
                                  }
                          
                                  Button
                                  {
                                      text: "Click to bind properties"
                          
                                      onClicked:
                                      {
                                          for (var i = 0; i < listModel.count; ++i)
                                          {
                                              var dynObj = listModel.get(i).obj;
                                              dynObj.boxTxt = Qt.binding(function() {return inputTxt.text});
                                          }
                                      }
                                  }
                              }
                          }
                          

                          Box.qml

                          import QtQuick 2.0
                          
                          Rectangle
                          {
                              property alias boxTxt: txt.text
                          
                              width: 100
                              height: 100
                              color: "lightblue"
                          
                              Text
                              {
                                  id: txt
                                  text: "Dynamic object"
                              }
                          }
                          
                          
                          E Offline
                          E Offline
                          Eeli K
                          wrote on last edited by
                          #18

                          @Obi-Wan (Had to finish the previous post in a hurry, continuing...) So the problem is not in dynamic objects or C++ but that the property binding function can't handle variables taken from the immediate function context, in this case the loop counter. However, I got this working:

                          var f = function(indx) {return function(){return backend.array[indx].x}}
                          obj.boxTxt = Qt.binding(f(i))
                          

                          Don't ask me why.

                          O E 2 Replies Last reply
                          1
                          • E Eeli K

                            @Obi-Wan (Had to finish the previous post in a hurry, continuing...) So the problem is not in dynamic objects or C++ but that the property binding function can't handle variables taken from the immediate function context, in this case the loop counter. However, I got this working:

                            var f = function(indx) {return function(){return backend.array[indx].x}}
                            obj.boxTxt = Qt.binding(f(i))
                            

                            Don't ask me why.

                            O Offline
                            O Offline
                            Obi-Wan
                            wrote on last edited by
                            #19

                            @Eeli-K Very interesting! I will give this a go tomorrow and report back! :)

                            1 Reply Last reply
                            0
                            • E Eeli K

                              @Obi-Wan (Had to finish the previous post in a hurry, continuing...) So the problem is not in dynamic objects or C++ but that the property binding function can't handle variables taken from the immediate function context, in this case the loop counter. However, I got this working:

                              var f = function(indx) {return function(){return backend.array[indx].x}}
                              obj.boxTxt = Qt.binding(f(i))
                              

                              Don't ask me why.

                              E Offline
                              E Offline
                              Eeli K
                              wrote on last edited by
                              #20

                              @Eeli-K said in Binding C++ properties exposed to QML to dynamically created QML objects:

                              Don't ask me why.

                              I asked myself. This seems to be an example of a closure and functional programming in javascript. f() returns a function where indx is bound, it has a new value in each invocation of f(), and therefore the inner anonymous function works in the binding without the original scope. The backend object is in scope anyways (in QML) and the function is run every time when the array property is changed.

                              There's nothing miraculous in it, but the Qt documentation doesn't seem to give any advice about these kinds of situations where the binding function should handle variables which are in the outer function scope but not in the QML scope.

                              1 Reply Last reply
                              1
                              • O Obi-Wan

                                @Eeli-K You are absolutely right, backend.array.x should be backend.array[i].x in both cases. That what a mistake in my example code. I edited the original question.

                                The second part has me confused however. I don't understand why I should have to bind the C++ QVector2D.x specifically? In my head (which again might be confused) it should be enough to create a property of the QList, and all the data in the QList should be available in QML.

                                If I have a single QVector2D in C++ and use Q_PROPERTY to expose that to QML, I can indeed bind the x and y components of that vector to a property of any static QML object. I do not have to create a property of the x and y components in that case, why is that different now?

                                I haven't tried, but I expect I would have the same problem if I used a single QVector2D to expose some data to QML and then dynamically created one single box. That is, I think the problems comes from the QML object being created some time after application start, and that the bindings therefore need to be handled in a special way.

                                @GrecKo Thanks for the tip! How do you use the Repeater to create objects on the go?

                                In the documentation it says:

                                The Repeater type creates all of its delegate items when the repeater is first created. This can be inefficient if there are a large number of delegate items and not all of the items are required to be visible at the same time. If this is the case, consider using other view types like ListView (which only creates delegate items when they are scrolled into view) or use the Dynamic Object Creation methods to create items as they are required.

                                The Dynamic Object Creation methods they refer to are the ones I am using :)

                                GrecKoG Offline
                                GrecKoG Offline
                                GrecKo
                                Qt Champions 2018
                                wrote on last edited by
                                #21

                                In the documentation it says:

                                The Repeater type creates all of its delegate items when the repeater is first created. This can be inefficient if there are a large number of delegate items and not all of the items are required to be visible at the same time. If this is the case, consider using other view types like ListView (which only creates delegate items when they are scrolled into view) or use the Dynamic Object Creation methods to create items as they are required.

                                The Dynamic Object Creation methods they refer to are the ones I am using :)

                                Why do you need that though ? A repeater can do what you are doing with less code.
                                Most of the time, handling dynamic object creation is counter-productive.

                                E O 2 Replies Last reply
                                2
                                • GrecKoG GrecKo

                                  In the documentation it says:

                                  The Repeater type creates all of its delegate items when the repeater is first created. This can be inefficient if there are a large number of delegate items and not all of the items are required to be visible at the same time. If this is the case, consider using other view types like ListView (which only creates delegate items when they are scrolled into view) or use the Dynamic Object Creation methods to create items as they are required.

                                  The Dynamic Object Creation methods they refer to are the ones I am using :)

                                  Why do you need that though ? A repeater can do what you are doing with less code.
                                  Most of the time, handling dynamic object creation is counter-productive.

                                  E Offline
                                  E Offline
                                  Eeli K
                                  wrote on last edited by
                                  #22

                                  @GrecKo @Obi-Wan I agree with GrecKo. Although it would be nice to put my finding in real use, it's better to use standard means of creating several similar items, which is using views, models and delegates (in this case the Repeater may be the best).

                                  1 Reply Last reply
                                  1
                                  • GrecKoG GrecKo

                                    In the documentation it says:

                                    The Repeater type creates all of its delegate items when the repeater is first created. This can be inefficient if there are a large number of delegate items and not all of the items are required to be visible at the same time. If this is the case, consider using other view types like ListView (which only creates delegate items when they are scrolled into view) or use the Dynamic Object Creation methods to create items as they are required.

                                    The Dynamic Object Creation methods they refer to are the ones I am using :)

                                    Why do you need that though ? A repeater can do what you are doing with less code.
                                    Most of the time, handling dynamic object creation is counter-productive.

                                    O Offline
                                    O Offline
                                    Obi-Wan
                                    wrote on last edited by
                                    #23

                                    @GrecKo @Eeli-K Thank you very much both of you!

                                    I immediately got it working with your fix Eeli-K. I guess it goes to show that some knowledge of javascript is indeed useful! (I personaly have about zero, and just try to do what I would in C++ and adapt it from there ...)

                                    I also got it working with the Repeater now, and it does indeed do what I want with a lot less code:

                                    Repeater
                                    {
                                    model: backEnd.array.length // Adjusts number of boxes according to C++ list length
                                    Box
                                    {
                                    // Binds x and y properties using Repeater's index in the array
                                    pos_x: backEnd.array[index].x 
                                    pos_y: backEnd.array[index].y
                                    }
                                    }
                                    

                                    For some reason my mind was sort of set on the Repeater having to know the number of items on startup. It could just be that I'm not used to thinking QML'y and couldn't comprehend that binding the model property to the array length would create the number of objects as needed. (It isn't crucial, but I still don't understand the name of that property, what is the rationale behind "model" meaning roughly "number of items created by Repeater"?)

                                    Googling Dynamic Object Creation QML leads you here which I guess led me to think this was the primary way of achieving dynamic object creation.

                                    I guess I really should read up on models, views and delegates, and just the general QML "mindset"!

                                    Again, thanks, I learned a lot from this little exercise! :)

                                    GrecKoG 1 Reply Last reply
                                    0
                                    • O Obi-Wan

                                      @GrecKo @Eeli-K Thank you very much both of you!

                                      I immediately got it working with your fix Eeli-K. I guess it goes to show that some knowledge of javascript is indeed useful! (I personaly have about zero, and just try to do what I would in C++ and adapt it from there ...)

                                      I also got it working with the Repeater now, and it does indeed do what I want with a lot less code:

                                      Repeater
                                      {
                                      model: backEnd.array.length // Adjusts number of boxes according to C++ list length
                                      Box
                                      {
                                      // Binds x and y properties using Repeater's index in the array
                                      pos_x: backEnd.array[index].x 
                                      pos_y: backEnd.array[index].y
                                      }
                                      }
                                      

                                      For some reason my mind was sort of set on the Repeater having to know the number of items on startup. It could just be that I'm not used to thinking QML'y and couldn't comprehend that binding the model property to the array length would create the number of objects as needed. (It isn't crucial, but I still don't understand the name of that property, what is the rationale behind "model" meaning roughly "number of items created by Repeater"?)

                                      Googling Dynamic Object Creation QML leads you here which I guess led me to think this was the primary way of achieving dynamic object creation.

                                      I guess I really should read up on models, views and delegates, and just the general QML "mindset"!

                                      Again, thanks, I learned a lot from this little exercise! :)

                                      GrecKoG Offline
                                      GrecKoG Offline
                                      GrecKo
                                      Qt Champions 2018
                                      wrote on last edited by GrecKo
                                      #24

                                      @Obi-Wan a model can be a QAbstractItemModel, QList<QObject*>, QStringList, a js array, an integer, a single instance of an object, etc. You can read more about it here : http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html

                                      Your example could be simplified by directly using the array, and not just its length

                                      Repeater {
                                          model: backEnd.array
                                          Box {
                                              pos_x: modelData.x
                                              pos_y: modelData.y
                                          }
                                      }
                                      
                                      O 1 Reply Last reply
                                      1
                                      • GrecKoG GrecKo

                                        @Obi-Wan a model can be a QAbstractItemModel, QList<QObject*>, QStringList, a js array, an integer, a single instance of an object, etc. You can read more about it here : http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html

                                        Your example could be simplified by directly using the array, and not just its length

                                        Repeater {
                                            model: backEnd.array
                                            Box {
                                                pos_x: modelData.x
                                                pos_y: modelData.y
                                            }
                                        }
                                        
                                        O Offline
                                        O Offline
                                        Obi-Wan
                                        wrote on last edited by
                                        #25

                                        @GrecKo Ahh! Thanks! That explains the name! The code just keeps on getting shorter ...

                                        And I guess this is what you meant in your original reply where you wrote:

                                        Why not just use the QVariantList as a model in QML ?

                                        ?

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

                                          Indeed :) , sorry for the brevity of my answer but I wasn't sure what you needed and was kinda lost by where the previous discussion was heading.
                                          As you said, I feel that the Dynamic Object Creation in QML article is misguiding a lot of people (here and on StackOverflow) by not mentioning saner alternatives like model + repeater/view or even a declaratively created Component.

                                          Ultimately I think that you should use imperative object creation only for temporary object needed by the UI layer, like showing a dialog for example. I have yet to see another legit usecase for it (or I don't remember it).

                                          O 1 Reply Last reply
                                          2

                                          • Login

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