Unsolved 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:
Here's what it's supposed to look like (no children). I'm not using a QSortFilterProxyModel in this one:
Here's what happens when I use a QSortFilterProxyModel (the white part is a selected row):
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.
-
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.
-
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:
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.