QAbstractItemModel + QSortFilterProxyModel : removing rows does not work



  • I'm using PySide, but the issue I'm having might be the same with plain Qt.
    I'm getting bugs when I try to remove rows from a QAbstractItemModel when I use a QSortFilterProxyModel with it. If I don't use the QSortFilterProxyModel, it works as expected.

    Here it is before removing the rows:
    alt text

    Here's what it's supposed to look like (no children). I'm not using a QSortFilterProxyModel in this one:
    alt text

    Here's what happens when I use a QSortFilterProxyModel (the white part is a selected row):
    alt text

    Here is a simplified version of my code:

    self._treeView = QTreeView()
    self._customModel = CustomModel()
    self._proxyModel = QSortFilterProxyModel()
    self._proxyModel.setSourceModel(self._customModel)
    self._treeView.setModel(self._proxyModel)
    

    I'm completely stumped why it's like this. I'd really appreciate any help.


  • Lifetime Qt Champion

    Hi,

    What do you mean by remove row ? When it's filtered or when you delete it from your model ?



  • I'm actually deleting the rows from my model by using beginRemoveRows(), then removing all of the parent item's children, then calling endRemoveRows().



  • Hi,

    are you getting the QModelIndex from your proxy model or from your QAIM's model? If you're using your QAIM's model, you should get the index using mapFromSource:

    void removeRow(const QModelIndex& parent)
    {
    QModelIndex index =  _yourProxyModel->mapFromSource(parent);
    beginRemoveRows(index,....);
    // do you cleaning 
    endRemoveRows();
    }
    

    hope it helps



  • I'm using the index from my source model. In setData, if the version is changed, then it removes all children, then adds the new children for that version.
    In the example below, I'm just removing children because I want to get that working before I add the new children.

    def setData(self, index, value, role=Qt.EditRole):
            if not index.isValid():
                return False
            
            if role == Qt.EditRole:
                if index.column() == versionColumn:
                    item = index.internalPointer()
                    last = self.rowCount(index) - 1
                    self.beginRemoveRows(index, 0, last)
                    item._children = []
                    self.endRemoveRows()
                    return True
            
            return False
    


  • try to map the index to your proxymodel as I said...I'm pretty sure the magie will come here.


  • Lifetime Qt Champion

    There's something strange with your logic here: why does doing any change to the versionColumn trigger that delete ? Also is _children really related to the model itself in terms of indexes ?



  • instead of doing it manually with

        self.beginRemoveRows(index, 0, last)
        item._children = []
        self.endRemoveRows()
    

    try using removeRows() if that works.

    On a separate note I'm not sure inserting/deleting rows in the setData method is such a good design



  • I was able to remove rows using everyone's suggestions.

    I implemented the removeRows method:

    def removeRows(self, row, count, parent=QModelIndex()):
            last = row + count - 1
            self.beginRemoveRows(parent, row, last)
            item = parent.internalPointer()
            item.removeRows(row, count)
            self.endRemoveRows()
    

    I emit the dataChanged signal in setData:

    def setData(self, index, value, role=Qt.EditRole):
            if not index.isValid():
                return False
            
            item = index.internalPointer()
            
            if role == Qt.EditRole:
                if index.column() == versionColumn:
                    item.currentVersion = value
                    self.dataChanged.emit(index, index)
                    return True
            
            return False
    

    I connect to the dataChanged signal of the QSortFilterProxyModel. By calling removeRows from the QSortFilterProxyModel instead of inside the QAbstractItemModel, it seems to have fixed the issue:

    @Slot(QModelIndex, QModelIndex)
    def onVersionDataChanged(self, topLeft, bottomRight):
        if topLeft.column() == versionColumn:
            count = self._versionSortFilterProxyModel.rowCount(topLeft)
            self._versionSortFilterProxyModel.removeRows(0, count, topLeft)
    

    However, using insertRows does not work properly now. Right after remove rows I do this:

    sourceIndex = self._versionSortFilterProxyModel.mapToSource(topLeft)
    item = sourceIndex.internalPointer()
    self._versionSortFilterProxyModel.insertRows(0, item.childrenToCreate, topLeft)
    
    def insertRows(self, row, count, parent=QModelIndex()):
        last = row + count - 1
        self.beginInsertRows(parent, row, last)
        item = parent.internalPointer()
        item.createChildren()
        self.endInsertRows()
        return True
    

    Here's what it looks like:
    alt text

    It started with 4 rows, I removed all of them, then I inserted 3 rows. Yet there are still 4 (the fourth one is blank).

    What I was really hoping for was begineResetModel()/endResetModel() but for 1 row instead. However, there is nothing like this which is why I'm using remove then insert.

    @SGaist
    Changing the version needs to change the children, because the children represent the contents of that version. A different version will contain different contents which is why I'm removing then inserting after a version is set.



  • I found a solution which is not ideal, but it was the only thing that really worked.
    I put my code back to the way it was before and instead of using removeRows and insertRows for an item, I emit layoutAboutToBeChanged, then I change the version and the item's children, then I emit layoutChanged.

    The reason this is not ideal is because the whole view gets updated instead of just the item and its children. I'm sure this means that every item gets sorted and filtered again. It seems that this has changed with Qt 5.x, but unfortunately PySide does not support Qt 5.x.


Log in to reply
 

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