RvdK
RvdK

Reputation: 19800

QML Update Json Model

I have an QT which gui is created in QML. I'm using a custom component which uses a model to display data. At runtime, this model will change. However the changes are not reflected.

MyCustomComponent
{
    property var myModel: [
        {
            icon: "qrc:/resources/my_icon1.png",
            data: "Initial text",
        },
        {
            icon: "qrc:/resources/my_icon2.png",
            data: "Initial text",
        }
    ]
    model: myModel

    property int myProp: 0

    onMyPropChanged:
    {
        refreshModel()
    }

    function refreshModel()
    {
        console.error("refreshModel: before: myModel:" + myModel[0].data +  ", model:" + model[0].data);
        myModel[0].data = "alternate text"
        console.error("refreshModel: after: myModel:" + myModel[0].data +  ", model:" + model[0].data);
    }
}

Outcome:

refreshModel: before: myModel:Initial text, model:Initial text
refreshModel: after: myModel:alternate text, model:Initial text

So at runtime myProp is changed, refreshModel will be called. However the model itself remains unchanged, the myModel does change.

Changing model directly and not does not work either. (so remove the myModel in-between property).

What do I need to do differently?

Side note: Initially I was using a ListModel + ListElement, but this has other issues where I'm looking for an alternative (ListElement: cannot use script for property value)

Upvotes: 1

Views: 2704

Answers (3)

GDevT
GDevT

Reputation: 241

As the other answers already point out, a JSON structure is a variant type. The modelChanged signal thus only fires in case you reassign a new value (new JSON) to the property. Doing small changes to parts of the structure will not fire the signal.

Also Note: When using such a variant model for e.g. your ListView, you will suffer from performance and usability drawbacks as well. Even if you modify the model and fire the signal manually, the view requires a full re-draw. It only knows that "Something in the model changed".

To avoid this, you can transform your JSON into an actual ListModel type (QAbstractListModel implementation), using JsonListModel:

MyCustomComponent
{
    property var myJson: [
        {
            id: 1,
            icon: "qrc:/resources/my_icon1.png",
            data: "Initial text",
        },
        {
            id: 2,
            icon: "qrc:/resources/my_icon2.png",
            data: "Initial text",
        }
    ]

    model: JsonListModel {
      source: myJson
      keyField: "id"
    }

    property int myProp: 0

    onMyPropChanged:
    {
        refreshModel()
    }

    function refreshModel()
    {
        myJson[0].data = "alternate text"
        myJsonChanged() // emit changed signal, JsonListModel thus also updates
    }
}

You can find more details about the JsonListModel and its usage here.

Upvotes: 0

Have a look at what the Qt documentation says about the var type.

It is important to note that changes in regular properties of JavaScript objects assigned to a var property will not trigger updates of bindings that access them.

So in order for the update to be triggered, you have to manually emit the myModelChanged signal, just after you modify the model.

function refreshModel()
{
    console.error("refreshModel: before: myModel:" + myModel[0].data +  ", model:" + model[0].data);
    myModel[0].data = "alternate text"
    myModelChanged()
    console.error("refreshModel: after: myModel:" + myModel[0].data +  ", model:" + model[0].data);
}

Upvotes: 4

marv
marv

Reputation: 1

Indeed, modifying the myModel structure will not trigger a signal. I got used to this pattern, which has the advantage of using an immutable structure:

MyCustomComponent
{
    function computeModel(initial)
    {
        return [
            {
                icon: "qrc:/resources/my_icon1.png",
                data: initial ? "Initial text" : "alternate text",
            },
            {
                icon: "qrc:/resources/my_icon2.png",
                data: "Initial text",
            }
        ];
    }
    model: computeModel(true)

    property int myProp: 0

    onMyPropChanged:
    {
        model = computeModel(false);
    }
}

Upvotes: 0

Related Questions