I am learning how to use proxy models recently, And I want to create a custom proxy model that can flat nodes in a source tree model to a list model, I have found a good solution to this: How to create a proxy model that would flatten nodes of a QAbstractItemModel into a list in PySide?
However, when I try to removeRows()
(remove tree node) from the source tree model, the proxy model crashes, I guess it's because the source model didn't emit layoutChanged
signal to the proxy model to refresh the self.m_rowMap and self.m_indexMap?
【question1】: How to fix the crash?
【question2】:For QSortFilterProxyModel
, removeRows()
from the source model won't crash the proxy model, so I also want to know the underlying mechanism of QSortFilterProxyModel
, especially the implementation of the following methods:
setSourceModel(),
mapFromSource(),
mapToSource(),
mapSelectionFromSource(),
mapSelectionToSource()
Especially how it emits signals between the sourceModel
and the QSortFilterProxyModel
?
Reproduce example:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import (QAbstractProxyModel, QModelIndex, pyqtSlot)
class FlatProxyModel(QAbstractProxyModel):
def __init__(self, parent=None):
# super(FlatProxyModel, self).__init__(parent)
super().__init__(parent)
def buildMap(self, model, parent=QModelIndex(), row=0):
"""
Usage:
* to build the rowMap and indexMap of the treeModel
"""
if row == 0:
self.m_rowMap = {}
self.m_indexMap = {}
rows = model.rowCount(parent)
for r in range(rows):
index = model.index(r, 0, parent)
print('row', row, 'item', model.data(index))
self.m_rowMap[index] = row
self.m_indexMap[row] = index
row = row + 1
if model.hasChildren(index):
row = self.buildMap(model, index, row)
return row
def setSourceModel(self, model):
QAbstractProxyModel.setSourceModel(self, model)
self.buildMap(model)
print(flush=True)
model.dataChanged.connect(self.sourceDataChanged)
def mapFromSource(self, index):
if index not in self.m_rowMap:
return QModelIndex()
# print('mapping to row', self.m_rowMap[index], flush = True)
return self.createIndex(self.m_rowMap[index], index.column())
def mapToSource(self, index):
if not index.isValid() or (index.row() not in self.m_indexMap):
return QModelIndex()
# print('mapping from row', index.row(), flush = True)
return self.m_indexMap[index.row()]
def columnCount(self, parent):
return QAbstractProxyModel.sourceModel(self).columnCount(self.mapToSource(parent))
def rowCount(self, parent):
# print('rows:', len(self.m_rowMap), flush=True)
return len(self.m_rowMap) if not parent.isValid() else 0
def index(self, row, column, parent):
# print('index for:', row, column, flush=True)
if parent.isValid():
return QModelIndex()
return self.createIndex(row, column)
def parent(self, index):
return QModelIndex()
@pyqtSlot(QModelIndex, QModelIndex)
def sourceDataChanged(self, topLeft, bottomRight):
self.dataChanged.emit(self.mapFromSource(topLeft),
self.mapFromSource(bottomRight))
class myWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel()
names = ['Foo', 'Bar', 'Baz']
for first in names:
row = QtGui.QStandardItem(first)
for second in names:
row.appendRow(QtGui.QStandardItem(first + second))
self.model.appendRow(row)
self.proxy = FlatProxyModel()
self.proxy.setSourceModel(self.model)
self.nestedProxy = FlatProxyModel()
self.nestedProxy.setSourceModel(self.proxy)
vLayout = QtWidgets.QVBoxLayout(self)
hLayout = QtWidgets.QHBoxLayout()
self.treeView = QtWidgets.QTreeView()
self.treeView.setModel(self.model)
self.treeView.expandAll()
self.treeView.header().hide()
hLayout.addWidget(self.treeView)
self.listView1 = QtWidgets.QListView()
self.listView1.setModel(self.proxy)
hLayout.addWidget(self.listView1)
self.listView2 = QtWidgets.QListView()
self.listView2.setModel(self.nestedProxy)
hLayout.addWidget(self.listView2)
vLayout.addLayout(hLayout)
removeButton = QtWidgets.QPushButton('Remove')
removeButton.clicked.connect(self.removeItems)
vLayout.addWidget(removeButton)
def removeItems(self):
index = self.treeView.currentIndex()
model = index.model()
model.removeRows(index.row(), 1, index.parent())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = myWidget()
w.show()
sys.exit(app.exec_())