QAbstractItemModel: how can I associate more data with a QModelIndex than just a pointer or integer?
-
@Guy-Gizmo
ok. so its already "fixed" at that point.
how many items are we talking about? -
Hi,
What structure do you currently use to store your data ?
-
@SGaist It's nested QObject-derived classes. Their structure is similar to that of files and folders, where the container class can contain any number of other containers and items. One of my goals with this structure was to have it so that any of the objects could be deleted, recreated, or reorganized at any time, and any aspect of my application that needs to access to it uses the unique id of the object (essentially its full path in the structure) and doesn't hold on to references to these objects. I've managed to make that work, and it's resulted in a lot of flexibility in how this structure and any derivative structures can be implemented. Except I'm just not sure how to make it work with QAbstractItemModel when I can't associate a QModelIndex with an object using its address in memory.
-
So a tree like structure where you have one root item and all the other children at different levels ?
So you are implementing a model a bit like QFileSystemModel ?
-
@SGaist Although I would have the same problem with any model whose data was ephemeral in the manner that I made mine, regardless of its structure. As long as items in your data can't be identified by a pointer value, be it an ID that fits in 32 or 64 bits, or an address in memory, you can't seem to use it properly with QAbstractItemModel.
So far I've worked around it by using the "separate structure" method I mentioned in my OP. Specifically, I have a separate map that keys on the unique ID of the items in my model. Every time I create a QModelIndex, I create an element in that map for the unique ID that the QModelIndex represents (if that element in the map doesn't already exist). Then I just have each QModelIndex point to the elements in that map. But as I said before, there's no way for me to know when to delete anything in that map so it can grow without bound. It typically won't grow very fast but a) I don't know that for sure as I can't anticipate all use cases and b) it's still a very bad way to do this and I really want to replace it with something better.
But unless I'm missing something (and I really hope that I am), I don't think there's any other way to do this at all!
Maybe this is something to bring up for discussion in one of the Qt mailing lists? It really strikes me that QAbstractItemModel would be a lot more flexible and powerful if a QModelIndex could contain a QVariant, which would instantly solve this issue for me.
-
@jalomic That will cause two QModelIndexes that refer to the same item to not be considered equal, i.e. the == operator will return false. But I could set it up so that two equal QModelndexes get the same pointer.
The other problem is, when do I free all of the stack allocated ids? Whose destructor do I use?
-
@Guy-Gizmo You can use m_cleanup_ids_stack as map
MyStruct* id_ptr; if( m_cleanup_ids_stack.find(my_long_id) != m_cleanup_ids_stack.end()) { id_ptr = m_cleanup_ids_stack[my_long_id]; } else { id_ptr = new MyStruct(my_long_id) } return createIndex(row, column, id_ptr);
The other problem is, when do I free all of the stack allocated ids? Whose destructor do I use?
In model destructor
-
@Guy-Gizmo said:
The actual structures in my model are ephemeral and are often being destroyed and recreated, causing the internalPointer stored in any given QModelIndex to become invalid.
Model indexes are not intended to be stored anyway (see the docs for more details).
As long as items in your data can't be identified by a pointer value, be it an ID that fits in 32 or 64 bits, or an address in memory, you can't seem to use it properly with QAbstractItemModel.
Why?
QSortFilterProxyModel
manages just fine. It does the mapping through internal structures (vectors and hashes).But as I said before, there's no way for me to know when to delete anything in that map so it can grow without bound.
Well, you delete from the map when the underlying data has gone away. Which you can easily catch by subscribing to the
QObject::destroyed()
signal.
From what I understand you want to expose a
QObject
tree through an abstract model and you want to attach additional data for each object. I see two options:- You use dynamic properties or a
QObject
derived class to "inject" your data directly into the objects and you refer to the object by its address when you need to construct aQModelIndex
- You store your additional data in the model subclass and refer to it by the complementary
QObject
's address. You can clean up the entry when theQObject
is destroyed, thus keeping the mapping clean.
class MyModel : public QAbstractItemModel { // ... private: // When inserting/constructing the model connect QObject::destroy to a lambda or a private slot // to clean up the following hash QHash<QObject *, MyObjectData> associatedData; }
And for example when you create the
MyObjectData
instances:QObject * object; //< The object the data refers to MyObjectData data; //< The data you want to attach QObject::connect(object, &QObject::destroyed, [this] (QObject * o) -> void { associatedData.remove(o); }); associatedData.insert(object, data);
Kind regards.
- You use dynamic properties or a
-
@kshegunov said:
Model indexes are not intended to be stored anyway (see the docs for more details).
True, but QPersistentModelIndex has the same issue. If I hold on to a QPersistentModelIndex that's still valid but the actual object its pointer refers to has been deleted, then it's still no good, because:
Well, you delete from the map when the underlying data has gone away. Which you can easily catch by subscribing to the
QObject::destroyed()
signal.That's the thing. When I say the objects in my data are ephemeral, what I mean is that they're constantly being destroyed and recreated even though the item in the model with that particular path / unique ID is still valid. There's a whole lot of machinery happening behind the scenes that neither QAbstractItemModel nor any other part of my app is supposed to know about. These objects may be deleted, and then attempting to access one at a particular key may trigger it to be recreated on demand. Or any number of items in the structure may be rearranged in memory but without actually changing the data it represents. The whole point of me setting it up that way is that the actual logical structure of the data and the physical arrangement and implementation of it are conceptually completely separate. The latter is abstracted away by the interface of the former.
So the only safe way to refer to or access any of the objects in this data is by its path / unique ID. And I can't use the object being destroyed as a sign that the unique ID / path that object was associated with no longer exists. The only way to know that an object no longer exists is to ask for it using one of my accessor functions and see that it's not there. My problem seems to be that QAbstractItemModel makes certain assumptions about how your data is implemented that runs contrary to what I have.
From what I understand you want to expose a
QObject
tree through an abstract model and you want to attach additional data for each object. I see two options:Not exactly. I want to be able to identify a particular item in my data through something more complex than an integer or memory address. Using QObject properties doesn't help because I need this ID to get the object in the first place, and the ID can't be represented by an integer.
So,
That all being said, I think I have a solution. When I said "the only way to know that an object no longer exists is to ask for it using one of my accessor functions and see that it's not there" I realized I could write a function that sweeps through my structure of IDs, finds the ones that don't refer to a valid object any more, and then delete them from the structure. I can then call this function at regular intervals or other points when it's safe to do so.
I'm still kind of new to using QAbstractItemModel, though, and if I knew what I know now back when I started implementing this, I would have chosen to do things differently. It's pretty clear to me now that the way I implemented my data just simply doesn't jive with QAbstractItemModel.
-
Hi @Guy-Gizmo,
I think that the design pattern of QModelIndex has to be ephemeral and offer a great way to handle datas structure between your view and your model. So the idea (i think) of this design, is to let Qt do his job with his QModelIndex and just provide a data structure "well formed". QModelIndex is just something for call back from the view... the model provide it back also from your datas structure (and do it each time you need). The concept of QModelIndex is definitly not to have static adresses.The idea of QModelIndex is also to be able to handle part of datas from a big source from anywhere (database or streams or... whatever datas come from) in time when you need it (and not hold that all the time).
Maybe you have a good reason to change the design pattern of Qt by do a special (and not indicate) use of QModelIndex, but i not understand this point. I tried before to do same as you because i was not well understood the idea of this design pattern around QModelIndex, who is a good design pattern, and then i used it the wrong way and loose time for finally do nothing more better than Qt QModelIndex does. I not said ti is you situation, because actually, i not understand the idea.
But, it may be an idea to reconsider your design pattern due to existing Qt QModelIndex design pattern, and use Qt QModelIndex has expected in the idea to achieve what you a re searching to do with your view and your model.
I will follow this thread for maybe lmearn something more and see the idea you are trusting and trying to achieve.
good luck.
-
@Guy-Gizmo said:
The whole point of me setting it up that way is that the actual logical structure of the data and the physical arrangement and implementation of it are conceptually completely separate. The latter is abstracted away by the interface of the former.
Then I think the cleanest solution is to have a tree (or w/e) that represents your logical structure, and that will abstract the whole physical layer. For a simple example you can look at the docs - the simple tree model.
You can put anything in that tree and it's basically your "data interface". It also shouldn't care that the physical nodes are destroyed as long as there's a logical node living. This way you don't have with model indexes and things getting destroyed in the background, because it's the middleman's responsibility. You should hover take care to notify the model when a node/leaf from the "logical structure" tree is destroyed (through the appropriate signals), so views can make proper adjustments.I hope that helps somewhat.
Kind regards.