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

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.
  • E Eeli K
    5 Aug 2017, 10:02

    @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 5 Aug 2017, 14:48 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 5 Aug 2017, 17:31
    0
    • O Obi-Wan
      5 Aug 2017, 14:48

      @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 5 Aug 2017, 17:31 last edited by Eeli K 8 May 2017, 18:47
      #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
        5 Aug 2017, 14:48

        @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 5 Aug 2017, 20:55 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 6 Aug 2017, 12:08
        1
        • E Eeli K
          5 Aug 2017, 20:55

          @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 6 Aug 2017, 12:08 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
            5 Aug 2017, 20:55

            @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 6 Aug 2017, 16:12 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
              4 Aug 2017, 18:59

              @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 :)

              G Offline
              G Offline
              GrecKo
              Qt Champions 2018
              wrote on 6 Aug 2017, 22:01 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 7 Aug 2017, 06:52
              2
              • G GrecKo
                6 Aug 2017, 22:01

                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 7 Aug 2017, 06:52 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
                • G GrecKo
                  6 Aug 2017, 22:01

                  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 7 Aug 2017, 07:42 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! :)

                  G 1 Reply Last reply 7 Aug 2017, 07:57
                  0
                  • O Obi-Wan
                    7 Aug 2017, 07:42

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

                    G Offline
                    G Offline
                    GrecKo
                    Qt Champions 2018
                    wrote on 7 Aug 2017, 07:57 last edited by GrecKo 8 Jul 2017, 07:59
                    #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 7 Aug 2017, 08:12
                    1
                    • G GrecKo
                      7 Aug 2017, 07:57

                      @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 7 Aug 2017, 08:12 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
                      • G Offline
                        G Offline
                        GrecKo
                        Qt Champions 2018
                        wrote on 7 Aug 2017, 08:27 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 7 Aug 2017, 09:02
                        2
                        • G GrecKo
                          7 Aug 2017, 08:27

                          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 Offline
                          O Offline
                          Obi-Wan
                          wrote on 7 Aug 2017, 09:02 last edited by
                          #27

                          @GrecKo said in Binding C++ properties exposed to QML to dynamically created QML objects:

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

                          I will keep this in mind!

                          Hopefully this little discusion might help someone else struggling to understand the same concepts!

                          1 Reply Last reply
                          0

                          25/27

                          7 Aug 2017, 08:12

                          • Login

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