QAbstractItemModel: how can I associate more data with a QModelIndex than just a pointer or integer?
-
I'm implemented a class derived from QAbstractItemModel. The standard way to create a QModelIndex that represents an item in my model is use
QAbstractItemModel::createIndex
and pass it a pointer to that item. However, I can't use that method: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. So rather than addressing each item by its location in memory, they have what is essentially a unique ID that indicates where in the model they are, and I can consistently use that to retrieve an item. This unique ID cannot be represented by an 8 byte integer, so I cannot use it with theinternalId
orinternalPointer
methods inQModelIndex
.Is there any way for me to create a QModelIndex that has more data associated with it than just a pointer?
One idea I've played around with is creating a separate structure that contains the unique ID for each of the items in my model, and having each instance of
QModelIndex
point to an entry in that. But not only does that totally suck because I'm creating an entirely new entity which introduces extra complexity and opportunities for bugs, but there's no good way for me to know when entries in this structure need to be deleted. I can't delete them when items in my model are deleted as that would defeat its purpose, and there's no way for me to know when aQModelIndex
is destroyed and no longer referring to an item in this structure. So that's out.What on Earth can I do here? And why can't I construct a
QModelIndex
with aQVariant
? That would easily fix this problem and makeQModelIndex
significantly more powerful and better to work with. -
hi
I was wondering if you could hash your unique ID so it could fit
in the
qint64 QModelIndex::internalId() const
long long int has ok range even for many items.Just a thought. might also suck :)
i think using QVariant with QModelIndex might be difficult with generic views as how can they know what to
take out of the variant. But it might be possible even QModelIndex dont seem to
feature many virtuals for overrides.anyway was just a note. hopefully others have more concrete suggestions :)
-
@mrjj said:
hi
I was wondering if you could hash your unique ID so it could fit
in the
qint64 QModelIndex::internalId() const
long long int has ok range even for many items.I did think of this, actually. The question is, what do I do in the case of a hash collision?
For example, I was thinking of creating a map that keys on the hash of a unique ID and whose value is a pointer to the object in my model with that unique ID. Every time an item in my model is created, it inserts itself into that map, and every time it's deleted it removes itself. Then when I need to get an item from a QModelIndex, I just look in that table using the hashed unique ID and I get the correct one.
But inevitably, two items will eventually have two unique IDs that have the same hash. How do I resolve that? A QModelIndex has no idea which one of the two is the correct one unless I can store more information in the QModelIndex, at which point I'm back to square one. :-(
-
well, when new item is created, you create the hash from the unique ID.
Then you if u get collision, you create new unique ID and hash it until no collision.
Of Course it all depends of your unique ID etc. if even possible. also how many items and the
hash-friendliness of the unique ID.Unless better suggestions later , you could test with current data and see how often u get collision.
-
@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.