Bind to another model's data in a delegate
-
I need to bind a text property in my delegate to data from another QAbstractListModel. The only way I can think to achieve this would be to bind it to the data method of the external model, but this doesn't appear to react to data changes. The alternative, which I'm trying to avoid, would be to expose it through the model to which the delegate is tied.
What is the "correct" way here?
-
Can you give us more context? how is the data from the other model selected (row, column, index)?
Do you need this for one delegate at a time or for all delegates? -
Can you give us more context? how is the data from the other model selected (row, column, index)?
Do you need this for one delegate at a time or for all delegates? -
I need to bind a text property in my delegate to data from another QAbstractListModel. The only way I can think to achieve this would be to bind it to the data method of the external model, but this doesn't appear to react to data changes. The alternative, which I'm trying to avoid, would be to expose it through the model to which the delegate is tied.
What is the "correct" way here?
@ECEC said in Bind to another model's data in a delegate:
The alternative, which I'm trying to avoid, would be to expose it through the model to which the delegate is tied.
Why? This sounds like the correct way to me, rather than duplicating data and effectively reimplementing the model view framework.
-
@jeremy_k , Just to make sure we're on the same page, you are suggesting that I should connect Model B's dataChanged() to a slot in Model A, which will then check whether the relevant data has changed and emit its own dataChanged? Model A's data() will simply query Model B's data() to get the data it needs.
Is that right?
-
@jeremy_k , Just to make sure we're on the same page, you are suggesting that I should connect Model B's dataChanged() to a slot in Model A, which will then check whether the relevant data has changed and emit its own dataChanged? Model A's data() will simply query Model B's data() to get the data it needs.
Is that right?
@ECEC said in Bind to another model's data in a delegate:
@jeremy_k , Just to make sure we're on the same page, you are suggesting that I should connect Model B's dataChanged() to a slot in Model A, which will then check whether the relevant data has changed and emit its own dataChanged? Model A's data() will simply query Model B's data() to get the data it needs.
No, although you may be highlighting a gap in my understanding. My suggestion is to have a single model which feeds both views. Maintaining a single source of truth rather than multiple related models is usually easier to work with.
-
The models are totally independent and I don't think it would make sense to combine them. The property from model B that we want to access in model A is only units (i.e. mm, cm, inches, etc) and we want to display this for convenience, although it has no effect on how the user interacts with model A's data.
Each item in model A is associated with the id of an item in model B.
-
The models are totally independent and I don't think it would make sense to combine them. The property from model B that we want to access in model A is only units (i.e. mm, cm, inches, etc) and we want to display this for convenience, although it has no effect on how the user interacts with model A's data.
Each item in model A is associated with the id of an item in model B.
@ECEC said in Bind to another model's data in a delegate:
The models are totally independent and I don't think it would make sense to combine them. The property from model B that we want to access in model A is only units (i.e. mm, cm, inches, etc) and we want to display this for convenience, although it has no effect on how the user interacts with model A's data.
Each item in model A is associated with the id of an item in model B.
I'm even more convinced now. Units not attached to scalars?
The single model can be a proxy model that combines the two, similar to how a SQL join combines multiple tables into one for the sake of presentation. That allows the view to be unconcerned with the details of different sources of information.
-
@ECEC said in Bind to another model's data in a delegate:
The models are totally independent and I don't think it would make sense to combine them. The property from model B that we want to access in model A is only units (i.e. mm, cm, inches, etc) and we want to display this for convenience, although it has no effect on how the user interacts with model A's data.
Each item in model A is associated with the id of an item in model B.
I'm even more convinced now. Units not attached to scalars?
The single model can be a proxy model that combines the two, similar to how a SQL join combines multiple tables into one for the sake of presentation. That allows the view to be unconcerned with the details of different sources of information.
@jeremy_k said in Bind to another model's data in a delegate:
The single model can be a proxy model that combines the two, similar to how a SQL join combines multiple tables into one for the sake of presentation. That allows the view to be unconcerned with the details of different sources of information.
As @jeremy_k says. I did not comment before because I know nothing about QML or its exact relationship with a
QAbstractListModel. But assuming you can do things at the backend model side (e.g. not worry about delegates, ids or Q_INVOKABLE) one way is to use aQAbstractProxyModel(start fromQIdentityProxyModelfor this) to add a column (with id lookup) from another table. Another similar is whatQSqlRelationalTableModelis doing, which is precisely what it sounds like you want: "combine two tables via foreign key, so an "id" in the child table points to a "word" to use for it in the parent table". You can also do it directly in your child table'sdata()method. Finally you can also do it by performing aJOINat the table side. If you then need or want to still present this as aQAbstractListModelto keep QML happy you can wrap the above to present it that way to the outside world. -
@jeremy_k , A proxy model can only have one source though, so surely this would still require connecting to model B's dataChanged to be aware of relevant changes (i.e units changed)?
@jeremy_k , I'd like to avoid redeveloping a significant portion of the application to use a database. Seems overkill for what I'm trying to achieve.
-
@jeremy_k , A proxy model can only have one source though, so surely this would still require connecting to model B's dataChanged to be aware of relevant changes (i.e units changed)?
@jeremy_k , I'd like to avoid redeveloping a significant portion of the application to use a database. Seems overkill for what I'm trying to achieve.
-
@JonB, apologies, the second point was for you. Although please feel free to respond to both.
-
I never intended you to use an actual database. Only the
QSqlRelationaltableModelhas anything to do with databases, and that was merely an illustration of something which is probably doing what you want might be written. The principle applies whether backend database or not, it's actually about looking up a value in one table from another table with both in memory. -
I don't understand what you mean about your stuff "doesn't appear to react to data changes", "dataChanged()" or "units changed". Presumably the units table which "we want to display this for convenience" does not itself change? It holds display information for "mm, cm, inches, etc" and your real data table ("child") simply has a column in each row stating what units (another column) is using which you want to look up (in the "parent") to show the units against the value? Changes get made in the "parent" (value) table, not in the "child) (unit lookup) table?
-
-
@JonB
I fear that I haven't explained this clearly. Let me illustrate the problem with the following simplified scenario:struct Building { int id; QString name; QColor color; int width; int depth; int height; }We have a BuildingListModel with a vector of Building which inherits from QAbstractListModel, and exposes the data above via custom roles. This all works fine.
struct Trip { int fromBuildingID; int toBuildingID; QString tripName; VehicleType vehicle; }And here we have a TripListModel with a vector of Trips which also inherits from QAbstractListModel and exposes the data via custom roles. This also works fine.
In the Trip list view's delegate, the user selects the from building and to building ids from a combo box which connects to the BuildingListModel. Again this is all good.
But now, at this point, we decide it might be helpful to the user if we display the color of the "from" building on the left side of the delegate and the color of the "to" building on the right side of the delegate. This has no bearing on the inner workings of the TripListModel and is only for visual convenience.
We also have to consider that the user might change a Building's color at any point via the Building's list view, and any such changes should be reflected in the Trip list view's delegates.
To me at least, the most logical solution would be to connect BuildingListModel's dataChanged() to a slot in TripListModel. When a Building's data changes, we can then check whether it was for the Color role, retrieve the changed Building's ID, and then search our vector of Trip for any items that are associated with that Building ID, and finally emit dataChanged() for a color role in the TripListModel.
I hope that makes sense.
-
@JonB
I fear that I haven't explained this clearly. Let me illustrate the problem with the following simplified scenario:struct Building { int id; QString name; QColor color; int width; int depth; int height; }We have a BuildingListModel with a vector of Building which inherits from QAbstractListModel, and exposes the data above via custom roles. This all works fine.
struct Trip { int fromBuildingID; int toBuildingID; QString tripName; VehicleType vehicle; }And here we have a TripListModel with a vector of Trips which also inherits from QAbstractListModel and exposes the data via custom roles. This also works fine.
In the Trip list view's delegate, the user selects the from building and to building ids from a combo box which connects to the BuildingListModel. Again this is all good.
But now, at this point, we decide it might be helpful to the user if we display the color of the "from" building on the left side of the delegate and the color of the "to" building on the right side of the delegate. This has no bearing on the inner workings of the TripListModel and is only for visual convenience.
We also have to consider that the user might change a Building's color at any point via the Building's list view, and any such changes should be reflected in the Trip list view's delegates.
To me at least, the most logical solution would be to connect BuildingListModel's dataChanged() to a slot in TripListModel. When a Building's data changes, we can then check whether it was for the Color role, retrieve the changed Building's ID, and then search our vector of Trip for any items that are associated with that Building ID, and finally emit dataChanged() for a color role in the TripListModel.
I hope that makes sense.
@ECEC said in Bind to another model's data in a delegate:
We also have to consider that the user might change a Building's color at any point via the Building's list view, and any such changes should be reflected in the Trip list view's delegates.
Yes, but this example is rather different from your earlier one where the (parent) "lookup" table was only going to hold a list of "unit measurement words". There presumably the parent, lookup table was not going to change: if a child row had the id for "inches" in the lookup column you were not going to change the lookup table for that to suddenly be "feet". So you don't to know if the parent table is changing, it isn't.
In your new one the "parent" table,
Buildings, can apparently change. So, yes, you do need to interceptBuilding'sdataChanged()and then emit aTrip'sdataChanged()against each cell whosefromBuildingIDortoBuildingIDequals theBuilding's id whose color has consequently altered. Even though the building id value in trips has not itself been changed, you need the color to be re-read and updated.But all this is good and fine. There is no reason for you not to implement something like this. You don't need to expose the knowledge of the link to the outside world. At least this is fine to do in the backend I know about, in C++ with abstract models and/or proxies as you please. I can't say about QML or roles or delegates.
It's not for the same purpose as your situation, but in the work I am doing (an "emulator") now I happen to have:
- A model for the contents of memory.
- A tableview to display that, with updates as memory changes. This is a complete "memory view".
- A model for a "watch view". This is separate and user can set rows to "watch" individual memory locations.
- The tableview to display that, with updates as memory changes. This is a selective memory "watch view".
Obviously the complete memory model's
dataChanged()is connected to the memory view, so that updates. But the watch model has its own columns (address watched, label etc.) as well as a column for the memory value which is in the memory model. So the watch model has to attach to the memory model'sdataChanged(): when it fires, go through all watch rows, if they are watching a changed memory location then emit adataChanged()against whatever row/column that happens to be in watch model so that watch view updates. Same kind of situation as yours. And it works fine. Whether you do that with subclassing, proxies or other (e.g. justdata()method) is a detail.If you look at the implementation of
QIdentityProxyModel/QAbstractProxyModelit hooks onto all the signals of the underlying sourceQAbstractItemModelto bring them into the proxy. So this procedure of "chaining" signals (likedataChanged()) from a base model to a wrapped model is not unusual. -
Sorry, it seems I led you astray. The scenario above is equivalent to what I was trying to describe originally.
Units are part of a struct which are held by a vector in Model B, and can change during runtime.So, I think we are both in agreement then that the best thing would be to do as I suggested and connect one model to the other's dataChanged().
Thanks!
-
@jeremy_k , A proxy model can only have one source though, so surely this would still require connecting to model B's dataChanged to be aware of relevant changes (i.e units changed)?
@jeremy_k , I'd like to avoid redeveloping a significant portion of the application to use a database. Seems overkill for what I'm trying to achieve.
@ECEC said in Bind to another model's data in a delegate:
@jeremy_k , A proxy model can only have one source though, so surely this would still require connecting to model B's dataChanged to be aware of relevant changes (i.e units changed)?
QAbstractProxyModel::setSourceModel() only accepts one model. That doesn't mean that a proxy must use or limit itself to this interface.