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 the internalId or internalPointer methods in QModelIndex.

    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 a QModelIndex 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 a QVariant? That would easily fix this problem and make QModelIndex significantly more powerful and better to work with.


  • Qt Champions 2016

    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. :-(


  • Qt Champions 2016

    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.



  • @mrjj Unfortunately the unique ID represents where in my data model the item is, so there's no way to create a new unique ID in the case of a collision.


  • Qt Champions 2016

    @Guy-Gizmo
    ok. so its already "fixed" at that point.
    how many items are we talking about?



  • @mrjj Hard to say exactly. The model represents a user-editable library of data, so the number of items could range anywhere from tens to thousands.


  • Lifetime Qt Champion

    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.


  • Lifetime Qt Champion

    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 In structure it is very much 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.



  • What's stopping you to create your id in heap ?
    MyStruct* id_ptr = new MyStruct(...)
    m_cleanup_ids_stack.push_back(id_ptr)
    return createIndex(row, column, id_ptr);

    After that delete all items of m_cleanup_ids_stack in destructor ?



  • @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



  • @jalomic Unfortunately this model stays around essentially for the lifetime of the application, so it will still be possible for m_cleanup_ids_stack to grow without bound.


  • Qt Champions 2016

    @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:

    1. 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 a QModelIndex
    2. 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 the QObject 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.



  • @kshegunov You forgot to notify model about data destruction ! beginRemove... etc



  • @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.


  • Qt Champions 2016

    @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.



Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.