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 two QSortFilterProxyModels. 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 warning QSortFilterProxyModel: 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 store QPersistentModelIndexes 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 the removeRows() function in the treemodel. In case the item is a leave after I removed its children (like here) I call invalidateFilter() 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 the QUndoCommand I stored a pointer to the item m_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?



  • I guess using a temporary object as parent

    m_children.at(row)->setParent(TreeItemPtr());
    

    might have something to do with this



  • @Jagh

    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
    

Log in to reply
 

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