how to cause view to update based on model change?
-
Hi all -
In this code:
ListView { model: spaceProxyModel delegate: GridView { required property Space space property var spaceFeatureList: sceneModel.featureUuidList(false, space.uuid) // from a C++ function model: spaceFeatureList delegate: Item { ...
The property spaceFeatureList gets updated from another screen, but my view doesn't reflect the change to the model (I've confirmed that spaceFeatureList is indeed being changed). What do I need to do to force the view update?
Thanks...
-
@jeremy_k said in how to cause view to update based on model change?:
Doing this will cause the view to reset and create new delegates for all items visible. The documentation apparently isn't sufficiently explicit. Assigning a JS object to a view's
model
property does not create a QAIM that tracks the object's content.from my own experience, QAbstractItemModel doesn't magically track the object content. If you modify the underlying data out of the QAIM or any connected view, the QAIM is unaware of these changes. I mean that if a QAIM encapsulates a QList<WhatSoEver> list, and you modify that list directly, the QAIM won't see the change.
I'm getting a little bit confused now, but as OP has two views and the problem is, if I understand it right, when one view is modified, the other is not updated.
What I would make is an underlying ListModel that would encapsulate the JSArray. And bind those two view to that ListModel rather than to the JSArray. Perhaps wouldn't solve everything, a good beginning I think.
@GrecKo said in how to cause view to update based on model change?:
Forget the dataChanged() discussion, it is not relevant here as you realized since no QAIM is involved.
Yep my bad, I really don't understand where I got a dataChanged signal to ListModel, it doesn't have any.
@ankou29666 said in how to cause view to update based on model change?:
@jeremy_k said in how to cause view to update based on model change?:
Doing this will cause the view to reset and create new delegates for all items visible. The documentation apparently isn't sufficiently explicit. Assigning a JS object to a view's
model
property does not create a QAIM that tracks the object's content.from my own experience, QAbstractItemModel doesn't magically track the object content. If you modify the underlying data out of the QAIM or any connected view, the QAIM is unaware of these changes. I mean that if a QAIM encapsulates a QList<WhatSoEver> list, and you modify that list directly, the QAIM won't see the change.
Put more abstractly, if code avoids the interface of a class, it won't function as an instance of that class. Hopefully that's not a surprise.
I'm getting a little bit confused now, but as OP has two views and the problem is, if I understand it right, when one view is modified, the other is not updated.
What I would make is an underlying ListModel that would encapsulate the JSArray. And bind those two view to that ListModel rather than to the JSArray. Perhaps wouldn't solve everything, a good beginning I think.
At the risk of repeating, a JS Array (or any javascript object) does not have a change notification signal. This means that nothing else is aware that an update is required. Detecting changes must therefore depend on another mechanism. For example:
- Assign a new object to a QtObject's property when the change is made.
- Run a Timer and go looking for changes in data, refreshing display Items as appropriate.
- Limit changes to a dedicated editor "page" that doesn't rely on change signals, and reload all other "pages" when the editor page is destroyed.
There are probably other options that I haven't considered.
The TL;DR; version is: Why not use ListModel? It has member functions for inserting, removing, and modifying items in the list that can be called from javascript.
-
Hi,
Might be a silly question but are you emitting the dataChanged signal from your model with the roles used in QML ?
-
@SGaist that's actually a very good question, and the answer is "no." I thought dataChanged() was only a method of a proper QAbstractItemModel object - my model is just a JS array that is initialized by a call to an invokable function. Do I need to implement my own signal (and handler) for when the list changes?
-
if your data structure changes outside of the model, then your data has to emit a signal to the model, that the model itself will have to relay to the view. Assuming that the datastructure is a QObject derivative that can emit signals, which is probably not your case.
With data not being a QObject, I would add a method to the model that would emit the signal. This method would have to be called upon any single data update
If your data changes outside of the model, the model has to be notified of that change so that it can transmit to (the cascade of proxies if ever relevant and to) the view.
-
if your data structure changes outside of the model, then your data has to emit a signal to the model, that the model itself will have to relay to the view. Assuming that the datastructure is a QObject derivative that can emit signals, which is probably not your case.
With data not being a QObject, I would add a method to the model that would emit the signal. This method would have to be called upon any single data update
If your data changes outside of the model, the model has to be notified of that change so that it can transmit to (the cascade of proxies if ever relevant and to) the view.
@ankou29666 said in how to cause view to update based on model change?:
With data not being a QObject, I would add a method to the model that would emit the signal.
My model is a simple JS array. How does an array emit a signal?
-
@ankou29666 said in how to cause view to update based on model change?:
With data not being a QObject, I would add a method to the model that would emit the signal.
My model is a simple JS array. How does an array emit a signal?
-
@JoeCFD that works for a C++ model, but in this instance, my model is just a JS array of strings. I don't have the context from which to make any of those calls.
-
@ankou29666 said in how to cause view to update based on model change?:
With data not being a QObject, I would add a method to the model that would emit the signal.
My model is a simple JS array. How does an array emit a signal?
@mzimmers said in how to cause view to update based on model change?:
@ankou29666 said in how to cause view to update based on model change?:
With data not being a QObject, I would add a method to the model that would emit the signal.
My model is a simple JS array. How does an array emit a signal?
As I said, you create a method in your model
Item { ListModel { id: yourListModel function emitSignal () { emit dataChanged ; } } }
and you ensure that any modification to the data is followed by a call to this method.
This is basically what I did by the past (but in C++ not in QML).
but in the future, in order to make it fool proof, I would rather put the data structure as protected or private member of the model, such that any change that has been made outside of the views has to be done through public accessors of the model, that automatically trigger the appropriate signals. -
@mzimmers said in how to cause view to update based on model change?:
@ankou29666 said in how to cause view to update based on model change?:
With data not being a QObject, I would add a method to the model that would emit the signal.
My model is a simple JS array. How does an array emit a signal?
As I said, you create a method in your model
Item { ListModel { id: yourListModel function emitSignal () { emit dataChanged ; } } }
and you ensure that any modification to the data is followed by a call to this method.
This is basically what I did by the past (but in C++ not in QML).
but in the future, in order to make it fool proof, I would rather put the data structure as protected or private member of the model, such that any change that has been made outside of the views has to be done through public accessors of the model, that automatically trigger the appropriate signals. -
not impossible, I had made a little pause with Qt and currently rather working on the UI than the code, so it's far from impossible that I make a few bullshits in my code snippets.
-
There's still something missing here -- again, my model isn't a QML list model; it's a simple JS array. According to the docs, this is a valid model. What the docs don't say, though (at least nowhere that I can find), is how such a model can generate a signal to update the view. Maybe this can't be done from an array model, but I imagine that somehow, I can effect the same thing.
-
I would rather formulate it this way : the doc doesn't say that such JS array data doesn't update the underlying Qt model, if any, and thus, the Qt views.
-
There's still something missing here -- again, my model isn't a QML list model; it's a simple JS array. According to the docs, this is a valid model. What the docs don't say, though (at least nowhere that I can find), is how such a model can generate a signal to update the view. Maybe this can't be done from an array model, but I imagine that somehow, I can effect the same thing.
@mzimmers
Yes, a JSON array is not going to be able to emit a signal. What is the problem with callingdataChanged()
per above whenever you change existing data the underlying JS array? I mean even if we were in C++ and I altered an array or vector orQList
or whatever was my model's actual data I would still have to emitdataChanged()
explicitly correspondingly. -
Hi all -
In this code:
ListView { model: spaceProxyModel delegate: GridView { required property Space space property var spaceFeatureList: sceneModel.featureUuidList(false, space.uuid) // from a C++ function model: spaceFeatureList delegate: Item { ...
The property spaceFeatureList gets updated from another screen, but my view doesn't reflect the change to the model (I've confirmed that spaceFeatureList is indeed being changed). What do I need to do to force the view update?
Thanks...
Forget the dataChanged() discussion, it is not relevant here as you realized since no QAIM is involved.
@mzimmers said in how to cause view to update based on model change?:The property spaceFeatureList gets updated from another screen, but my view doesn't reflect the change to the model (I've confirmed that spaceFeatureList is indeed being changed). What do I need to do to force the view update?
Nothing, it is handed automatically if the spaceFeatureList actually changes.
Are you sure it does? How did you confirm it? -
Forget the dataChanged() discussion, it is not relevant here as you realized since no QAIM is involved.
@mzimmers said in how to cause view to update based on model change?:The property spaceFeatureList gets updated from another screen, but my view doesn't reflect the change to the model (I've confirmed that spaceFeatureList is indeed being changed). What do I need to do to force the view update?
Nothing, it is handed automatically if the spaceFeatureList actually changes.
Are you sure it does? How did you confirm it?@GrecKo said in how to cause view to update based on model change?:
How did you confirm it?
I have a telltale printout.
My main QML file is SceneSetup.qml. On a button press, I push another component (SceneFeatureSelect.qml) onto my stack. I pass the model as a required property. When SceneFeatureSelect changes the list (the model) it uses a callback to notify SceneSetup. My printout in SceneSetup confirms the addition to the model, but my GridView doesn't change.
-
@mzimmers said in how to cause view to update based on model change?:
My printout in SceneSetup confirms the addition to the model, but my GridView doesn't change.
To which model?
That's too abstract for me to make the link with your code.
Does or does not thespaceFeatureList
get reevaluated with a new value?
You didn't confirm it withonSpaceFeatureListChanged: print("space feature list changed"
if I understand correctly.In the following code:
ListView { model: spaceProxyModel delegate: GridView { required property Space space property var spaceFeatureList: sceneModel.featureUuidList(false, space.uuid)
The spaceFeatureList will get reevaluated if you change if
sceneModel
corresponding NOTIFY signal is emitted or if there's adataChanged
signal for theuuid
role of the corresponding row inspaceProxyModel
(or for a modelResest or layoutChanged).
It also may work iffeatureUuidList
is a function implemented in JS but I doubt that's the case. -
@mzimmers said in how to cause view to update based on model change?:
My printout in SceneSetup confirms the addition to the model, but my GridView doesn't change.
To which model?
That's too abstract for me to make the link with your code.
Does or does not thespaceFeatureList
get reevaluated with a new value?
You didn't confirm it withonSpaceFeatureListChanged: print("space feature list changed"
if I understand correctly.In the following code:
ListView { model: spaceProxyModel delegate: GridView { required property Space space property var spaceFeatureList: sceneModel.featureUuidList(false, space.uuid)
The spaceFeatureList will get reevaluated if you change if
sceneModel
corresponding NOTIFY signal is emitted or if there's adataChanged
signal for theuuid
role of the corresponding row inspaceProxyModel
(or for a modelResest or layoutChanged).
It also may work iffeatureUuidList
is a function implemented in JS but I doubt that's the case. -
@mzimmers said in how to cause view to update based on model change?:
My printout in SceneSetup confirms the addition to the model, but my GridView doesn't change.
To which model?
That's too abstract for me to make the link with your code.
Does or does not thespaceFeatureList
get reevaluated with a new value?
You didn't confirm it withonSpaceFeatureListChanged: print("space feature list changed"
if I understand correctly.In the following code:
ListView { model: spaceProxyModel delegate: GridView { required property Space space property var spaceFeatureList: sceneModel.featureUuidList(false, space.uuid)
The spaceFeatureList will get reevaluated if you change if
sceneModel
corresponding NOTIFY signal is emitted or if there's adataChanged
signal for theuuid
role of the corresponding row inspaceProxyModel
(or for a modelResest or layoutChanged).
It also may work iffeatureUuidList
is a function implemented in JS but I doubt that's the case.@GrecKo said in how to cause view to update based on model change?:
To which model?
That's too abstract for me to make the link with your code.Sorry - this is a fairly complicated bit of stuff. Here's a snippet of the relevant code:
Pane { id: sceneSetupPane signal featureListChanged(spaceId: string, activityId: string, status: bool) function changeCallback(spaceId, activityId, status) { featureListChanged(spaceId, activityId, status) } ListView { model: spaceProxyModel delegate: GridView { required property Space space property var spaceFeatureList: sceneModel.featureUuidList(false, space.uuid) model: spaceFeatureList delegate: Text { text: modelData } Connections { target: sceneSetupPane function onFeatureListChanged(spaceId, activityId, status) { if (spaceId.localeCompare(space.uuid) === 0) { if (status) { if (spaceFeatureList.indexOf(activityId) === -1) { var i = spaceFeatureList.push(activityId) } } else { var j = spaceFeatureList.indexOf(activityId) if (j !== -1) { spaceFeatureList.splice(spaceFeatureList.indexOf(activityId), 1) } } console.log("spaceFeatureList is now", spaceFeatureList) } } }
I've verified that the callback is invoked appropriately, all the arguments are good, and the spaceFeatureList is correct after processing. Yet, my view doesn't change.