Can I efficiently get just one item from QAbstractItemModel while keeping role bindings?
-
Hey, got a non-trivial QML question..
Can I efficiently get just a one item from QAbstractListModel-derived model (ideally through ID) without using proxy filter model while keeping the role bindings?
My use case is having models that are related (basically foreign key) and delegates that need data from both of them. E.g. my player delegate should show its own roles as well as roles from playlist item. PlayerModel has role "playlistUuid".
I want to avoid having hundreds of proxyfilter models so what I did is redirecting specific data() calls to playlistModel->data() as well as connecting playlistModel's dataChanged to playerModel's dataChanged (translating role numbers and indexes). That works but is very tedious and error-prone, so I am looking for better way. Any ideas?
-
@arakan94 said in Can I efficiently get just one item from QAbstractItemModel while keeping role bindings?:
Got any experience with that?
I don't know what kind of overhead the DB layer will bring. All I can think is to write a couple of trivial apps that test and compare model types for efficiency. Probably much easier said than done.
I have not used CBOR. We use message pack for some of our serialization for websockets.
Edit: If the DB is completely memory based, wouldn't the tables be regenerated each time anyway? So if it gets a CBOR object(s) wouldn't it just create a new table? I don't know what that would do to the QML side though.
@fcarney yeah, probably would have to just test and measure the performance, which is probably quite a bit of work with unsure result and sadly, I don't have unlimited time..
Meanwhile, I've came up with different approach and even tested it a little and it seems to be working. Not sure how viable it is or what pitfalls it might have though.
Basically, I'd change my models into models of QObjects with Q_PROPERTY for every role. Model would function as normal but would have extra functionality - it would be possible to register another QObject of same class as a mirror (each xChanged property signal from source item would be connected to setX signal of the mirror). This mirror QObject would exist in QML and its properties could be used in the same way as model roles.
Repeater { model: playerModel delegate: Item { Label { text: "Player name: " + nameRole } PlaylistMirror { id: playlist uuid: playlistUuidRole } Label { text: "Playlist name: " + playlist.name } }
This mirror object would register itself to the model whenever its uuid property changed. Something like this (with checks ofc):
void register(QUuid uuid, Playlist *mirror) { Playlist * source = find(uuid); connect(source, &Playlist::nameChanged, mirror, &Playlist::setName); mirror->setName(source->name()); // initial sync // same for all roles }
It should be safe - if the source object got destroyed by mistake, it would simply stop updating the mirror instead of crashing that would happen if I just got raw pointer from the model. There would have to be some logic for cleaning (if mirror changed the uuid, it would have to disconnect and connect again; if source was deleted, mirror should be cleaned) but otherwise, it seems like decent solution.
The performance hit of generating xChanged signals without any receiver should be negligible and there is no need for any extra models or filtering. Higher memory usage is non-issue.
Here's diagram for better illustration:
What do you think?
-
https://doc.qt.io/qt-5/qml-qtqml-models-delegatemodel.html ?
Without code or a block diagram I am struggling to understand what you are asking.
-
Hopefully, these diagrams will make it clear :)
I need to do this:
Normal Qt-way would be this:
My ugly solution with "chaining" (or delegating?) roles via custom signals (playlistNameRole will give you nameRole from PlaylistModel from Index with uuidRole equal to playlistUuidRole):
-
Are you trying to create a relational set of models? If so maybe a database would be better. Then you can do joins.
https://forum.qt.io/topic/2828/multiplemodels-oneview/21 This topic looks similar.
You can also create memory based databases in Qt:
http://blog.davidecoppola.com/2016/11/howto-embed-database-in-application-with-sqlite-and-qt/I am not quite getting why you need a lot of proxy filters. Is it just that you have a lot of delegates and each is using a filter? Or you need distinct filters?
-
Are you trying to create a relational set of models? If so maybe a database would be better. Then you can do joins.
https://forum.qt.io/topic/2828/multiplemodels-oneview/21 This topic looks similar.
You can also create memory based databases in Qt:
http://blog.davidecoppola.com/2016/11/howto-embed-database-in-application-with-sqlite-and-qt/I am not quite getting why you need a lot of proxy filters. Is it just that you have a lot of delegates and each is using a filter? Or you need distinct filters?
@fcarney yes, it's basically database JOIN operation
I didn't use SQL because it's distributed application and clients should provide standard desktop application UX, meaning reading DB over network for every view is a no. Model/View approach was chosen for simplicity of QML programming and management of the objects and it works like this:
- each client has local copy of all models
- setData (as well as all create, etc. operations) are forwarded to central server
- server naturally serializes those through qt signal system and its models execute them
- successful operations are then broadcasted to all clients who also execute them, ensuring the content of all the copies is same
It's true, that having in memory Sqlite DB and its accompanying models could probably work the same, but I am not sure about the performance (could easily be better, maybe?) nor how are the models actually used (seem to be limited to TableView?). I use User roles exclusively (and lots of them) - if those were implemented as columns, would it work the same? Would I get all the dataChanged signals like I do with normal models?
Yes, I have lots of delegates and each would have to use filter model with different filter regExp. Some could possibly be shared, but it wouldn't be straightforward - imagine grid of playlist shortcuts (containing uuid referencing the playlist and some metadata) that need to get thumbnail and name from "their" playlist. There would have to be some pool of filter models to limit the number of filter models to minimum, but it could still be quite a large number. And given that it's supposed to have lots of these relational features, I'd rather consider the performance implications now than rewrite it completely later. I'd also love to avoid solutions (such as what I came up with) that require lots of coding.
In any case, thank you for your time! I appreciate it :)
-
I am not doing joins in my database stuff at the moment. I am using sqlite file to take data. I have each table represented as a QSqlTableModel. Each row I am using with a Repeater. I then use the column name to access the values in my delegate. I use this to draw maps for locations in an area. I have not used this with a qml table at all. Just Repeater with delegates.
There are quite a few objects that deal with database. Including a relational table: https://doc.qt.io/qt-5/qtsql-module.html
-
I am not doing joins in my database stuff at the moment. I am using sqlite file to take data. I have each table represented as a QSqlTableModel. Each row I am using with a Repeater. I then use the column name to access the values in my delegate. I use this to draw maps for locations in an area. I have not used this with a qml table at all. Just Repeater with delegates.
There are quite a few objects that deal with database. Including a relational table: https://doc.qt.io/qt-5/qtsql-module.html
@fcarney thanks, might be worth checking it out.. although it would be substantial rewrite - I have over dozen custom QAbstractList-derived models, all saved (and synchronized over network) as CBOR
I was also cautioned against DB solution because of how complicated schema updates are - and it's clear that over the life of the application, there will be substantial changes to what data are stored. With CBOR, most changes (adding/removing columns) are outright compatible and changes of data type are easily possible (detection and conversion in de-serialization process). Got any experience with that?
-
@arakan94 said in Can I efficiently get just one item from QAbstractItemModel while keeping role bindings?:
Got any experience with that?
I don't know what kind of overhead the DB layer will bring. All I can think is to write a couple of trivial apps that test and compare model types for efficiency. Probably much easier said than done.
I have not used CBOR. We use message pack for some of our serialization for websockets.
Edit: If the DB is completely memory based, wouldn't the tables be regenerated each time anyway? So if it gets a CBOR object(s) wouldn't it just create a new table? I don't know what that would do to the QML side though.
-
@fcarney said in Can I efficiently get just one item from QAbstractItemModel while keeping role bindings?:
I don't know what that would do to the QML side though.
Come to think of it. We have used dynamic roles before. We started at the user role and made rolenames. We were using a table and I think somehow we retrieved the number of roles. So maybe the qml side can be just as dynamic.
-
@arakan94 said in Can I efficiently get just one item from QAbstractItemModel while keeping role bindings?:
Got any experience with that?
I don't know what kind of overhead the DB layer will bring. All I can think is to write a couple of trivial apps that test and compare model types for efficiency. Probably much easier said than done.
I have not used CBOR. We use message pack for some of our serialization for websockets.
Edit: If the DB is completely memory based, wouldn't the tables be regenerated each time anyway? So if it gets a CBOR object(s) wouldn't it just create a new table? I don't know what that would do to the QML side though.
@fcarney yeah, probably would have to just test and measure the performance, which is probably quite a bit of work with unsure result and sadly, I don't have unlimited time..
Meanwhile, I've came up with different approach and even tested it a little and it seems to be working. Not sure how viable it is or what pitfalls it might have though.
Basically, I'd change my models into models of QObjects with Q_PROPERTY for every role. Model would function as normal but would have extra functionality - it would be possible to register another QObject of same class as a mirror (each xChanged property signal from source item would be connected to setX signal of the mirror). This mirror QObject would exist in QML and its properties could be used in the same way as model roles.
Repeater { model: playerModel delegate: Item { Label { text: "Player name: " + nameRole } PlaylistMirror { id: playlist uuid: playlistUuidRole } Label { text: "Playlist name: " + playlist.name } }
This mirror object would register itself to the model whenever its uuid property changed. Something like this (with checks ofc):
void register(QUuid uuid, Playlist *mirror) { Playlist * source = find(uuid); connect(source, &Playlist::nameChanged, mirror, &Playlist::setName); mirror->setName(source->name()); // initial sync // same for all roles }
It should be safe - if the source object got destroyed by mistake, it would simply stop updating the mirror instead of crashing that would happen if I just got raw pointer from the model. There would have to be some logic for cleaning (if mirror changed the uuid, it would have to disconnect and connect again; if source was deleted, mirror should be cleaned) but otherwise, it seems like decent solution.
The performance hit of generating xChanged signals without any receiver should be negligible and there is no need for any extra models or filtering. Higher memory usage is non-issue.
Here's diagram for better illustration:
What do you think?