Results 1 to 7 of 7

Thread: Implementing a QAbstractItemModel that reacts to external changes

  1. #1
    Join Date
    Jan 2020
    Posts
    4
    Qt products
    Qt5
    Platforms
    MacOS X Unix/X11 Windows

    Question Implementing a QAbstractItemModel that reacts to external changes

    I'm writing a QAbstractItemModel for a tree view that represents a hierarchy of QObjects.
    I'm storing the pointer to the QObject as the internal pointer in the model indexes.

    Sometimes the objects are added/removed from the hierarchy (always outside the view / item model) and I want the model to notify the view about the change.

    The problem I'm having is that functions like beginRemoveRows etc say that they need to be called before the removal has happened and functions like endRemoveRows should happen after the data has been removed.

    Since I'm reacting to changes happening outside the model this isn't possible as I can be notified of the change having already happened in the storage.
    I tried just calling the begin/end functions but doing that causes the model to call parent() passing indexes with objects which have already been deleted.

    Is there any way around this issue?

  2. #2
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Implementing a QAbstractItemModel that reacts to external changes

    QObject instances send a signal (QObject::destroyed()) just before they are completely destroyed. If you are storing pointers to QObjects in your model, you could connect a slot to these signals and set the stored pointers to null. Obviously when you access a stored pointer, you'll have to also check it for validity. Depending on how large your tree is, traversing the tree to find the QModelIndex containing a specific QObject pointer could be expensive, so you might want to create a map between QObject and QModelIndex (using QPersistentModelIndex).

    If this is not workable, then instead of using begin/end..Rows() calls, use beginModelReset / endModelReset. This tells any views that the entire model is invalid, so it will not try to access anything and will simply re-create the entire view from scratch. This also works if you have proxy models in-between your core model and views.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  3. #3
    Join Date
    Jan 2020
    Posts
    4
    Qt products
    Qt5
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing a QAbstractItemModel that reacts to external changes

    I'm already using the destroyed signal (calling beginRemoveRows / endRemoveRows from there) the invalid objects are accessed on beginInsertRows when something is added after something else got deleted.
    I tried using reset model but that collapses all tree items in the view which is annoying.

    At the moment I'm using a dirty work-around that keeps all pointers in a set and add/removes them recursively when something changes, then checking for validity to avoid segmentation faults.
    But I still haven't figured why Qt is sending me invalid indexes from rows that have already been deleted.

  4. #4
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Implementing a QAbstractItemModel that reacts to external changes

    But I still haven't figured why Qt is sending me invalid indexes from rows that have already been deleted.
    Maybe you are sending the wrong indexes or parent in your calls to beginRemoveRows() ?
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  5. #5
    Join Date
    Jan 2020
    Posts
    4
    Qt products
    Qt5
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing a QAbstractItemModel that reacts to external changes

    I don't think so. The view does react correctly, removing the rows that have been deleted.

    This is the relevant code, element_deleted is connected to QObject::destroyed
    Qt Code:
    1. QModelIndex ProjectModel::index_from_qobject(QObject* obj) const
    2. {
    3. if ( !obj->parent() )
    4. return createIndex(0, 0, obj);
    5. return createIndex(obj->parent()->children().indexOf(obj), 0, obj);
    6. }
    7.  
    8. void ProjectModel::element_deleted(QObject* obj)
    9. {
    10. QObject* parent = obj->parent();
    11. auto parent_index = index_from_qobject(parent);
    12. int row = parent->children().indexOf(obj);
    13. beginRemoveRows(parent_index, row, row);
    14. endRemoveRows();
    15. }
    To copy to clipboard, switch view to plain text mode 

  6. #6
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Implementing a QAbstractItemModel that reacts to external changes

    You are accessing the properties of the QObject even though it has been destroyed and those properties are almost certainly no longer valid. For example, in line 10, you access the object's parent(). You don't check to see if the pointer returned is non-null before passing it to index_from_qobject(), which then checks -that- QObject's parent(), and further, accesses that parent's children() and the indexOf() for those children. Nowhere do you check any return value for validity. Any of those parent() calls could be returning a null pointer, and the children() call could be returning an empty list.

    I think that by the time you receive the QObject::destroyed() signal, you have to assume that the QObject pointer is no better than a void *, since you really don't know where in the destruction cycle that signal get emitted. Maybe it is bottom-up in the QObject parent-child hierarchy, but you can't assume that the parent of the QObject hasn't already cleaned up its children() list before deleting the child. So maybe indexOf() is returning -1.

    I think the basic problem is that your tree model is coupled too closely with the QObject hierarchy, and you are relying on the QObject hierarchy remaining intact throughout the deletion process so you can maintain your tree hierarchy. You need to decouple them by creating a separate tree data structure that mirrors the QObject hierarchy but has its own methods for determining parent-child relationships, independently of the QObject hierarchy.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  7. #7
    Join Date
    Jan 2020
    Posts
    4
    Qt products
    Qt5
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implementing a QAbstractItemModel that reacts to external changes

    I'm just going by the documentation

    https://doc.qt.io/qt-5/qobject.html#destroyed

    destroyed is emitted while the object still exists, debugging shows it still has the parent pointer set up correctly.

    Children are destroyed before their parents in my setup but in any case I'm having this issue with leaf objects

Similar Threads

  1. doubleclick with tableview reacts only once
    By qt_gotcha in forum Newbie
    Replies: 1
    Last Post: 13th July 2010, 18:17
  2. Replies: 0
    Last Post: 2nd June 2010, 18:52
  3. QAbstractItemModel Using External QList Examples
    By jeffpogo in forum Qt Programming
    Replies: 2
    Last Post: 13th July 2009, 16:52
  4. Implementing threads...
    By Abc in forum Qt Programming
    Replies: 7
    Last Post: 12th June 2008, 09:41
  5. Implementing IME using QT
    By pmohod in forum Qt Programming
    Replies: 0
    Last Post: 16th February 2007, 13:22

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.