Multiple Proxymodels on one Sourcemodel
-
Hello!
I'm trying to implement an editable treeview in qml that includes an undofunction. I have one source model (
QAbstractItemModel
) and twoQSortFilterProxyModel
s. One model is filtering out the leaves and one is displaying just the leaves. My problem is that removing an item from the treeview that has leaves as children leads to crashes. Before the crashes the warningQSortFilterProxyModel: inconsistent changes reported by source model
appears. It seems to me that due to the structure change in the source model (I remove leaves, so other items will be the new leaves) there are some inconsistencies in the proxymodel's indexes, but after trying for a long time to find a solution I just cannot come up with new ideas. Help would be greatly appreciated.I think its best if I show some code:
- The first step in the whole process is pressing the delete button in the treeview. This creates a
QUndoCommand
undoManager.createDeleteKeyCommand(treeModel, noLeavesProxyModel.mapToSource(treeView.currentIndex))
- The undomanager pushes the command on the stack
void UndoManager::createDeleteKeyCommand(TreeModel* model, const QModelIndex &index) { if(index.isValid()) m_undoStack->push(new DeleteKeyCommand(model, index)); }
- Pushing the command on the stack calls the
redo()
function. I've experienced myself that it is not possible to storeQPersistentModelIndex
es in a case like this, so the location of the item is stored in the form of a Path
void DeleteKeyCommand::redo() { QModelIndex index = m_model->pathToIndex(m_path); if (index.isValid()) { m_model->removeRow(m_row, index); } }
removeRow()
calls theremoveRows()
function in the treemodel. In case the item is a leave after I removed its children (like here) I callinvalidateFilter()
so the views update accordingly (they do not if I miss this)
bool TreeModel::removeRows(int row, int count, const QModelIndex &parent) { TreeItem *parentItem = getItem(parent); Q_ASSERT(parentItem); bool success = true; bool noChildren = parentItem->childCount() == count; beginRemoveRows(parent, row, row + count - 1); success = parentItem->removeChildren(row, count); endRemoveRows(); if(noChildren){ emit invalidateFilter(); } return success; }
- Finally the item gets removed from the
QList
. Since I do not delete the item I change its parent so that it is detached from the tree.
bool TreeItem::removeChildren(int row, int count) { if(row < 0 || row + count > m_children.count()) return false; for(int i = 0; i < count; i++) { m_children.at(row)->setParent(TreeItemPtr()); m_children.removeAt(row); } return true; }
- To undo the deletion
undo()
is called (when creating theQUndoCommand
I stored a pointer to the itemm_item(qvariant_cast<TreeItemPtr>(model->data(index, TreeModel::ItemRole)))
)...
void DeleteKeyCommand::undo() { QModelIndex index = m_model->pathToIndex(m_path); if (index.isValid()) { QList<TreeItemPtr> items; Q_ASSERT(m_item); items.append(m_item); m_model->setItemsToInsert(items); m_model->insertRows(m_row, items.count(), index); } }
- ... which calls the
insertRows()
function ...
bool TreeModel::insertRows(int row, int count, const QModelIndex &parent) { TreeItem *parentItem = getItem(parent); Q_ASSERT(parentItem); bool success = true; bool noChildren = parentItem->childCount() == 0; QList<TreeItemPtr> items = getItemsToInsert(); if(items.isEmpty()) { qDebug() << "No items to insert"; return false; } Q_ASSERT(count == items.count()); beginInsertRows(parent, row, row + count - 1); foreach (TreeItemPtr item, items) { TreeItemPtr p = getItemPtr(parent); Q_ASSERT(p); item->setParent(p); } success = parentItem->insertChildren(row, items); endInsertRows(); if(noChildren){ emit invalidateFilter(); } return success; }
- ... and inserts the item back into the
QList
(after resetting the parentitem)
bool TreeItem::insertChildren(int index, QList<QSharedPointer<TreeItem> > items) { if (index < 0 || index > m_children.count()) return false; foreach(TreeItemPtr item, items) { m_children.insert(index, item); } return true; }
The whole process works fine except when I remove an item that has leaves as children. Pressing undo and redo multiple times will lead to a crash. Anybody got an idea what the problem might be?
- The first step in the whole process is pressing the delete button in the treeview. This creates a
-
I guess using a temporary object as parent
m_children.at(row)->setParent(TreeItemPtr());
might have something to do with this
Thank you for your answer. For testing purposes I created an item in the treemodel that definitely exists for the whole time the application is running and added it to the
removeChildren(row, count, m_tmpParent)
signature so it can be set as temporary parent but that did not change anything (as does not setting the parent at all)ratbr QModelIndex(0,0,0x1d60a80,NoLeavesProxyModel(0x7fffffffe070)) 0 0 rr QModelIndex(0,0,0x1d60a80,NoLeavesProxyModel(0x7fffffffe070)) 0 0 ratbr QModelIndex(0,0,0x8f7940,TreeModel(0x7fffffffe0f0)) 0 0 QSortFilterProxyModel: inconsistent changes reported by source model rr QModelIndex(0,0,0x8f7940,TreeModel(0x7fffffffe0f0)) 0 0 ratbr QModelIndex(0,0,0x1d61510,NoLeavesProxyModel(0x7fffffffe070)) 0 0 rr QModelIndex(0,0,0x1d61510,NoLeavesProxyModel(0x7fffffffe070)) 0 0