QAbstractItemModel + QSortFilterProxyModel : removing rows does not work
-
wrote on 23 Apr 2016, 04:44 last edited by
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 ?
-
wrote on 25 Apr 2016, 22:38 last edited by
I'm actually deleting the rows from my model by using beginRemoveRows(), then removing all of the parent item's children, then calling endRemoveRows().
-
wrote on 26 Apr 2016, 07:45 last edited by
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
-
wrote on 26 Apr 2016, 22:37 last edited by
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
-
wrote on 27 Apr 2016, 06:34 last edited by
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 ? -
wrote on 27 Apr 2016, 07:23 last edited by
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
-
wrote on 28 Apr 2016, 01:25 last edited by elveatles
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
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. -
wrote on 30 Apr 2016, 00:06 last edited by
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.
5/10